diff --git a/native/src/Android.mk b/native/src/Android.mk index e3130d0b5..406fd5cc7 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -168,7 +168,6 @@ LOCAL_SRC_FILES := \ sepolicy/api.cpp \ sepolicy/sepolicy.cpp \ sepolicy/policydb.cpp \ - sepolicy/statement.cpp \ sepolicy/policy-rs.cpp include $(BUILD_STATIC_LIBRARY) diff --git a/native/src/base/misc.rs b/native/src/base/misc.rs index fd87e2d01..ff9d1dfd0 100644 --- a/native/src/base/misc.rs +++ b/native/src/base/misc.rs @@ -1,5 +1,7 @@ +use std::fmt::Arguments; +use std::io::Write; use std::process::exit; -use std::{io, slice, str}; +use std::{fmt, io, slice, str}; use argh::EarlyExit; use libc::c_char; @@ -117,3 +119,16 @@ impl EarlyExitExt for Result { } } } + +pub struct FmtAdaptor<'a, T>(pub &'a mut T) +where + T: Write; + +impl fmt::Write for FmtAdaptor<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error) + } + fn write_fmt(&mut self, args: Arguments<'_>) -> fmt::Result { + self.0.write_fmt(args).map_err(|_| fmt::Error) + } +} diff --git a/native/src/sepolicy/api.cpp b/native/src/sepolicy/api.cpp index 843f47d22..a69de7163 100644 --- a/native/src/sepolicy/api.cpp +++ b/native/src/sepolicy/api.cpp @@ -143,7 +143,7 @@ void sepolicy::type(Str type, StrVec attrs) { void sepolicy::attribute(Str name) { expand(name, [this](auto ...args) { - print_rule("name", args...); + print_rule("attribute", args...); impl->add_type(args..., TYPE_ATTRIB); }); } diff --git a/native/src/sepolicy/lib.rs b/native/src/sepolicy/lib.rs index 9c44dbfed..a5e08e888 100644 --- a/native/src/sepolicy/lib.rs +++ b/native/src/sepolicy/lib.rs @@ -1,4 +1,5 @@ #![feature(format_args_nl)] +#![feature(try_blocks)] use io::Cursor; use std::fmt::Write; @@ -8,7 +9,8 @@ use std::pin::Pin; pub use base; use base::libc::{O_CLOEXEC, O_RDONLY}; -use base::{error, BufReadExt, FsPath, LoggedResult, Utf8CStr}; +use base::{BufReadExt, FsPath, LoggedResult, Utf8CStr}; +use statement::{parse_statement, print_statement_help}; use crate::ffi::sepolicy; @@ -88,6 +90,7 @@ mod ffi { fn parse_statement(sepol: Pin<&mut sepolicy>, statement: Utf8CStrRef); fn magisk_rules(sepol: Pin<&mut sepolicy>); fn xperm_to_string(perm: &Xperm) -> String; + fn print_statement_help(); } } @@ -95,7 +98,6 @@ trait SepolicyExt { fn load_rules(self: Pin<&mut Self>, rules: &[u8]); fn load_rule_file(self: Pin<&mut Self>, filename: &Utf8CStr); fn load_rules_from_reader(self: Pin<&mut Self>, reader: &mut T); - fn parse_statement(self: Pin<&mut Self>, statement: &str); } impl SepolicyExt for sepolicy { @@ -105,13 +107,12 @@ impl SepolicyExt for sepolicy { } fn load_rule_file(self: Pin<&mut sepolicy>, filename: &Utf8CStr) { - fn inner(sepol: Pin<&mut sepolicy>, filename: &Utf8CStr) -> LoggedResult<()> { + let result: LoggedResult<()> = try { let file = FsPath::from(filename).open(O_RDONLY | O_CLOEXEC)?; let mut reader = BufReader::new(file); - sepol.load_rules_from_reader(&mut reader); - Ok(()) - } - inner(self, filename).ok(); + self.load_rules_from_reader(&mut reader); + }; + result.ok(); } fn load_rules_from_reader(mut self: Pin<&mut sepolicy>, reader: &mut T) { @@ -120,16 +121,10 @@ impl SepolicyExt for sepolicy { if line.is_empty() { return true; } - self.as_mut().parse_statement(line); + parse_statement(self.as_mut(), line); true }); } - - fn parse_statement(self: Pin<&mut Self>, statement: &str) { - if statement::parse_statement(self, statement).is_err() { - error!("sepolicy rule syntax error: {statement}"); - } - } } fn load_rule_file(sepol: Pin<&mut sepolicy>, filename: &Utf8CStr) { @@ -140,10 +135,6 @@ fn load_rules(sepol: Pin<&mut sepolicy>, rules: &[u8]) { sepol.load_rules(rules); } -fn parse_statement(sepol: Pin<&mut sepolicy>, statement: &Utf8CStr) { - sepol.parse_statement(statement.as_str()); -} - trait SepolicyMagisk { fn magisk_rules(self: Pin<&mut Self>); } diff --git a/native/src/sepolicy/main.cpp b/native/src/sepolicy/main.cpp index 170f1a38e..1c30da377 100644 --- a/native/src/sepolicy/main.cpp +++ b/native/src/sepolicy/main.cpp @@ -85,7 +85,8 @@ int main(int argc, char *argv[]) { rule_files.emplace_back(argv[i + 1]); ++i; } else if (option == "help"sv) { - statement_help(); + rust::print_statement_help(); + exit(0); } else { usage(argv[0]); } diff --git a/native/src/sepolicy/policy.hpp b/native/src/sepolicy/policy.hpp index 148e63405..ea082ed06 100644 --- a/native/src/sepolicy/policy.hpp +++ b/native/src/sepolicy/policy.hpp @@ -40,5 +40,3 @@ private: }; #define impl reinterpret_cast(this) - -void statement_help(); diff --git a/native/src/sepolicy/sepolicy.cpp b/native/src/sepolicy/sepolicy.cpp index f4c5b4c0a..82cecc8e3 100644 --- a/native/src/sepolicy/sepolicy.cpp +++ b/native/src/sepolicy/sepolicy.cpp @@ -715,7 +715,7 @@ void sepol_impl::print_type(FILE *fp, type_datum_t *type) { } } if (ebitmap_get_bit(&db->permissive_map, type->s.value)) { - fprintf(stdout, "permissive %s\n", name); + fprintf(fp, "permissive %s\n", name); } } diff --git a/native/src/sepolicy/statement.cpp b/native/src/sepolicy/statement.cpp deleted file mode 100644 index c4fac2583..000000000 --- a/native/src/sepolicy/statement.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include -#include -#include - -#include - -#include "policy.hpp" - -using namespace std; - -static const char *type_msg_1 = -R"EOF("allow *source_type *target_type *class *perm_set" -"deny *source_type *target_type *class *perm_set" -"auditallow *source_type *target_type *class *perm_set" -"dontaudit *source_type *target_type *class *perm_set" -)EOF"; - -static const char *type_msg_2 = -R"EOF("allowxperm *source_type *target_type *class operation xperm_set" -"auditallowxperm *source_type *target_type *class operation xperm_set" -"dontauditxperm *source_type *target_type *class operation xperm_set" -- The only supported operation right now is 'ioctl' -- xperm_set is one or multiple hexadecimal numeric values ranging from 0x0000 to 0xFFFF. - Multiple values consist of a space separated list enclosed in braces ({}). - Use the complement operator (~) to specify all permissions except those explicitly listed. - Use the range operator (-) to specify all permissions within the low – high range. - Use the match all operator (*) to match all ioctl commands. - The special value 0 is used to clear all rules. - Some examples: - allowxperm source target class ioctl 0x8910 - allowxperm source target class ioctl { 0x8910-0x8926 0x892A-0x8935 } - allowxperm source target class ioctl ~{ 0x8910 0x892A } - allowxperm source target class ioctl * -)EOF"; - -static const char *type_msg_3 = -R"EOF("permissive *type" -"enforce *type" -)EOF"; - -static const char *type_msg_4 = -R"EOF("typeattribute ^type ^attribute" -)EOF"; - -static const char *type_msg_5 = -R"EOF("type type_name ^(attribute)" -- Argument 'attribute' is optional, default to 'domain' -)EOF"; - -static const char *type_msg_6 = -R"EOF("attribute attribute_name" -)EOF"; - -static const char *type_msg_7 = -R"EOF("type_transition source_type target_type class default_type (object_name)" -- Argument 'object_name' is optional -)EOF"; - -static const char *type_msg_8 = -R"EOF("type_change source_type target_type class default_type" -"type_member source_type target_type class default_type" -)EOF"; - -static const char *type_msg_9 = -R"EOF("genfscon fs_name partial_path fs_context" -)EOF"; - -void statement_help() { - fprintf(stderr, -R"EOF(One policy statement should be treated as one parameter; -this means each policy statement should be enclosed in quotes. -Multiple policy statements can be provided in a single command. - -Statements has a format of " [args...]". -Arguments labeled with (^) can accept one or more entries. -Multiple entries consist of a space separated list enclosed in braces ({}). -Arguments labeled with (*) are the same as (^), but additionally -support the match-all operator (*). - -Example: "allow { s1 s2 } { t1 t2 } class *" -Will be expanded to: - -allow s1 t1 class { all-permissions-of-class } -allow s1 t2 class { all-permissions-of-class } -allow s2 t1 class { all-permissions-of-class } -allow s2 t2 class { all-permissions-of-class } - -Supported policy statements: - -%s -%s -%s -%s -%s -%s -%s -%s -%s -)EOF", type_msg_1, type_msg_2, type_msg_3, type_msg_4, -type_msg_5, type_msg_6, type_msg_7, type_msg_8, type_msg_9); - exit(0); -} diff --git a/native/src/sepolicy/statement.rs b/native/src/sepolicy/statement.rs index a8aa182d6..3ba4d7b16 100644 --- a/native/src/sepolicy/statement.rs +++ b/native/src/sepolicy/statement.rs @@ -1,6 +1,8 @@ +use std::fmt::{Display, Formatter, Write}; +use std::io::stderr; use std::{iter::Peekable, pin::Pin, vec::IntoIter}; -use base::{LoggedError, LoggedResult}; +use base::{error, warn, FmtAdaptor}; use crate::ffi::Xperm; use crate::sepolicy; @@ -34,14 +36,29 @@ pub enum Token<'a> { } type Tokens<'a> = Peekable>>; +type SimpleResult = Result; +type ParseResult<'a> = Result<(), ParseError<'a>>; + +enum ParseError<'a> { + General, + AvtabAv(Token<'a>), + AvtabXperms(Token<'a>), + AvtabType(Token<'a>), + TypeState(Token<'a>), + TypeAttr, + TypeTrans, + NewType, + NewAttr, + GenfsCon, +} macro_rules! throw { () => { - Err(LoggedError::default())? + Err(())? }; } -fn parse_id<'a>(tokens: &mut Tokens<'a>) -> LoggedResult<&'a str> { +fn parse_id<'a>(tokens: &mut Tokens<'a>) -> SimpleResult<&'a str> { match tokens.next() { Some(Token::ID(name)) => Ok(name), _ => throw!(), @@ -52,7 +69,7 @@ fn parse_id<'a>(tokens: &mut Tokens<'a>) -> LoggedResult<&'a str> { // names ::= names(mut v) CM ID(n) { v.push(n); v }; // term ::= ID(n) { vec![n] } // term ::= LB names(n) RB { n }; -fn parse_term<'a>(tokens: &mut Tokens<'a>) -> LoggedResult> { +fn parse_term<'a>(tokens: &mut Tokens<'a>) -> SimpleResult> { match tokens.next() { Some(Token::ID(name)) => Ok(vec![name]), Some(Token::LB) => { @@ -77,7 +94,7 @@ fn parse_term<'a>(tokens: &mut Tokens<'a>) -> LoggedResult> { // terms ::= ST { vec![] } // terms ::= term(n) { n } -fn parse_terms<'a>(tokens: &mut Tokens<'a>) -> LoggedResult> { +fn parse_terms<'a>(tokens: &mut Tokens<'a>) -> SimpleResult> { match tokens.peek() { Some(Token::ST) => { tokens.next(); @@ -89,7 +106,7 @@ fn parse_terms<'a>(tokens: &mut Tokens<'a>) -> LoggedResult> { // xperm ::= HX(low) { Xperm{low, high: low, reset: false} }; // xperm ::= HX(low) HP HX(high) { Xperm{low, high, reset: false} }; -fn parse_xperm(tokens: &mut Tokens) -> LoggedResult { +fn parse_xperm(tokens: &mut Tokens) -> SimpleResult { let low = match tokens.next() { Some(Token::HX(low)) => low, _ => throw!(), @@ -118,7 +135,7 @@ fn parse_xperm(tokens: &mut Tokens) -> LoggedResult { // // xperm_list ::= xperm(p) { vec![p] } // xperm_list ::= xperm_list(mut l) xperm(p) { l.push(p); l } -fn parse_xperms(tokens: &mut Tokens) -> LoggedResult> { +fn parse_xperms(tokens: &mut Tokens) -> SimpleResult> { let mut xperms = Vec::new(); let reset = match tokens.peek() { Some(Token::TL) => { @@ -188,98 +205,147 @@ fn parse_xperms(tokens: &mut Tokens) -> LoggedResult> { // statement ::= TC ID(s) ID(t) ID(c) ID(d) { extra.as_mut().type_change(s, t, c, d); }; // statement ::= TM ID(s) ID(t) ID(c) ID(d) { extra.as_mut().type_member(s, t, c, d);}; // statement ::= GF ID(s) ID(t) ID(c) { extra.as_mut().genfscon(s, t, c); }; -fn exec_statement(sepolicy: Pin<&mut sepolicy>, tokens: &mut Tokens) -> LoggedResult<()> { +fn exec_statement<'a>(sepolicy: Pin<&mut sepolicy>, tokens: &'a mut Tokens) -> ParseResult<'a> { let action = match tokens.next() { Some(token) => token, - _ => throw!(), + _ => Err(ParseError::General)?, }; match action { Token::AL | Token::DN | Token::AA | Token::DA => { - let s = parse_terms(tokens)?; - let t = parse_terms(tokens)?; - let c = parse_terms(tokens)?; - let p = parse_terms(tokens)?; - match action { - Token::AL => sepolicy.allow(s, t, c, p), - Token::DN => sepolicy.deny(s, t, c, p), - Token::AA => sepolicy.auditallow(s, t, c, p), - Token::DA => sepolicy.dontaudit(s, t, c, p), - _ => unreachable!(), + let result: SimpleResult<()> = try { + let s = parse_terms(tokens)?; + let t = parse_terms(tokens)?; + let c = parse_terms(tokens)?; + let p = parse_terms(tokens)?; + match action { + Token::AL => sepolicy.allow(s, t, c, p), + Token::DN => sepolicy.deny(s, t, c, p), + Token::AA => sepolicy.auditallow(s, t, c, p), + Token::DA => sepolicy.dontaudit(s, t, c, p), + _ => unreachable!(), + } + }; + if result.is_err() { + Err(ParseError::AvtabAv(action))? } } Token::AX | Token::AY | Token::DX => { - let s = parse_terms(tokens)?; - let t = parse_terms(tokens)?; - let c = parse_terms(tokens)?; - let p = if matches!(tokens.next(), Some(Token::IO)) { - parse_xperms(tokens)? - } else { - throw!() + let result: SimpleResult<()> = try { + let s = parse_terms(tokens)?; + let t = parse_terms(tokens)?; + let c = parse_terms(tokens)?; + let p = if matches!(tokens.next(), Some(Token::IO)) { + parse_xperms(tokens)? + } else { + throw!() + }; + match action { + Token::AX => sepolicy.allowxperm(s, t, c, p), + Token::AY => sepolicy.auditallowxperm(s, t, c, p), + Token::DX => sepolicy.dontauditxperm(s, t, c, p), + _ => unreachable!(), + } }; - match action { - Token::AX => sepolicy.allowxperm(s, t, c, p), - Token::AY => sepolicy.auditallowxperm(s, t, c, p), - Token::DX => sepolicy.dontauditxperm(s, t, c, p), - _ => unreachable!(), + if result.is_err() { + Err(ParseError::AvtabXperms(action))? } } Token::PM | Token::EF => { - let t = parse_terms(tokens)?; - match action { - Token::PM => sepolicy.permissive(t), - Token::EF => sepolicy.enforce(t), - _ => unreachable!(), + let result: SimpleResult<()> = try { + let t = parse_terms(tokens)?; + match action { + Token::PM => sepolicy.permissive(t), + Token::EF => sepolicy.enforce(t), + _ => unreachable!(), + } + }; + if result.is_err() { + Err(ParseError::TypeState(action))? } } Token::TA => { - let t = parse_term(tokens)?; - let a = parse_term(tokens)?; - sepolicy.typeattribute(t, a) + let result: SimpleResult<()> = try { + let t = parse_term(tokens)?; + let a = parse_term(tokens)?; + sepolicy.typeattribute(t, a) + }; + if result.is_err() { + Err(ParseError::TypeAttr)? + } } Token::TY => { - let t = parse_id(tokens)?; - let a = if tokens.peek().is_none() { - vec![] - } else { - parse_term(tokens)? + let result: SimpleResult<()> = try { + let t = parse_id(tokens)?; + let a = if tokens.peek().is_none() { + vec![] + } else { + parse_term(tokens)? + }; + sepolicy.type_(t, a) }; - sepolicy.type_(t, a) + if result.is_err() { + Err(ParseError::NewType)? + } } Token::AT => { - let t = parse_id(tokens)?; - sepolicy.attribute(t) + let result: SimpleResult<()> = try { + let t = parse_id(tokens)?; + sepolicy.attribute(t) + }; + if result.is_err() { + Err(ParseError::NewAttr)? + } } - Token::TT | Token::TC | Token::TM => { - let s = parse_id(tokens)?; - let t = parse_id(tokens)?; - let c = parse_id(tokens)?; - let d = parse_id(tokens)?; - match action { - Token::TT => { - let o = if tokens.peek().is_none() { - "" - } else { - parse_id(tokens)? - }; - sepolicy.type_transition(s, t, c, d, o) + Token::TC | Token::TM => { + let result: SimpleResult<()> = try { + let s = parse_id(tokens)?; + let t = parse_id(tokens)?; + let c = parse_id(tokens)?; + let d = parse_id(tokens)?; + match action { + Token::TC => sepolicy.type_change(s, t, c, d), + Token::TM => sepolicy.type_member(s, t, c, d), + _ => unreachable!(), } - Token::TC => sepolicy.type_change(s, t, c, d), - Token::TM => sepolicy.type_member(s, t, c, d), - _ => unreachable!(), + }; + if result.is_err() { + Err(ParseError::AvtabType(action))? + } + } + Token::TT => { + let result: SimpleResult<()> = try { + let s = parse_id(tokens)?; + let t = parse_id(tokens)?; + let c = parse_id(tokens)?; + let d = parse_id(tokens)?; + let o = if tokens.peek().is_none() { + "" + } else { + parse_id(tokens)? + }; + sepolicy.type_transition(s, t, c, d, o); + }; + if result.is_err() { + Err(ParseError::TypeTrans)? } } Token::GF => { - let s = parse_id(tokens)?; - let t = parse_id(tokens)?; - let c = parse_id(tokens)?; - sepolicy.genfscon(s, t, c) + let result: SimpleResult<()> = try { + let s = parse_id(tokens)?; + let t = parse_id(tokens)?; + let c = parse_id(tokens)?; + sepolicy.genfscon(s, t, c) + }; + if result.is_err() { + Err(ParseError::GenfsCon)? + } } - _ => throw!(), + _ => Err(ParseError::General)?, } if tokens.peek().is_none() { Ok(()) } else { - throw!() + Err(ParseError::General) } } @@ -345,7 +411,156 @@ fn tokenize_statement(statement: &str) -> Vec { tokens } -pub fn parse_statement(sepolicy: Pin<&mut sepolicy>, statement: &str) -> LoggedResult<()> { +pub fn parse_statement(sepolicy: Pin<&mut sepolicy>, statement: &str) { let mut tokens = tokenize_statement(statement).into_iter().peekable(); - exec_statement(sepolicy, &mut tokens) + let result = exec_statement(sepolicy, &mut tokens); + if let Err(e) = result { + warn!("Syntax error in: \"{}\"", statement); + error!("Hint: {}", e); + } +} + +// Token to string +impl Display for Token<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Token::AL => f.write_str("allow"), + Token::DN => f.write_str("deny"), + Token::AA => f.write_str("auditallow"), + Token::DA => f.write_str("dontaudit"), + Token::AX => f.write_str("allowxperm"), + Token::AY => f.write_str("auditallowxperm"), + Token::DX => f.write_str("dontauditxperm"), + Token::PM => f.write_str("permissive"), + Token::EF => f.write_str("enforce"), + Token::TA => f.write_str("typeattribute"), + Token::TY => f.write_str("type"), + Token::AT => f.write_str("attribute"), + Token::TT => f.write_str("type_transition"), + Token::TC => f.write_str("type_change"), + Token::TM => f.write_str("type_member"), + Token::GF => f.write_str("genfscon"), + Token::IO => f.write_str("ioctl"), + Token::LB => f.write_char('{'), + Token::RB => f.write_char('}'), + Token::CM => f.write_char(','), + Token::ST => f.write_char('*'), + Token::TL => f.write_char('~'), + Token::HP => f.write_char('-'), + Token::HX(n) => f.write_fmt(format_args!("{:06X}", n)), + Token::ID(s) => f.write_str(s), + } + } +} + +impl Display for ParseError<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ParseError::General => format_statement_help(f), + ParseError::AvtabAv(action) => { + write!(f, "{action} *source_type *target_type *class *perm_set") + } + ParseError::AvtabXperms(action) => { + write!( + f, + "{action} *source_type *target_type *class operation xperm_set" + ) + } + ParseError::AvtabType(action) => { + write!(f, "{action} source_type target_type class default_type") + } + ParseError::TypeState(action) => { + write!(f, "{action} *type") + } + ParseError::TypeAttr => f.write_str("typeattribute ^type ^attribute"), + ParseError::TypeTrans => f.write_str( + "type_transition source_type target_type class default_type (object_name)", + ), + ParseError::NewType => f.write_str("type type_name ^(attribute)"), + ParseError::NewAttr => f.write_str("attribute attribute_name"), + ParseError::GenfsCon => f.write_str("genfscon fs_name partial_path fs_context"), + } + } +} + +fn format_statement_help(f: &mut dyn Write) -> std::fmt::Result { + write!( + f, + r#"** Policy statements: + +One policy statement should be treated as a single parameter; +this means each policy statement should be enclosed in quotes. +Multiple policy statements can be provided in a single command. + +Statements has a format of " [args...]". +Arguments labeled with (^) can accept one or more entries. +Multiple entries consist of a space separated list enclosed in braces ({{}}). +Arguments labeled with (*) are the same as (^), but additionally +support the match-all operator (*). + +Example: "allow {{ s1 s2 }} {{ t1 t2 }} class *" +Will be expanded to: + +allow s1 t1 class {{ all-permissions-of-class }} +allow s1 t2 class {{ all-permissions-of-class }} +allow s2 t1 class {{ all-permissions-of-class }} +allow s2 t2 class {{ all-permissions-of-class }} + +** Extended permissions: + +The only supported operation for extended permissions right now is 'ioctl'. +xperm_set is one or multiple hexadecimal numeric values ranging from 0x0000 to 0xFFFF. +Multiple values consist of a space separated list enclosed in braces ({{}}). +Use the complement operator (~) to specify all permissions except those explicitly listed. +Use the range operator (-) to specify all permissions within the low – high range. +Use the match all operator (*) to match all ioctl commands (0x0000-0xFFFF). +The special value 0 is used to clear all rules. + +Some examples: +allowxperm source target class ioctl 0x8910 +allowxperm source target class ioctl {{ 0x8910-0x8926 0x892A-0x8935 }} +allowxperm source target class ioctl ~{{ 0x8910 0x892A }} +allowxperm source target class ioctl * + +** Supported policy statements: + +{} +{} +{} +{} +{} +{} +{} +{} +{} +{} +{} +{} +{} +{} +{} +{} +"#, + ParseError::AvtabAv(Token::AL), + ParseError::AvtabAv(Token::DN), + ParseError::AvtabAv(Token::AA), + ParseError::AvtabAv(Token::DA), + ParseError::AvtabXperms(Token::AX), + ParseError::AvtabXperms(Token::AY), + ParseError::AvtabXperms(Token::DX), + ParseError::TypeState(Token::PM), + ParseError::TypeState(Token::EF), + ParseError::TypeAttr, + ParseError::NewType, + ParseError::NewAttr, + ParseError::TypeTrans, + ParseError::AvtabType(Token::TC), + ParseError::AvtabType(Token::TM), + ParseError::GenfsCon + ) +} + +pub fn print_statement_help() { + format_statement_help(&mut FmtAdaptor(&mut stderr())).ok(); + eprintln!(); }