Make argh directly parse into Utf8CString

This commit is contained in:
topjohnwu
2025-08-26 23:37:16 -07:00
committed by John Wu
parent ea72666df8
commit 3599dcedfb
7 changed files with 95 additions and 116 deletions

View File

@@ -7,7 +7,7 @@ use std::fmt::{Debug, Display, Formatter, Write};
use std::ops::Deref; use std::ops::Deref;
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::Utf8Error; use std::str::{FromStr, Utf8Error};
use std::{fmt, mem, slice, str}; use std::{fmt, mem, slice, str};
use thiserror::Error; use thiserror::Error;
@@ -200,7 +200,18 @@ impl From<String> for Utf8CString {
impl From<&str> for Utf8CString { impl From<&str> for Utf8CString {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
value.to_string().into() let mut s = String::with_capacity(value.len() + 1);
s.push_str(value);
s.nul_terminate();
Utf8CString(s)
}
}
impl FromStr for Utf8CString {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.into())
} }
} }

View File

@@ -1,4 +1,4 @@
use crate::{Utf8CStr, cstr, ffi}; use crate::{Utf8CStr, Utf8CString, cstr, ffi};
use argh::{EarlyExit, MissingRequirements}; use argh::{EarlyExit, MissingRequirements};
use libc::c_char; use libc::c_char;
use std::{ use std::{
@@ -105,9 +105,9 @@ impl<T> EarlyExitExt<T> for Result<T, EarlyExit> {
pub struct PositionalArgParser<'a>(pub slice::Iter<'a, &'a str>); pub struct PositionalArgParser<'a>(pub slice::Iter<'a, &'a str>);
impl PositionalArgParser<'_> { impl PositionalArgParser<'_> {
pub fn required(&mut self, field_name: &'static str) -> Result<String, EarlyExit> { pub fn required(&mut self, field_name: &'static str) -> Result<Utf8CString, EarlyExit> {
if let Some(next) = self.0.next() { if let Some(next) = self.0.next() {
Ok(next.to_string()) Ok((*next).into())
} else { } else {
let mut missing = MissingRequirements::default(); let mut missing = MissingRequirements::default();
missing.missing_positional_arg(field_name); missing.missing_positional_arg(field_name);
@@ -116,17 +116,17 @@ impl PositionalArgParser<'_> {
} }
} }
pub fn optional(&mut self) -> Option<String> { pub fn optional(&mut self) -> Option<Utf8CString> {
self.0.next().map(ToString::to_string) self.0.next().map(|s| (*s).into())
} }
pub fn last_required(&mut self, field_name: &'static str) -> Result<String, EarlyExit> { pub fn last_required(&mut self, field_name: &'static str) -> Result<Utf8CString, EarlyExit> {
let r = self.required(field_name)?; let r = self.required(field_name)?;
self.ensure_end()?; self.ensure_end()?;
Ok(r) Ok(r)
} }
pub fn last_optional(&mut self) -> Result<Option<String>, EarlyExit> { pub fn last_optional(&mut self) -> Result<Option<Utf8CString>, EarlyExit> {
let r = self.optional(); let r = self.optional();
if r.is_none() { if r.is_none() {
return Ok(r); return Ok(r);

View File

@@ -8,7 +8,7 @@ use crate::sign::{sha1_hash, sign_boot_image};
use argh::{CommandInfo, EarlyExit, FromArgs, SubCommand}; use argh::{CommandInfo, EarlyExit, FromArgs, SubCommand};
use base::{ use base::{
CmdArgs, EarlyExitExt, LoggedResult, MappedFile, PositionalArgParser, ResultExt, Utf8CStr, CmdArgs, EarlyExitExt, LoggedResult, MappedFile, PositionalArgParser, ResultExt, Utf8CStr,
WriteExt, cmdline_logging, cstr, libc, libc::umask, log_err, Utf8CString, WriteExt, cmdline_logging, cstr, libc, libc::umask, log_err,
}; };
use std::ffi::c_char; use std::ffi::c_char;
use std::io::{Seek, SeekFrom, Write}; use std::io::{Seek, SeekFrom, Write};
@@ -46,7 +46,7 @@ struct Unpack {
#[argh(switch, short = 'h')] #[argh(switch, short = 'h')]
dump_header: bool, dump_header: bool,
#[argh(positional)] #[argh(positional)]
img: String, img: Utf8CString,
} }
#[derive(FromArgs)] #[derive(FromArgs)]
@@ -55,33 +55,33 @@ struct Repack {
#[argh(switch, short = 'n')] #[argh(switch, short = 'n')]
no_compress: bool, no_compress: bool,
#[argh(positional)] #[argh(positional)]
img: String, img: Utf8CString,
#[argh(positional, default = r#""new-boot.img".to_string()"#)] #[argh(positional, default = r#"Utf8CString::from("new-boot.img")"#)]
out: String, out: Utf8CString,
} }
#[derive(FromArgs)] #[derive(FromArgs)]
#[argh(subcommand, name = "verify")] #[argh(subcommand, name = "verify")]
struct Verify { struct Verify {
#[argh(positional)] #[argh(positional)]
img: String, img: Utf8CString,
#[argh(positional)] #[argh(positional)]
cert: Option<String>, cert: Option<Utf8CString>,
} }
#[derive(FromArgs)] #[derive(FromArgs)]
#[argh(subcommand, name = "sign")] #[argh(subcommand, name = "sign")]
struct Sign { struct Sign {
#[argh(positional)] #[argh(positional)]
img: String, img: Utf8CString,
#[argh(positional)] #[argh(positional)]
args: Vec<String>, args: Vec<Utf8CString>,
} }
struct Extract { struct Extract {
payload: String, payload: Utf8CString,
partition: Option<String>, partition: Option<Utf8CString>,
outfile: Option<String>, outfile: Option<Utf8CString>,
} }
impl FromArgs for Extract { impl FromArgs for Extract {
@@ -106,18 +106,18 @@ impl SubCommand for Extract {
#[argh(subcommand, name = "hexpatch")] #[argh(subcommand, name = "hexpatch")]
struct HexPatch { struct HexPatch {
#[argh(positional)] #[argh(positional)]
file: String, file: Utf8CString,
#[argh(positional)] #[argh(positional)]
src: String, src: Utf8CString,
#[argh(positional)] #[argh(positional)]
dest: String, dest: Utf8CString,
} }
#[derive(FromArgs)] #[derive(FromArgs)]
#[argh(subcommand, name = "cpio")] #[argh(subcommand, name = "cpio")]
struct Cpio { struct Cpio {
#[argh(positional)] #[argh(positional)]
file: String, file: Utf8CString,
#[argh(positional)] #[argh(positional)]
cmds: Vec<String>, cmds: Vec<String>,
} }
@@ -126,7 +126,7 @@ struct Cpio {
#[argh(subcommand, name = "dtb")] #[argh(subcommand, name = "dtb")]
struct Dtb { struct Dtb {
#[argh(positional)] #[argh(positional)]
file: String, file: Utf8CString,
#[argh(subcommand)] #[argh(subcommand)]
action: DtbAction, action: DtbAction,
} }
@@ -137,14 +137,14 @@ struct Split {
#[argh(switch, short = 'n')] #[argh(switch, short = 'n')]
no_decompress: bool, no_decompress: bool,
#[argh(positional)] #[argh(positional)]
file: String, file: Utf8CString,
} }
#[derive(FromArgs)] #[derive(FromArgs)]
#[argh(subcommand, name = "sha1")] #[argh(subcommand, name = "sha1")]
struct Sha1 { struct Sha1 {
#[argh(positional)] #[argh(positional)]
file: String, file: Utf8CString,
} }
#[derive(FromArgs)] #[derive(FromArgs)]
@@ -153,8 +153,8 @@ struct Cleanup {}
struct Compress { struct Compress {
format: FileFormat, format: FileFormat,
file: String, file: Utf8CString,
out: Option<String>, out: Option<Utf8CString>,
} }
impl FromArgs for Compress { impl FromArgs for Compress {
@@ -185,8 +185,8 @@ impl SubCommand for Compress {
} }
struct Decompress { struct Decompress {
file: String, file: Utf8CString,
out: Option<String>, out: Option<Utf8CString>,
} }
impl FromArgs for Decompress { impl FromArgs for Decompress {
@@ -357,7 +357,7 @@ fn boot_main(cmds: CmdArgs) -> LoggedResult<i32> {
cmds[1] = &cmds[1][2..]; cmds[1] = &cmds[1][2..];
} }
let mut cli = if cmds[1].starts_with("compress=") { let cli = if cmds[1].starts_with("compress=") {
// Skip the main parser, directly parse the subcommand // Skip the main parser, directly parse the subcommand
Compress::from_args(&cmds[..2], &cmds[2..]).map(|compress| Cli { Compress::from_args(&cmds[..2], &cmds[2..]).map(|compress| Cli {
action: Action::Compress(compress), action: Action::Compress(compress),
@@ -375,40 +375,29 @@ fn boot_main(cmds: CmdArgs) -> LoggedResult<i32> {
Action::Unpack(Unpack { Action::Unpack(Unpack {
no_decompress, no_decompress,
dump_header, dump_header,
ref mut img, img,
}) => { }) => {
return Ok(unpack( return Ok(unpack(&img, no_decompress, dump_header));
Utf8CStr::from_string(img),
no_decompress,
dump_header,
));
} }
Action::Repack(Repack { Action::Repack(Repack {
no_compress, no_compress,
mut img, img,
mut out, out,
}) => { }) => {
repack( repack(&img, &out, no_compress);
Utf8CStr::from_string(&mut img),
Utf8CStr::from_string(&mut out),
no_compress,
);
} }
Action::Verify(Verify { mut img, mut cert }) => { Action::Verify(Verify { img, cert }) => {
if !verify_cmd( if !verify_cmd(&img, cert.as_deref()) {
Utf8CStr::from_string(&mut img),
cert.as_mut().map(Utf8CStr::from_string),
) {
return log_err!(); return log_err!();
} }
} }
Action::Sign(Sign { mut img, mut args }) => { Action::Sign(Sign { img, args }) => {
let mut iter = args.iter_mut(); let mut iter = args.iter();
sign_cmd( sign_cmd(
Utf8CStr::from_string(&mut img), &img,
iter.next().map(Utf8CStr::from_string), iter.next().map(AsRef::as_ref),
iter.next().map(Utf8CStr::from_string), iter.next().map(AsRef::as_ref),
iter.next().map(Utf8CStr::from_string), iter.next().map(AsRef::as_ref),
)?; )?;
} }
Action::Extract(Extract { Action::Extract(Extract {
@@ -416,42 +405,34 @@ fn boot_main(cmds: CmdArgs) -> LoggedResult<i32> {
partition, partition,
outfile, outfile,
}) => { }) => {
extract_boot_from_payload(&payload, partition.as_deref(), outfile.as_deref()) extract_boot_from_payload(
.log_with_msg(|w| w.write_str("Failed to extract from payload"))?; &payload,
partition.as_ref().map(AsRef::as_ref),
outfile.as_ref().map(AsRef::as_ref),
)
.log_with_msg(|w| w.write_str("Failed to extract from payload"))?;
} }
Action::HexPatch(HexPatch { Action::HexPatch(HexPatch { file, src, dest }) => {
mut file, if !hexpatch(&file, &src, &dest) {
mut src,
mut dest,
}) => {
if !hexpatch(
&mut file,
Utf8CStr::from_string(&mut src),
Utf8CStr::from_string(&mut dest),
) {
log_err!("Failed to patch")?; log_err!("Failed to patch")?;
} }
} }
Action::Cpio(Cpio { mut file, mut cmds }) => { Action::Cpio(Cpio { file, cmds }) => {
cpio_commands(Utf8CStr::from_string(&mut file), &mut cmds) cpio_commands(&file, &cmds).log_with_msg(|w| w.write_str("Failed to process cpio"))?;
.log_with_msg(|w| w.write_str("Failed to process cpio"))?;
} }
Action::Dtb(Dtb { mut file, action }) => { Action::Dtb(Dtb { file, action }) => {
return dtb_commands(Utf8CStr::from_string(&mut file), &action) return dtb_commands(&file, &action)
.map(|b| if b { 0 } else { 1 }) .map(|b| if b { 0 } else { 1 })
.log_with_msg(|w| w.write_str("Failed to process dtb")); .log_with_msg(|w| w.write_str("Failed to process dtb"));
} }
Action::Split(Split { Action::Split(Split {
no_decompress, no_decompress,
mut file, file,
}) => { }) => {
return Ok(split_image_dtb( return Ok(split_image_dtb(&file, no_decompress));
Utf8CStr::from_string(&mut file),
no_decompress,
));
} }
Action::Sha1(Sha1 { mut file }) => { Action::Sha1(Sha1 { file }) => {
let file = MappedFile::open(Utf8CStr::from_string(&mut file))?; let file = MappedFile::open(&file)?;
let mut sha1 = [0u8; 20]; let mut sha1 = [0u8; 20];
sha1_hash(file.as_ref(), &mut sha1); sha1_hash(file.as_ref(), &mut sha1);
for byte in &sha1 { for byte in &sha1 {
@@ -463,15 +444,11 @@ fn boot_main(cmds: CmdArgs) -> LoggedResult<i32> {
eprintln!("Cleaning up..."); eprintln!("Cleaning up...");
cleanup(); cleanup();
} }
Action::Decompress(Decompress { mut file, mut out }) => { Action::Decompress(Decompress { file, out }) => {
decompress_cmd(&mut file, out.as_mut())?; decompress_cmd(&file, out.as_deref())?;
} }
Action::Compress(Compress { Action::Compress(Compress { format, file, out }) => {
format, compress_cmd(format, &file, out.as_deref())?;
mut file,
mut out,
}) => {
compress_cmd(format, &mut file, out.as_mut())?;
} }
} }
Ok(0) Ok(0)

View File

@@ -284,13 +284,7 @@ pub fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
// Command-line entry points // Command-line entry points
pub(crate) fn decompress_cmd( pub(crate) fn decompress_cmd(infile: &Utf8CStr, outfile: Option<&Utf8CStr>) -> LoggedResult<()> {
infile: &mut String,
outfile: Option<&mut String>,
) -> LoggedResult<()> {
let infile = Utf8CStr::from_string(infile);
let outfile = outfile.map(Utf8CStr::from_string);
let in_std = infile == "-"; let in_std = infile == "-";
let mut rm_in = false; let mut rm_in = false;
@@ -353,12 +347,9 @@ pub(crate) fn decompress_cmd(
pub(crate) fn compress_cmd( pub(crate) fn compress_cmd(
method: FileFormat, method: FileFormat,
infile: &mut String, infile: &Utf8CStr,
outfile: Option<&mut String>, outfile: Option<&Utf8CStr>,
) -> LoggedResult<()> { ) -> LoggedResult<()> {
let infile = Utf8CStr::from_string(infile);
let outfile = outfile.map(Utf8CStr::from_string);
let in_std = infile == "-"; let in_std = infile == "-";
let mut rm_in = false; let mut rm_in = false;

View File

@@ -754,7 +754,7 @@ impl Display for CpioEntry {
} }
} }
pub(crate) fn cpio_commands(file: &Utf8CStr, cmds: &mut Vec<String>) -> LoggedResult<()> { pub(crate) fn cpio_commands(file: &Utf8CStr, cmds: &Vec<String>) -> LoggedResult<()> {
let mut cpio = if file.exists() { let mut cpio = if file.exists() {
Cpio::load_from_file(file)? Cpio::load_from_file(file)?
} else { } else {

View File

@@ -102,9 +102,9 @@ fn hex2byte(hex: &[u8]) -> Vec<u8> {
v v
} }
pub fn hexpatch(file: &mut String, from: &Utf8CStr, to: &Utf8CStr) -> bool { pub fn hexpatch(file: &Utf8CStr, from: &Utf8CStr, to: &Utf8CStr) -> bool {
let res: LoggedResult<bool> = try { let res: LoggedResult<bool> = try {
let mut map = MappedFile::open_rw(Utf8CStr::from_string(file))?; let mut map = MappedFile::open_rw(file)?;
let pattern = hex2byte(from.as_bytes()); let pattern = hex2byte(from.as_bytes());
let patch = hex2byte(to.as_bytes()); let patch = hex2byte(to.as_bytes());

View File

@@ -2,8 +2,8 @@ use crate::ffi::SePolicy;
use crate::statement::format_statement_help; use crate::statement::format_statement_help;
use argh::FromArgs; use argh::FromArgs;
use base::{ use base::{
CmdArgs, EarlyExitExt, FmtAdaptor, LoggedResult, Utf8CStr, cmdline_logging, cstr, libc::umask, CmdArgs, EarlyExitExt, FmtAdaptor, LoggedResult, Utf8CString, cmdline_logging, cstr,
log_err, libc::umask, log_err,
}; };
use std::ffi::c_char; use std::ffi::c_char;
use std::io::stderr; use std::io::stderr;
@@ -26,13 +26,13 @@ struct Cli {
print_rules: bool, print_rules: bool,
#[argh(option)] #[argh(option)]
load: Option<String>, load: Option<Utf8CString>,
#[argh(option)] #[argh(option)]
save: Option<String>, save: Option<Utf8CString>,
#[argh(option)] #[argh(option)]
apply: Vec<String>, apply: Vec<Utf8CString>,
#[argh(positional)] #[argh(positional)]
polices: Vec<String>, polices: Vec<String>,
@@ -85,10 +85,10 @@ pub unsafe extern "C" fn main(
print_usage(cmds.first().unwrap_or(&"magiskpolicy")); print_usage(cmds.first().unwrap_or(&"magiskpolicy"));
return 1; return 1;
} }
let mut cli = Cli::from_args(&[cmds[0]], &cmds[1..]).on_early_exit(|| print_usage(cmds[0])); let cli = Cli::from_args(&[cmds[0]], &cmds[1..]).on_early_exit(|| print_usage(cmds[0]));
let mut sepol = match (&mut cli.load, cli.load_split, cli.compile_split) { let mut sepol = match (cli.load, cli.load_split, cli.compile_split) {
(Some(file), false, false) => SePolicy::from_file(Utf8CStr::from_string(file)), (Some(file), false, false) => SePolicy::from_file(&file),
(None, true, false) => SePolicy::from_split(), (None, true, false) => SePolicy::from_split(),
(None, false, true) => SePolicy::compile_split(), (None, false, true) => SePolicy::compile_split(),
(None, false, false) => SePolicy::from_file(cstr!("/sys/fs/selinux/policy")), (None, false, false) => SePolicy::from_file(cstr!("/sys/fs/selinux/policy")),
@@ -115,8 +115,8 @@ pub unsafe extern "C" fn main(
sepol.magisk_rules(); sepol.magisk_rules();
} }
for file in &mut cli.apply { for file in cli.apply {
sepol.load_rule_file(Utf8CStr::from_string(file)); sepol.load_rule_file(&file);
} }
for statement in &cli.polices { for statement in &cli.polices {
@@ -127,8 +127,8 @@ pub unsafe extern "C" fn main(
log_err!("Cannot apply policy")?; log_err!("Cannot apply policy")?;
} }
if let Some(file) = &mut cli.save { if let Some(file) = cli.save {
if !sepol.to_file(Utf8CStr::from_string(file)) { if !sepol.to_file(&file) {
log_err!("Cannot dump policy to {}", file)?; log_err!("Cannot dump policy to {}", file)?;
} }
} }