From 89aee6ffa732a655f5bc7c4d8cc0a097d9f21141 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Tue, 12 Sep 2023 17:35:01 -0700 Subject: [PATCH] Add more to the Utf8CStr family Better C strings with path operations --- native/src/base/cstr.rs | 575 +++++++++++++++++++++++++++ native/src/base/cxx_extern.rs | 24 +- native/src/base/files.rs | 168 ++++---- native/src/base/lib.rs | 3 + native/src/base/logging.rs | 39 +- native/src/base/misc.rs | 271 +------------ native/src/base/xwrap.rs | 19 +- native/src/boot/cpio.rs | 2 +- native/src/boot/payload.rs | 1 - native/src/core/daemon.rs | 17 +- native/src/core/logging.rs | 32 +- native/src/core/resetprop/persist.rs | 59 +-- native/src/init/logging.rs | 10 +- 13 files changed, 783 insertions(+), 437 deletions(-) create mode 100644 native/src/base/cstr.rs diff --git a/native/src/base/cstr.rs b/native/src/base/cstr.rs new file mode 100644 index 000000000..acb415392 --- /dev/null +++ b/native/src/base/cstr.rs @@ -0,0 +1,575 @@ +use std::cmp::min; +use std::ffi::{CStr, FromBytesWithNulError, OsStr}; +use std::fmt::{Arguments, Debug, Display, Formatter, Write}; +use std::ops::{Deref, DerefMut}; +use std::path::Path; +use std::str::{Utf8Chunks, Utf8Error}; +use std::{fmt, mem, slice, str}; + +use crate::slice_from_ptr_mut; +use libc::c_char; +use thiserror::Error; + +pub fn copy_cstr + ?Sized>(dest: &mut [u8], src: &T) -> usize { + let src = src.as_ref().to_bytes_with_nul(); + let len = min(src.len(), dest.len()); + dest[..len].copy_from_slice(&src[..len]); + len - 1 +} + +fn utf8_cstr_append(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize { + let mut used = buf.len(); + if used >= buf.capacity() - 1 { + // Truncate + return 0; + } + let dest = unsafe { &mut buf.mut_buf()[used..] }; + let len = min(s.len(), dest.len() - 1); + dest[..len].copy_from_slice(&s[..len]); + dest[len] = b'\0'; + used += len; + unsafe { buf.set_len(used) }; + len +} + +fn utf8_cstr_append_lossy(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize { + let chunks = Utf8Chunks::new(s); + let mut len = 0_usize; + for chunk in chunks { + len += utf8_cstr_append(buf, chunk.valid().as_bytes()); + if !chunk.invalid().is_empty() { + len += utf8_cstr_append( + buf, + char::REPLACEMENT_CHARACTER + .encode_utf8(&mut [0; 4]) + .as_bytes(), + ); + } + } + len +} + +pub trait Utf8CStrBuf: + Write + AsRef + AsMut + Deref + DerefMut +{ + fn buf(&self) -> &[u8]; + fn len(&self) -> usize; + + // Modifying the underlying buffer or length is unsafe because it can either: + // 1. Break null termination + // 2. Break UTF-8 validation + // 3. Introduce inner null byte in the string + unsafe fn mut_buf(&mut self) -> &mut [u8]; + unsafe fn set_len(&mut self, len: usize); + + fn append(&mut self, s: &str) -> usize; + fn append_lossy(&mut self, s: &[u8]) -> usize; + unsafe fn append_unchecked(&mut self, s: &[u8]) -> usize; + + #[inline(always)] + fn is_empty(&self) -> bool { + self.len() == 0 + } + + #[inline(always)] + fn capacity(&self) -> usize { + self.buf().len() + } + + fn clear(&mut self) { + unsafe { + self.mut_buf()[0] = b'\0'; + self.set_len(0); + } + } +} + +trait Utf8CStrBufImpl: Utf8CStrBuf { + #[inline(always)] + fn as_utf8_cstr(&self) -> &Utf8CStr { + // SAFETY: the internal buffer is always UTF-8 checked + // SAFETY: self.used is guaranteed to always <= SIZE - 1 + unsafe { Utf8CStr::from_bytes_unchecked(self.buf().get_unchecked(..(self.len() + 1))) } + } + + #[inline(always)] + fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr { + // SAFETY: the internal buffer is always UTF-8 checked + // SAFETY: self.used is guaranteed to always <= SIZE - 1 + unsafe { + let len = self.len() + 1; + Utf8CStr::from_bytes_unchecked_mut(self.mut_buf().get_unchecked_mut(..len)) + } + } +} + +// UTF-8 validated + null terminated reference to buffer +pub struct Utf8CStrSlice<'a> { + used: usize, + buf: &'a mut [u8], +} + +impl<'a> Utf8CStrSlice<'a> { + pub unsafe fn from_ptr(buf: *mut u8, len: usize) -> Utf8CStrSlice<'a> { + Self::from(slice_from_ptr_mut(buf, len)) + } +} + +impl<'a> From<&'a mut [u8]> for Utf8CStrSlice<'a> { + fn from(buf: &'a mut [u8]) -> Utf8CStrSlice<'a> { + buf[0] = b'\0'; + Utf8CStrSlice { used: 0, buf } + } +} + +impl Utf8CStrBuf for Utf8CStrSlice<'_> { + #[inline(always)] + fn buf(&self) -> &[u8] { + self.buf + } + + #[inline(always)] + fn len(&self) -> usize { + self.used + } + + #[inline(always)] + unsafe fn mut_buf(&mut self) -> &mut [u8] { + self.buf + } + + #[inline(always)] + unsafe fn set_len(&mut self, len: usize) { + self.used = len; + } + + #[inline(always)] + fn append(&mut self, s: &str) -> usize { + utf8_cstr_append(self, s.as_bytes()) + } + + #[inline(always)] + fn append_lossy(&mut self, s: &[u8]) -> usize { + utf8_cstr_append_lossy(self, s) + } + + #[inline(always)] + unsafe fn append_unchecked(&mut self, s: &[u8]) -> usize { + utf8_cstr_append(self, s) + } +} + +// UTF-8 validated + null terminated buffer on the stack +pub struct Utf8CStrArr { + used: usize, + buf: [u8; N], +} + +impl Utf8CStrArr { + pub fn new() -> Self { + Utf8CStrArr { + used: 0, + buf: [0; N], + } + } +} + +impl Utf8CStrBuf for Utf8CStrArr { + #[inline(always)] + fn buf(&self) -> &[u8] { + &self.buf + } + + #[inline(always)] + fn len(&self) -> usize { + self.used + } + + #[inline(always)] + unsafe fn mut_buf(&mut self) -> &mut [u8] { + &mut self.buf + } + + #[inline(always)] + unsafe fn set_len(&mut self, len: usize) { + self.used = len; + } + + #[inline(always)] + fn append(&mut self, s: &str) -> usize { + utf8_cstr_append(self, s.as_bytes()) + } + + #[inline(always)] + fn append_lossy(&mut self, s: &[u8]) -> usize { + utf8_cstr_append_lossy(self, s) + } + + #[inline(always)] + unsafe fn append_unchecked(&mut self, s: &[u8]) -> usize { + utf8_cstr_append(self, s) + } + + #[inline(always)] + fn capacity(&self) -> usize { + N + } +} + +impl Default for Utf8CStrArr<4096> { + fn default() -> Self { + Utf8CStrArr::<4096>::new() + } +} + +#[derive(Debug, Error)] +pub enum StrErr { + #[error(transparent)] + Utf8Error(#[from] Utf8Error), + #[error(transparent)] + CStrError(#[from] FromBytesWithNulError), + #[error("argument is null")] + NullPointerError, +} + +// Provide a way to have a heap allocated, UTF-8 validated, and null terminated string +pub trait StringExt { + fn nul_terminate(&mut self) -> &mut [u8]; +} + +impl StringExt for String { + fn nul_terminate(&mut self) -> &mut [u8] { + self.reserve(1); + // SAFETY: the string is reserved to have enough capacity to fit in the null byte + // SAFETY: the null byte is explicitly added outside of the string's length + unsafe { + let buf = slice::from_raw_parts_mut(self.as_mut_ptr(), self.len() + 1); + *buf.get_unchecked_mut(self.len()) = b'\0'; + buf + } + } +} + +// UTF-8 validated + null terminated string slice +pub struct Utf8CStr([u8]); + +impl Utf8CStr { + pub fn from_cstr(cstr: &CStr) -> Result<&Utf8CStr, StrErr> { + // Validate the buffer during construction + str::from_utf8(cstr.to_bytes())?; + Ok(unsafe { Self::from_bytes_unchecked(cstr.to_bytes_with_nul()) }) + } + + pub fn from_bytes(buf: &[u8]) -> Result<&Utf8CStr, StrErr> { + Self::from_cstr(CStr::from_bytes_with_nul(buf)?) + } + + pub fn from_bytes_mut(buf: &mut [u8]) -> Result<&mut Utf8CStr, StrErr> { + CStr::from_bytes_with_nul(buf)?; + str::from_utf8(buf)?; + // Both condition checked + unsafe { Ok(mem::transmute(buf)) } + } + + pub fn from_string(s: &mut String) -> &mut Utf8CStr { + let buf = s.nul_terminate(); + // SAFETY: the null byte is explicitly added to the buffer + unsafe { mem::transmute(buf) } + } + + #[inline(always)] + pub unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr { + mem::transmute(buf) + } + + #[inline(always)] + pub unsafe fn from_bytes_unchecked_mut(buf: &mut [u8]) -> &mut Utf8CStr { + mem::transmute(buf) + } + + pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> { + if ptr.is_null() { + return Err(StrErr::NullPointerError); + } + Self::from_cstr(unsafe { CStr::from_ptr(ptr) }) + } + + #[inline(always)] + pub fn as_bytes_with_nul(&self) -> &[u8] { + &self.0 + } + + #[inline(always)] + pub fn as_ptr(&self) -> *const c_char { + self.0.as_ptr().cast() + } + + #[inline(always)] + pub fn as_mut_ptr(&mut self) -> *mut c_char { + self.0.as_mut_ptr().cast() + } + + #[inline(always)] + pub fn as_cstr(&self) -> &CStr { + // SAFETY: Already validated as null terminated during construction + unsafe { CStr::from_bytes_with_nul_unchecked(&self.0) } + } + + #[inline(always)] + pub fn as_str(&self) -> &str { + // SAFETY: Already UTF-8 validated during construction + // SAFETY: The length of the slice is at least 1 due to null termination check + unsafe { str::from_utf8_unchecked(self.0.get_unchecked(..self.0.len() - 1)) } + } + + #[inline(always)] + pub fn as_str_mut(&mut self) -> &mut str { + // SAFETY: Already UTF-8 validated during construction + // SAFETY: The length of the slice is at least 1 due to null termination check + unsafe { str::from_utf8_unchecked_mut(self.0.get_unchecked_mut(..self.0.len() - 1)) } + } +} + +impl Deref for Utf8CStr { + type Target = str; + + #[inline(always)] + fn deref(&self) -> &str { + self.as_str() + } +} + +impl DerefMut for Utf8CStr { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_str_mut() + } +} + +// File system path extensions types + +pub struct FsPath(Utf8CStr); + +impl FsPath { + #[inline(always)] + pub fn from<'a, T: AsRef>(value: T) -> &'a FsPath { + unsafe { mem::transmute(value.as_ref()) } + } + + #[inline(always)] + pub fn from_mut<'a, T: AsMut>(mut value: T) -> &'a mut FsPath { + unsafe { mem::transmute(value.as_mut()) } + } +} + +impl Deref for FsPath { + type Target = Utf8CStr; + + #[inline(always)] + fn deref(&self) -> &Utf8CStr { + &self.0 + } +} + +impl DerefMut for FsPath { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Utf8CStr { + &mut self.0 + } +} + +pub struct FsPathBuf<'a>(&'a mut dyn Utf8CStrBuf); + +impl<'a> FsPathBuf<'a> { + pub fn new(value: &'a mut dyn Utf8CStrBuf) -> Self { + FsPathBuf(value) + } + + pub fn join>(self, path: T) -> Self { + fn inner(buf: &mut dyn Utf8CStrBuf, path: &str) { + if path.starts_with('/') { + buf.clear(); + } else { + buf.append("/"); + } + buf.append(path); + } + inner(self.0, path.as_ref()); + self + } + + pub fn join_fmt(self, name: T) -> Self { + fn inner(buf: &mut dyn Utf8CStrBuf, path: Arguments) { + buf.write_fmt(path).ok(); + } + inner(self.0, format_args!("/{}", name)); + self + } +} + +impl<'a> Deref for FsPathBuf<'a> { + type Target = FsPath; + + fn deref(&self) -> &FsPath { + FsPath::from(&self.0) + } +} + +impl<'a> DerefMut for FsPathBuf<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + FsPath::from_mut(&mut self.0) + } +} + +// Boilerplate trait implementations + +macro_rules! impl_str { + ($( ($t:ty, $($g:tt)*) )*) => {$( + impl<$($g)*> AsRef for $t { + #[inline(always)] + fn as_ref(&self) -> &Utf8CStr { + self + } + } + impl<$($g)*> AsRef for $t { + #[inline(always)] + fn as_ref(&self) -> &str { + self.as_str() + } + } + impl<$($g)*> AsRef for $t { + #[inline(always)] + fn as_ref(&self) -> &CStr { + self.as_cstr() + } + } + impl<$($g)*> AsRef for $t { + #[inline(always)] + fn as_ref(&self) -> &OsStr { + OsStr::new(self.as_str()) + } + } + impl<$($g)*> AsRef for $t { + #[inline(always)] + fn as_ref(&self) -> &Path { + Path::new(self.as_str()) + } + } + impl<$($g)*> Display for $t { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self.as_str(), f) + } + } + impl<$($g)*> Debug for $t { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(self.as_str(), f) + } + } + impl<$($g)*> PartialEq for $t { + #[inline(always)] + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } + } + impl<$($g)*> PartialEq<$t> for str { + #[inline(always)] + fn eq(&self, other: &$t) -> bool { + self == other.as_str() + } + } + impl<$($g)*> PartialEq for $t { + #[inline(always)] + fn eq(&self, other: &CStr) -> bool { + self.as_cstr() == other + } + } + impl<$($g)*> PartialEq<$t> for CStr { + #[inline(always)] + fn eq(&self, other: &$t) -> bool { + self == other.as_cstr() + } + } + impl, $($g)*> PartialEq for $t { + #[inline(always)] + fn eq(&self, other: &T) -> bool { + self.as_bytes_with_nul() == other.as_ref().as_bytes_with_nul() + } + } + )*} +} + +impl_str!( + (Utf8CStr,) + (FsPath,) + (FsPathBuf<'_>,) +); + +macro_rules! impl_str_buf { + ($( ($t:ty, $($g:tt)*) )*) => {$( + impl_str!(($t, $($g)*)); + impl<$($g)*> $t { + #[inline(always)] + pub fn append>(&mut self, s: T) -> usize { + utf8_cstr_append(self, s.as_ref().as_bytes()) + } + } + impl<$($g)*> Utf8CStrBufImpl for $t {} + impl<$($g)*> Write for $t { + #[inline(always)] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.append(s); + Ok(()) + } + } + impl<$($g)*> Deref for $t { + type Target = Utf8CStr; + + #[inline(always)] + fn deref(&self) -> &Utf8CStr { + self.as_utf8_cstr() + } + } + impl<$($g)*> DerefMut for $t { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Utf8CStr { + self.as_utf8_cstr_mut() + } + } + impl<$($g)*> AsMut for $t { + #[inline(always)] + fn as_mut(&mut self) -> &mut Utf8CStr { + self + } + } + )*} +} + +impl_str_buf!( + (Utf8CStrSlice<'_>,) + (Utf8CStrArr, const N: usize) +); + +// The cstr! macro is copied from https://github.com/bytecodealliance/rustix/blob/main/src/cstr.rs + +#[macro_export] +macro_rules! cstr { + ($($str:tt)*) => {{ + assert!( + !($($str)*).bytes().any(|b| b == b'\0'), + "cstr argument contains embedded NUL bytes", + ); + #[allow(unused_unsafe)] + unsafe { + $crate::Utf8CStr::from_bytes_unchecked(concat!($($str)*, "\0").as_bytes()) + } + }}; +} + +#[macro_export] +macro_rules! raw_cstr { + ($($str:tt)*) => {{ + cstr!($($str)*).as_ptr() + }}; +} diff --git a/native/src/base/cxx_extern.rs b/native/src/base/cxx_extern.rs index 86f56a83e..188026c37 100644 --- a/native/src/base/cxx_extern.rs +++ b/native/src/base/cxx_extern.rs @@ -1,6 +1,5 @@ // Functions in this file are only for exporting to C++, DO NOT USE IN RUST -use std::fmt::Write; use std::io; use std::os::fd::{BorrowedFd, OwnedFd, RawFd}; @@ -8,21 +7,24 @@ use cxx::private::c_char; use libc::mode_t; pub(crate) use crate::xwrap::*; -use crate::{ - fd_path, map_fd, map_file, mkdirs, realpath, rm_rf, slice_from_ptr_mut, Directory, ResultExt, - Utf8CStr, -}; +use crate::{fd_path, map_fd, map_file, Directory, FsPath, ResultExt, Utf8CStr, Utf8CStrSlice}; pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize { - fd_path(fd, buf) + let mut buf = Utf8CStrSlice::from(buf); + fd_path(fd, &mut buf) .log_cxx_with_msg(|w| w.write_str("fd_path failed")) - .map_or(-1_isize, |v| v as isize) + .map_or(-1_isize, |_| buf.len() as isize) } #[no_mangle] unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { match Utf8CStr::from_ptr(path) { - Ok(p) => realpath(p, slice_from_ptr_mut(buf, bufsz)).map_or(-1, |v| v as isize), + Ok(p) => { + let mut buf = Utf8CStrSlice::from_ptr(buf, bufsz); + FsPath::from(p) + .realpath(&mut buf) + .map_or(-1, |_| buf.len() as isize) + } Err(_) => -1, } } @@ -30,7 +32,7 @@ unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: us #[export_name = "mkdirs"] unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 { match Utf8CStr::from_ptr(path) { - Ok(p) => mkdirs(p, mode).map_or(-1, |_| 0), + Ok(p) => FsPath::from(p).mkdirs(mode).map_or(-1, |_| 0), Err(_) => -1, } } @@ -38,7 +40,7 @@ unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 { #[export_name = "rm_rf"] unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool { match Utf8CStr::from_ptr(path) { - Ok(p) => rm_rf(p).map_or(false, |_| true), + Ok(p) => FsPath::from(p).remove_all().is_ok(), Err(_) => false, } } @@ -48,7 +50,7 @@ unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool { fn inner(fd: OwnedFd) -> io::Result<()> { Directory::try_from(fd)?.remove_all() } - inner(fd).map_or(false, |_| true) + inner(fd).is_ok() } pub(crate) fn map_file_for_cxx(path: &[u8], rw: bool) -> &'static mut [u8] { diff --git a/native/src/base/files.rs b/native/src/base/files.rs index 273546828..cd61c1bdb 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -10,9 +10,14 @@ use std::os::unix::fs::FileTypeExt; use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd}; use std::{io, mem, ptr, slice}; -use libc::{c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH, O_RDONLY, O_RDWR}; +use libc::{ + c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_PATH, O_RDONLY, O_RDWR, +}; -use crate::{bfmt_cstr, copy_cstr, cstr, errno, error, FlatData, LibcReturn, Utf8CStr}; +use crate::{ + copy_cstr, cstr, errno, error, FlatData, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrArr, + Utf8CStrBuf, +}; pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result { unsafe { @@ -39,64 +44,10 @@ pub(crate) unsafe fn readlink_unsafe(path: *const c_char, buf: *mut u8, bufsz: u r } -pub fn readlink(path: &Utf8CStr, data: &mut [u8]) -> io::Result { - let r = - unsafe { readlink_unsafe(path.as_ptr(), data.as_mut_ptr(), data.len()) }.check_os_err()?; - Ok(r as usize) -} - -pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> io::Result { - let mut fd_buf = [0_u8; 40]; - let fd_path = bfmt_cstr!(&mut fd_buf, "/proc/self/fd/{}", fd); - readlink(fd_path, buf) -} - -// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp -pub fn realpath(path: &Utf8CStr, buf: &mut [u8]) -> io::Result { - let fd = open_fd!(path, O_PATH | O_CLOEXEC)?; - let mut st1: libc::stat; - let mut st2: libc::stat; - let mut skip_check = false; - unsafe { - st1 = mem::zeroed(); - if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 { - // This shall only fail on Linux < 3.6 - skip_check = true; - } - } - let len = fd_path(fd.as_raw_fd(), buf)?; - unsafe { - st2 = mem::zeroed(); - if libc::stat(buf.as_ptr().cast(), &mut st2) < 0 - || (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino)) - { - *errno() = ENOENT; - return Err(io::Error::last_os_error()); - } - } - Ok(len) -} - -pub fn mkdirs(path: &Utf8CStr, mode: mode_t) -> io::Result<()> { - let mut buf = [0_u8; 4096]; - let len = copy_cstr(&mut buf, path); - let buf = &mut buf[..len]; - let mut off = 1; - unsafe { - while let Some(p) = buf[off..].iter().position(|c| *c == b'/') { - buf[off + p] = b'\0'; - if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST { - return Err(io::Error::last_os_error()); - } - buf[off + p] = b'/'; - off += p + 1; - } - if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST { - return Err(io::Error::last_os_error()); - } - } - *errno() = 0; - Ok(()) +pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { + let mut arr = Utf8CStrArr::<40>::new(); + let path = FsPathBuf::new(&mut arr).join("/proc/self/fd").join_fmt(fd); + path.read_link(buf) } pub trait ReadExt { @@ -206,12 +157,11 @@ impl DirEntry<'_> { } } - pub fn path(&self, buf: &mut [u8]) -> io::Result { - let mut len = self.dir.path(buf)?; - buf[len] = b'/'; - len += 1; - len += copy_cstr(&mut buf[len..], self.d_name()); - Ok(len) + pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { + self.dir.path(buf)?; + buf.append("/"); + buf.append_lossy(self.d_name().to_bytes()); + Ok(()) } pub fn is_dir(&self) -> bool { @@ -320,7 +270,7 @@ impl Directory { unsafe { libc::rewinddir(self.dirp) } } - pub fn path(&self, buf: &mut [u8]) -> io::Result { + pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { fd_path(self.as_raw_fd(), buf) } @@ -448,17 +398,89 @@ impl Drop for Directory { } } -pub fn rm_rf(path: &Utf8CStr) -> io::Result<()> { - unsafe { - let f = File::from(open_fd!(path, O_RDONLY | O_CLOEXEC)?); +impl FsPath { + pub fn open(&self, flags: i32) -> io::Result { + Ok(File::from(open_fd!(self, flags)?)) + } + + pub fn create(&self, flags: i32, mode: mode_t) -> io::Result { + Ok(File::from(open_fd!(self, flags, mode)?)) + } + + pub fn exists(&self) -> bool { + unsafe { libc::access(self.as_ptr(), F_OK) == 0 } + } + + pub fn rename_to>(&self, name: T) -> io::Result<()> { + unsafe { libc::rename(self.as_ptr(), name.as_ref().as_ptr()).as_os_err() } + } + + pub fn remove(&self) -> io::Result<()> { + unsafe { libc::remove(self.as_ptr()).as_os_err() } + } + + pub fn remove_all(&self) -> io::Result<()> { + let f = self.open(O_RDONLY | O_CLOEXEC)?; let st = f.metadata()?; if st.is_dir() { let mut dir = Directory::try_from(OwnedFd::from(f))?; dir.remove_all()?; } - libc::remove(path.as_ptr()).check_os_err()?; + self.remove() + } + + pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { + buf.clear(); + unsafe { readlink_unsafe(self.as_ptr(), buf.as_mut_ptr().cast(), buf.capacity()) } + .as_os_err() + } + + pub fn mkdirs(&self, mode: mode_t) -> io::Result<()> { + let mut buf = [0_u8; 4096]; + let len = copy_cstr(&mut buf, self); + let buf = &mut buf[..len]; + let mut off = 1; + unsafe { + while let Some(p) = buf[off..].iter().position(|c| *c == b'/') { + buf[off + p] = b'\0'; + if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST { + return Err(io::Error::last_os_error()); + } + buf[off + p] = b'/'; + off += p + 1; + } + if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST { + return Err(io::Error::last_os_error()); + } + } + *errno() = 0; + Ok(()) + } + + // Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp + pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { + let fd = open_fd!(self, O_PATH | O_CLOEXEC)?; + let mut st1: libc::stat; + let mut st2: libc::stat; + let mut skip_check = false; + unsafe { + st1 = mem::zeroed(); + if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 { + // This will only fail on Linux < 3.6 + skip_check = true; + } + } + fd_path(fd.as_raw_fd(), buf)?; + unsafe { + st2 = mem::zeroed(); + libc::stat(buf.as_ptr(), &mut st2).as_os_err()?; + if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) { + *errno() = ENOENT; + return Err(io::Error::last_os_error()); + } + } + Ok(()) } - Ok(()) } pub struct MappedFile(&'static mut [u8]); @@ -516,7 +538,7 @@ pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8 let st = f.metadata()?; let sz = if st.file_type().is_block_device() { let mut sz = 0_u64; - unsafe { ioctl(f.as_raw_fd(), BLKGETSIZE64, &mut sz) }.check_os_err()?; + unsafe { ioctl(f.as_raw_fd(), BLKGETSIZE64, &mut sz) }.as_os_err()?; sz } else { st.st_size() diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index de93a5d40..c4f952108 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -1,14 +1,17 @@ #![allow(clippy::missing_safety_doc)] #![feature(format_args_nl)] #![feature(io_error_more)] +#![feature(utf8_chunks)] pub use libc; +pub use cstr::*; use cxx_extern::*; pub use files::*; pub use logging::*; pub use misc::*; +mod cstr; mod cxx_extern; mod files; mod logging; diff --git a/native/src/base/logging.rs b/native/src/base/logging.rs index c825ba269..a87cb5d48 100644 --- a/native/src/base/logging.rs +++ b/native/src/base/logging.rs @@ -1,11 +1,11 @@ use std::fmt; -use std::fmt::{Arguments, Display, Write as fWrite}; +use std::fmt::{Arguments, Display}; use std::io::{stderr, stdout, Write}; use std::panic::Location; use std::process::exit; use crate::ffi::LogLevel; -use crate::BufFormatter; +use crate::Utf8CStrArr; // Error handling and logging throughout the Rust codebase in Magisk: // @@ -37,9 +37,12 @@ pub static mut LOGGER: Logger = Logger { flags: 0, }; +type LogWriter = fn(level: LogLevel, msg: &[u8]); +type Formatter<'a> = &'a mut dyn fmt::Write; + #[derive(Copy, Clone)] pub struct Logger { - pub write: fn(level: LogLevel, msg: &[u8]), + pub write: LogWriter, pub flags: u32, } @@ -76,7 +79,7 @@ pub fn set_log_level_state(level: LogLevel, enabled: bool) { } } -fn do_log(level: LogLevel, f: F) { +fn log_with_writer(level: LogLevel, f: F) { let logger = unsafe { LOGGER }; if (logger.flags & level.as_disable_flag()) != 0 { return; @@ -88,15 +91,14 @@ fn do_log(level: LogLevel, f: F) { } pub fn log_from_cxx(level: LogLevel, msg: &[u8]) { - do_log(level, |write| write(level, msg)); + log_with_writer(level, |write| write(level, msg)); } -pub fn log_with_formatter fmt::Result>(level: LogLevel, f: F) { - do_log(level, |write| { - let mut buf = [0_u8; 4096]; - let mut w = BufFormatter::new(&mut buf); - let len = if f(&mut w).is_ok() { w.used } else { 0 }; - write(level, &buf[..len]); +pub fn log_with_formatter fmt::Result>(level: LogLevel, f: F) { + log_with_writer(level, |write| { + let mut buf = Utf8CStrArr::default(); + f(&mut buf).ok(); + write(level, buf.as_bytes_with_nul()); }); } @@ -214,13 +216,13 @@ where } #[cfg(not(debug_assertions))] - fn log_with_msg fmt::Result>(self, f: F) -> LoggedResult { + fn log_with_msg fmt::Result>(self, f: F) -> LoggedResult { self.log_with_msg_impl(LogLevel::Error, None, f) } #[track_caller] #[cfg(debug_assertions)] - fn log_with_msg fmt::Result>(self, f: F) -> LoggedResult { + fn log_with_msg fmt::Result>(self, f: F) -> LoggedResult { self.log_with_msg_impl(LogLevel::Error, Some(Location::caller()), f) } @@ -228,15 +230,12 @@ where self.log_impl(LogLevel::ErrorCxx, None) } - fn log_cxx_with_msg fmt::Result>( - self, - f: F, - ) -> LoggedResult { + fn log_cxx_with_msg fmt::Result>(self, f: F) -> LoggedResult { self.log_with_msg_impl(LogLevel::ErrorCxx, None, f) } fn log_impl(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedResult; - fn log_with_msg_impl fmt::Result>( + fn log_with_msg_impl fmt::Result>( self, level: LogLevel, caller: Option<&'static Location>, @@ -249,7 +248,7 @@ impl ResultExt for LoggedResult { self } - fn log_with_msg_impl fmt::Result>( + fn log_with_msg_impl fmt::Result>( self, level: LogLevel, caller: Option<&'static Location>, @@ -289,7 +288,7 @@ impl ResultExt for Result { } } - fn log_with_msg_impl fmt::Result>( + fn log_with_msg_impl fmt::Result>( self, level: LogLevel, caller: Option<&'static Location>, diff --git a/native/src/base/misc.rs b/native/src/base/misc.rs index 37450f0c3..1a39dd6ef 100644 --- a/native/src/base/misc.rs +++ b/native/src/base/misc.rs @@ -1,271 +1,10 @@ -use std::cmp::min; -use std::ffi::{CStr, FromBytesWithNulError, OsStr}; -use std::fmt::{Arguments, Debug, Display, Formatter}; -use std::ops::Deref; -use std::path::Path; use std::process::exit; -use std::str::Utf8Error; -use std::{fmt, io, mem, slice, str}; +use std::{io, mem, slice, str}; use argh::EarlyExit; use libc::c_char; -use thiserror::Error; -use crate::ffi; - -pub fn copy_str>(dest: &mut [u8], src: T) -> usize { - let src = src.as_ref(); - let len = min(src.len(), dest.len() - 1); - dest[..len].copy_from_slice(&src[..len]); - dest[len] = b'\0'; - len -} - -pub fn copy_cstr + ?Sized>(dest: &mut [u8], src: &T) -> usize { - let src = src.as_ref().to_bytes_with_nul(); - let len = min(src.len(), dest.len()); - dest[..len].copy_from_slice(&src[..len]); - len - 1 -} - -pub struct BufFormatter<'a> { - buf: &'a mut [u8], - pub used: usize, -} - -impl<'a> BufFormatter<'a> { - pub fn new(buf: &'a mut [u8]) -> Self { - BufFormatter { buf, used: 0 } - } -} - -impl<'a> fmt::Write for BufFormatter<'a> { - // The buffer should always be null terminated - fn write_str(&mut self, s: &str) -> fmt::Result { - if self.used >= self.buf.len() - 1 { - // Silent truncate - return Ok(()); - } - self.used += copy_str(&mut self.buf[self.used..], s); - // Silent truncate - Ok(()) - } -} - -pub fn fmt_to_buf(buf: &mut [u8], args: Arguments) -> usize { - let mut w = BufFormatter::new(buf); - if let Ok(()) = fmt::write(&mut w, args) { - w.used - } else { - 0 - } -} - -#[macro_export] -macro_rules! bfmt { - ($buf:expr, $($args:tt)*) => { - $crate::fmt_to_buf($buf, format_args!($($args)*)); - }; -} - -#[macro_export] -macro_rules! bfmt_cstr { - ($buf:expr, $($args:tt)*) => {{ - let len = $crate::fmt_to_buf($buf, format_args!($($args)*)); - #[allow(unused_unsafe, clippy::unnecessary_mut_passed)] - unsafe { - $crate::Utf8CStr::from_bytes_unchecked($buf.get_unchecked(..(len + 1))) - } - }}; -} - -// The cstr! macro is copied from https://github.com/bytecodealliance/rustix/blob/main/src/cstr.rs - -#[macro_export] -macro_rules! cstr { - ($($str:tt)*) => {{ - assert!( - !($($str)*).bytes().any(|b| b == b'\0'), - "cstr argument contains embedded NUL bytes", - ); - #[allow(unused_unsafe)] - unsafe { - $crate::Utf8CStr::from_bytes_unchecked(concat!($($str)*, "\0").as_bytes()) - } - }}; -} - -#[macro_export] -macro_rules! raw_cstr { - ($($str:tt)*) => {{ - cstr!($($str)*).as_ptr() - }}; -} - -#[derive(Debug, Error)] -pub enum StrErr { - #[error(transparent)] - Utf8Error(#[from] Utf8Error), - #[error(transparent)] - CStrError(#[from] FromBytesWithNulError), - #[error("argument is null")] - NullPointerError, -} - -pub trait StringExt { - fn nul_terminate(&mut self) -> &mut [u8]; -} - -impl StringExt for String { - fn nul_terminate(&mut self) -> &mut [u8] { - self.reserve(1); - // SAFETY: the string is reserved to have enough capacity to fit in the null byte - // SAFETY: the null byte is explicitly added outside of the string's length - unsafe { - let buf = slice::from_raw_parts_mut(self.as_mut_ptr(), self.len() + 1); - *buf.get_unchecked_mut(self.len()) = b'\0'; - buf - } - } -} - -// The better CStr: UTF-8 validated + null terminated buffer -#[derive(PartialEq)] -pub struct Utf8CStr { - inner: [u8], -} - -impl Utf8CStr { - pub fn from_cstr(cstr: &CStr) -> Result<&Utf8CStr, StrErr> { - // Validate the buffer during construction - str::from_utf8(cstr.to_bytes())?; - Ok(unsafe { Self::from_bytes_unchecked(cstr.to_bytes_with_nul()) }) - } - - pub fn from_bytes(buf: &[u8]) -> Result<&Utf8CStr, StrErr> { - Self::from_cstr(CStr::from_bytes_with_nul(buf)?) - } - - pub fn from_string(s: &mut String) -> &Utf8CStr { - let buf = s.nul_terminate(); - // SAFETY: the null byte is explicitly added to the buffer - unsafe { Self::from_bytes_unchecked(buf) } - } - - #[inline] - pub unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr { - mem::transmute(buf) - } - - pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> { - if ptr.is_null() { - return Err(StrErr::NullPointerError); - } - Self::from_cstr(unsafe { CStr::from_ptr(ptr) }) - } - - #[inline] - pub fn as_bytes(&self) -> &[u8] { - // The length of the slice is at least 1 due to null termination check - unsafe { self.inner.get_unchecked(..self.inner.len() - 1) } - } - - #[inline] - pub fn as_bytes_with_nul(&self) -> &[u8] { - &self.inner - } - - #[inline] - pub fn as_ptr(&self) -> *const c_char { - self.inner.as_ptr().cast() - } - - #[inline] - pub fn as_cstr(&self) -> &CStr { - // SAFETY: Already validated as null terminated during construction - unsafe { CStr::from_bytes_with_nul_unchecked(&self.inner) } - } -} - -impl Deref for Utf8CStr { - type Target = str; - - #[inline] - fn deref(&self) -> &str { - // SAFETY: Already UTF-8 validated during construction - unsafe { str::from_utf8_unchecked(self.as_bytes()) } - } -} - -impl Display for Utf8CStr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(self.deref(), f) - } -} - -impl Debug for Utf8CStr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Debug::fmt(self.deref(), f) - } -} - -impl AsRef for Utf8CStr { - #[inline] - fn as_ref(&self) -> &CStr { - self.as_cstr() - } -} - -impl AsRef for Utf8CStr { - #[inline] - fn as_ref(&self) -> &str { - self.deref() - } -} - -impl AsRef for Utf8CStr { - #[inline] - fn as_ref(&self) -> &OsStr { - OsStr::new(self.deref()) - } -} - -impl AsRef for Utf8CStr { - #[inline] - fn as_ref(&self) -> &Path { - Path::new(self.deref()) - } -} - -impl PartialEq for Utf8CStr { - #[inline] - fn eq(&self, other: &CStr) -> bool { - self.as_cstr() == other - } -} - -impl PartialEq for Utf8CStr { - #[inline] - fn eq(&self, other: &str) -> bool { - self.deref() == other - } -} - -impl PartialEq for CStr { - #[inline] - fn eq(&self, other: &Utf8CStr) -> bool { - self == other.as_cstr() - } -} - -impl PartialEq for str { - #[inline] - fn eq(&self, other: &Utf8CStr) -> bool { - self == other.deref() - } -} +use crate::{ffi, StrErr, Utf8CStr}; pub fn errno() -> &'static mut i32 { unsafe { &mut *libc::__errno() } @@ -332,6 +71,10 @@ where Ok(self) } } + fn as_os_err(self) -> io::Result<()> { + self.check_os_err()?; + Ok(()) + } } macro_rules! impl_libc_return { @@ -376,7 +119,7 @@ impl> MutBytesExt for T { pub fn map_args(argc: i32, argv: *const *const c_char) -> Result, StrErr> { unsafe { slice::from_raw_parts(argv, argc as usize) } .iter() - .map(|s| unsafe { Utf8CStr::from_ptr(*s) }.map(|s| s.deref())) + .map(|s| unsafe { Utf8CStr::from_ptr(*s) }.map(|s| s.as_str())) .collect() } diff --git a/native/src/base/xwrap.rs b/native/src/base/xwrap.rs index 77139d2ed..31100db70 100644 --- a/native/src/base/xwrap.rs +++ b/native/src/base/xwrap.rs @@ -1,7 +1,6 @@ // Functions in this file are only for exporting to C++, DO NOT USE IN RUST use std::ffi::CStr; -use std::fmt::Write; use std::os::unix::io::RawFd; use std::ptr; @@ -11,10 +10,7 @@ use libc::{ ssize_t, SYS_dup3, }; -use crate::{ - cstr, errno, mkdirs, raw_cstr, readlink_unsafe, realpath, slice_from_ptr_mut, ResultExt, - Utf8CStr, -}; +use crate::{cstr, errno, raw_cstr, readlink_unsafe, FsPath, ResultExt, Utf8CStr, Utf8CStrSlice}; fn ptr_to_str<'a, T>(ptr: *const T) -> &'a str { if ptr.is_null() { @@ -68,9 +64,13 @@ mod c_export { #[no_mangle] unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { match Utf8CStr::from_ptr(path) { - Ok(p) => realpath(p, slice_from_ptr_mut(buf, bufsz)) - .log_cxx_with_msg(|w| w.write_fmt(format_args!("realpath {} failed", p))) - .map_or(-1, |v| v as isize), + Ok(p) => { + let mut buf = Utf8CStrSlice::from_ptr(buf, bufsz); + FsPath::from(p) + .realpath(&mut buf) + .log_cxx_with_msg(|w| w.write_fmt(format_args!("realpath {} failed", p))) + .map_or(-1, |_| buf.len() as isize) + } Err(_) => -1, } } @@ -566,7 +566,8 @@ unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 { #[no_mangle] unsafe extern "C" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 { match Utf8CStr::from_ptr(path) { - Ok(p) => mkdirs(p, mode) + Ok(p) => FsPath::from(p) + .mkdirs(mode) .log_cxx_with_msg(|w| w.write_fmt(format_args!("mkdirs {} failed", p))) .map_or(-1, |_| 0), Err(_) => -1, diff --git a/native/src/boot/cpio.rs b/native/src/boot/cpio.rs index 3a11f0df6..5a830d9f5 100644 --- a/native/src/boot/cpio.rs +++ b/native/src/boot/cpio.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::ffi::CStr; -use std::fmt::{Display, Formatter, Write as FmtWrite}; +use std::fmt::{Display, Formatter}; use std::fs::{metadata, read, DirBuilder, File}; use std::io::Write; use std::mem::size_of; diff --git a/native/src/boot/payload.rs b/native/src/boot/payload.rs index 7b1a9ba97..50eaddac7 100644 --- a/native/src/boot/payload.rs +++ b/native/src/boot/payload.rs @@ -1,4 +1,3 @@ -use std::fmt::Write as FmtWrite; use std::fs::File; use std::io::{BufReader, Read, Seek, SeekFrom, Write}; use std::os::fd::{AsRawFd, FromRawFd}; diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index 4891fa1b9..2333aefac 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -3,7 +3,7 @@ use std::fs::File; use std::io; use std::sync::{Mutex, OnceLock}; -use base::{copy_str, cstr, Directory, ResultExt, Utf8CStr, WalkResult}; +use base::{cstr, Directory, ResultExt, Utf8CStr, Utf8CStrBuf, Utf8CStrSlice, WalkResult}; use crate::logging::{magisk_logging, zygisk_logging}; @@ -36,8 +36,7 @@ impl MagiskD {} pub fn find_apk_path(pkg: &[u8], data: &mut [u8]) -> usize { use WalkResult::*; - fn inner(pkg: &[u8], data: &mut [u8]) -> io::Result { - let mut len = 0_usize; + fn inner(pkg: &[u8], buf: &mut dyn Utf8CStrBuf) -> io::Result { let pkg = match Utf8CStr::from_bytes(pkg) { Ok(pkg) => pkg, Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), @@ -49,7 +48,7 @@ pub fn find_apk_path(pkg: &[u8], data: &mut [u8]) -> usize { let d_name = e.d_name().to_bytes(); if d_name.starts_with(pkg.as_bytes()) && d_name[pkg.len()] == b'-' { // Found the APK path, we can abort now - len = e.path(data)?; + e.path(buf)?; return Ok(Abort); } if d_name.starts_with(b"~~") { @@ -57,10 +56,12 @@ pub fn find_apk_path(pkg: &[u8], data: &mut [u8]) -> usize { } Ok(Skip) })?; - if len > 0 { - len += copy_str(&mut data[len..], "/base.apk"); + if !buf.is_empty() { + buf.append("/base.apk"); } - Ok(len) + Ok(buf.len()) } - inner(pkg, data).log().unwrap_or(0) + inner(pkg, &mut Utf8CStrSlice::from(data)) + .log() + .unwrap_or(0) } diff --git a/native/src/core/logging.rs b/native/src/core/logging.rs index cd6223b34..31801a878 100644 --- a/native/src/core/logging.rs +++ b/native/src/core/logging.rs @@ -1,5 +1,6 @@ use std::cmp::min; use std::ffi::{c_char, c_void}; +use std::fmt::Write as FmtWrite; use std::fs::File; use std::io::{IoSlice, Read, Write}; use std::os::fd::{AsRawFd, FromRawFd, RawFd}; @@ -244,8 +245,8 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void { let mut logfile: LogFile = Buffer(&mut tmp); let mut meta = LogMeta::default(); - let mut buf: [u8; MAX_MSG_LEN] = [0; MAX_MSG_LEN]; - let mut aux: [u8; 64] = [0; 64]; + let mut msg_buf = [0u8; MAX_MSG_LEN]; + let mut aux = Utf8CStrArr::<64>::new(); loop { // Read request @@ -262,16 +263,16 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void { continue; } - if meta.len < 0 || meta.len > buf.len() as i32 { + if meta.len < 0 || meta.len > MAX_MSG_LEN as i32 { continue; } // Read the rest of the message - let msg = &mut buf[..(meta.len as usize)]; + let msg = &mut msg_buf[..(meta.len as usize)]; pipe.read_exact(msg)?; // Start building the log string - + aux.clear(); let prio = ALogPriority::from_i32(meta.prio).unwrap_or(ALogPriority::ANDROID_LOG_UNKNOWN); let prio = match prio { @@ -287,7 +288,6 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void { // Note: the obvious better implementation is to use the rust chrono crate, however // the crate cannot fetch the proper local timezone without pulling in a bunch of // timezone handling code. To reduce binary size, fallback to use localtime_r in libc. - let mut aux_len: usize; unsafe { let mut ts: timespec = std::mem::zeroed(); let mut tm: tm = std::mem::zeroed(); @@ -296,24 +296,22 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void { { continue; } - aux_len = strftime( - aux.as_mut_ptr().cast(), - aux.len(), + let len = strftime( + aux.mut_buf().as_mut_ptr().cast(), + aux.capacity(), raw_cstr!("%m-%d %T"), &tm, ); + aux.set_len(len); let ms = ts.tv_nsec / 1000000; - aux_len += bfmt!( - &mut aux[aux_len..], + aux.write_fmt(format_args!( ".{:03} {:5} {:5} {} : ", - ms, - meta.pid, - meta.tid, - prio - ); + ms, meta.pid, meta.tid, prio + )) + .ok(); } - let io1 = IoSlice::new(&aux[..aux_len]); + let io1 = IoSlice::new(aux.as_bytes_with_nul()); let io2 = IoSlice::new(msg); // We don't need to care the written len because we are writing less than PIPE_BUF // It's guaranteed to always write the whole thing atomically diff --git a/native/src/core/resetprop/persist.rs b/native/src/core/resetprop/persist.rs index 5cb99a14d..7abc09bd7 100644 --- a/native/src/core/resetprop/persist.rs +++ b/native/src/core/resetprop/persist.rs @@ -1,18 +1,19 @@ use core::ffi::c_char; +use std::io::Read; use std::{ - fs::{read_to_string, remove_file, rename, File}, + fs::File, io::{BufWriter, Write}, ops::{Deref, DerefMut}, os::fd::FromRawFd, - path::{Path, PathBuf}, pin::Pin, }; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; +use base::libc::{O_CLOEXEC, O_RDONLY}; use base::{ - cstr, debug, libc::mkstemp, raw_cstr, Directory, LoggedError, LoggedResult, MappedFile, - StringExt, Utf8CStr, WalkResult, + cstr, debug, libc::mkstemp, raw_cstr, Directory, FsPath, FsPathBuf, LibcReturn, LoggedError, + LoggedResult, MappedFile, Utf8CStr, Utf8CStrArr, WalkResult, }; use crate::ffi::{clone_attr, prop_cb_exec, PropCb}; @@ -76,36 +77,41 @@ impl PropExt for PersistentProperties { } fn check_proto() -> bool { - Path::new(PERSIST_PROP!()).exists() + FsPath::from(cstr!(PERSIST_PROP!())).exists() } fn file_get_prop(name: &Utf8CStr) -> LoggedResult { - let path = PathBuf::new().join(PERSIST_PROP_DIR!()).join(name); - let path = path.as_path(); - debug!("resetprop: read prop from [{}]\n", path.display()); - Ok(read_to_string(path)?) + let mut buf = Utf8CStrArr::default(); + let path = FsPathBuf::new(&mut buf) + .join(PERSIST_PROP_DIR!()) + .join(name); + let mut file = path.open(O_RDONLY | O_CLOEXEC)?; + debug!("resetprop: read prop from [{}]\n", path); + let mut s = String::new(); + file.read_to_string(&mut s)?; + Ok(s) } fn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()> { - let path = PathBuf::new().join(PERSIST_PROP_DIR!()).join(name); - let path = path.as_path(); + let mut buf = Utf8CStrArr::default(); + let path = FsPathBuf::new(&mut buf) + .join(PERSIST_PROP_DIR!()) + .join(name); if let Some(value) = value { - let mut tmp = String::from(concat!(PERSIST_PROP_DIR!(), ".prop.XXXXXX")); + let mut buf = Utf8CStrArr::default(); + let mut tmp = FsPathBuf::new(&mut buf).join(concat!(PERSIST_PROP_DIR!(), ".prop.XXXXXX")); { let mut f = unsafe { - let fd = mkstemp(tmp.as_mut_ptr() as *mut c_char); - if fd < 0 { - return Err(Default::default()); - } + let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?; File::from_raw_fd(fd) }; f.write_all(value.as_bytes())?; } debug!("resetprop: write prop to [{}]\n", tmp); - rename(tmp, path)?; + tmp.rename_to(path)? } else { - debug!("resetprop: unlink [{}]\n", path.display()); - remove_file(path)?; + debug!("resetprop: unlink [{}]\n", path); + path.remove()?; } Ok(()) } @@ -122,23 +128,18 @@ fn proto_read_props() -> LoggedResult { } fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> { - let mut tmp = String::from(concat!(PERSIST_PROP!(), ".XXXXXX")); - tmp.nul_terminate(); + let mut buf = Utf8CStrArr::default(); + let mut tmp = FsPathBuf::new(&mut buf).join(concat!(PERSIST_PROP!(), ".XXXXXX")); { let f = unsafe { - let fd = mkstemp(tmp.as_mut_ptr().cast()); - if fd < 0 { - return Err(Default::default()); - } + let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?; File::from_raw_fd(fd) }; debug!("resetprop: encode with protobuf [{}]", tmp); props.write_message(&mut Writer::new(BufWriter::new(f)))?; } - unsafe { - clone_attr(raw_cstr!(PERSIST_PROP!()), tmp.as_ptr().cast()); - } - rename(tmp, PERSIST_PROP!())?; + unsafe { clone_attr(raw_cstr!(PERSIST_PROP!()), tmp.as_ptr()) }; + tmp.rename_to(cstr!(PERSIST_PROP!()))?; Ok(()) } diff --git a/native/src/init/logging.rs b/native/src/init/logging.rs index 30a38b10e..cc92c9c4e 100644 --- a/native/src/init/logging.rs +++ b/native/src/init/logging.rs @@ -51,10 +51,12 @@ pub fn setup_klog() { fn klog_write_impl(_: LogLevel, msg: &[u8]) { if let Some(kmsg) = KMSG.get().as_mut() { - let mut buf: [u8; 4096] = [0; 4096]; - let mut len = copy_str(&mut buf, b"magiskinit: "); - len += copy_str(&mut buf[len..], msg); - kmsg.write_all(&buf[..len]).ok(); + let mut buf = Utf8CStrArr::default(); + buf.append("magiskinit: "); + unsafe { + buf.append_unchecked(msg); + } + kmsg.write_all(buf.as_bytes()).ok(); } }