Use nix for libc functions

This commit is contained in:
topjohnwu
2025-09-08 23:59:29 -07:00
parent 17082af438
commit ef4e230258
31 changed files with 457 additions and 475 deletions

View File

@@ -3,20 +3,24 @@ use crate::{
cstr, errno, error,
};
use bytemuck::{Pod, bytes_of, bytes_of_mut};
use libc::{
EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, c_uint,
makedev, mode_t, stat,
};
use libc::{c_uint, makedev, mode_t};
use mem::MaybeUninit;
use nix::{
errno::Errno,
fcntl::{AT_FDCWD, OFlag},
sys::stat::{FchmodatFlags, Mode},
unistd::AccessFlags,
};
use num_traits::AsPrimitive;
use std::cmp::min;
use std::ffi::CStr;
use std::fmt::Display;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::ops::Deref;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
use std::path::Path;
use std::{io, mem, ptr, slice};
@@ -139,15 +143,12 @@ impl FileOrStd {
}
}
fn open_fd(path: &Utf8CStr, flags: i32, mode: mode_t) -> OsResult<'_, OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).into_os_result(
"open",
Some(path),
None,
)?;
Ok(OwnedFd::from_raw_fd(fd))
}
fn open_fd(path: &Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'_, OwnedFd> {
nix::fcntl::open(path, flags, Mode::from_bits_truncate(mode)).into_os_result(
"open",
Some(path),
None,
)
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
@@ -225,29 +226,24 @@ impl Utf8CStr {
unsafe { mem::transmute(self) }
}
pub fn open(&self, flags: i32) -> OsResult<'_, File> {
pub fn open(&self, flags: OFlag) -> OsResult<'_, File> {
Ok(File::from(open_fd(self, flags, 0)?))
}
pub fn create(&self, flags: i32, mode: mode_t) -> OsResult<'_, File> {
Ok(File::from(open_fd(self, O_CREAT | flags, mode)?))
pub fn create(&self, flags: OFlag, mode: mode_t) -> OsResult<'_, File> {
Ok(File::from(open_fd(self, OFlag::O_CREAT | flags, mode)?))
}
pub fn exists(&self) -> bool {
unsafe {
let mut st: stat = mem::zeroed();
libc::lstat(self.as_ptr(), &mut st) == 0
}
nix::sys::stat::lstat(self).is_ok()
}
pub fn rename_to<'a>(&'a self, name: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::rename(self.as_ptr(), name.as_ptr()).check_os_err(
"rename",
Some(self),
Some(name),
)
}
nix::fcntl::renameat(AT_FDCWD, self, AT_FDCWD, name).check_os_err(
"rename",
Some(self),
Some(name),
)
}
pub fn remove(&self) -> OsResult<'_, ()> {
@@ -267,73 +263,74 @@ impl Utf8CStr {
}
pub fn mkdir(&self, mode: mode_t) -> OsResult<'_, ()> {
unsafe {
if libc::mkdir(self.as_ptr(), mode) < 0 {
if *errno() == EEXIST {
libc::chmod(self.as_ptr(), mode).check_os_err("chmod", Some(self), None)?;
} else {
return Err(OsError::last_os_error("mkdir", Some(self), None));
}
}
match nix::unistd::mkdir(self, Mode::from_bits_truncate(mode)) {
Ok(_) | Err(Errno::EEXIST) => Ok(()),
Err(e) => Err(OsError::new(e, "mkdir", Some(self), None)),
}
Ok(())
}
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
let fd = self.open(O_PATH | O_CLOEXEC)?;
let mut st1: libc::stat;
let mut st2: libc::stat;
let fd = self.open(OFlag::O_PATH | OFlag::O_CLOEXEC)?;
let mut skip_check = false;
unsafe {
st1 = mem::zeroed();
if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 {
let st1 = match nix::sys::stat::fstat(&fd) {
Ok(st) => st,
Err(_) => {
// This will only fail on Linux < 3.6
skip_check = true;
unsafe { mem::zeroed() }
}
}
};
fd_path(fd.as_raw_fd(), buf)?;
unsafe {
st2 = mem::zeroed();
libc::stat(buf.as_ptr(), &mut st2).check_os_err("stat", Some(self), None)?;
if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {
*errno() = ENOENT;
return Err(OsError::last_os_error("realpath", Some(self), None));
}
let st2 = nix::sys::stat::stat(buf.as_cstr()).into_os_result("stat", Some(self), None)?;
if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {
return Err(OsError::new(Errno::ENOENT, "realpath", Some(self), None));
}
Ok(())
}
pub fn get_attr(&self) -> OsResult<'_, FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::lstat(self.as_ptr(), &mut attr.st).check_os_err("lstat", Some(self), None)?;
#[allow(unused_mut)]
let mut attr = FileAttr {
st: nix::sys::stat::lstat(self).into_os_result("lstat", Some(self), None)?,
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
}
con: cstr::buf::new(),
};
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
Ok(attr)
}
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
unsafe {
if !attr.is_symlink() && libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()) < 0
{
let self_attr = self.get_attr()?;
if !self_attr.is_symlink() {
return Err(OsError::last_os_error("chmod", Some(self), None));
}
if !attr.is_symlink()
&& let Err(e) = nix::sys::stat::fchmodat(
AT_FDCWD,
self,
Mode::from_bits_truncate((attr.st.st_mode & 0o777).as_()),
FchmodatFlags::FollowSymlink,
)
{
// Double check if self is symlink before reporting error
let self_attr = self.get_attr()?;
if !self_attr.is_symlink() {
return Err(OsError::new(e, "chmod", Some(self), None));
}
}
unsafe {
libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"lchown",
Some(self),
None,
)?;
}
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
Ok(())
}
@@ -388,17 +385,19 @@ impl Utf8CStr {
// ln -s target self
pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::symlink(target.as_ptr(), self.as_ptr()).check_os_err(
"symlink",
Some(target),
Some(self),
)
}
nix::unistd::symlinkat(target, AT_FDCWD, self).check_os_err(
"symlink",
Some(target),
Some(self),
)
}
pub fn mkfifo(&self, mode: mode_t) -> OsResult<'_, ()> {
unsafe { libc::mkfifo(self.as_ptr(), mode).check_os_err("mkfifo", Some(self), None) }
nix::unistd::mkfifo(self, Mode::from_bits_truncate(mode)).check_os_err(
"mkfifo",
Some(self),
None,
)
}
}
@@ -408,7 +407,7 @@ impl Utf8CStr {
pub fn remove_all(&self) -> LoggedResult<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
let dir = Directory::try_from(open_fd(self, O_RDONLY | O_CLOEXEC, 0)?)?;
let dir = Directory::open(self)?;
dir.remove_all()?;
}
Ok(self.remove()?)
@@ -431,12 +430,7 @@ impl Utf8CStr {
break;
};
path.append_path(s);
unsafe {
if libc::mkdir(path.as_ptr(), mode) < 0 && *errno() != EEXIST {
return Err(OsError::last_os_error("mkdir", Some(&path), None))?;
}
}
path.mkdir(mode)?;
}
*errno() = 0;
@@ -454,8 +448,11 @@ impl Utf8CStr {
// It's OK if remove failed
path.remove().ok();
if attr.is_file() {
let mut src = self.open(O_RDONLY | O_CLOEXEC)?;
let mut dest = path.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o777)?;
let mut src = self.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;
let mut dest = path.create(
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_CLOEXEC,
0o777,
)?;
std::io::copy(&mut src, &mut dest)?;
} else if attr.is_symlink() {
let mut buf = cstr::buf::default();
@@ -512,37 +509,41 @@ impl Utf8CStr {
impl FsPathFollow {
pub fn exists(&self) -> bool {
unsafe { libc::access(self.as_ptr(), F_OK) == 0 }
nix::unistd::access(self.deref(), AccessFlags::F_OK).is_ok()
}
pub fn get_attr(&self) -> OsResult<'_, FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::stat(self.as_ptr(), &mut attr.st).check_os_err("stat", Some(self), None)?;
#[allow(unused_mut)]
let mut attr = FileAttr {
st: nix::sys::stat::stat(self.deref()).into_os_result("lstat", Some(self), None)?,
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
}
con: cstr::buf::new(),
};
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
Ok(attr)
}
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
nix::sys::stat::fchmodat(
AT_FDCWD,
self.deref(),
Mode::from_bits_truncate((attr.st.st_mode & 0o777).as_()),
FchmodatFlags::FollowSymlink,
)
.check_os_err("chmod", Some(self), None)?;
unsafe {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).check_os_err(
"chmod",
Some(self),
None,
)?;
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"chown",
Some(self),
None,
)?;
}
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
Ok(())
}
@@ -751,7 +752,7 @@ unsafe extern "C" {
// We mark the returned slice static because it is valid until explicitly unmapped
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<'_, &'static mut [u8]> {
unsafe { map_file_at(BorrowedFd::borrow_raw(libc::AT_FDCWD), path, rw) }
map_file_at(AT_FDCWD, path, rw)
}
pub(crate) fn map_file_at<'a>(
@@ -765,17 +766,9 @@ pub(crate) fn map_file_at<'a>(
#[cfg(target_pointer_width = "32")]
const BLKGETSIZE64: u32 = 0x80041272;
let flag = if rw { O_RDWR } else { O_RDONLY };
let fd = unsafe {
OwnedFd::from_raw_fd(
libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).into_os_result(
"openat",
Some(path),
None,
)?,
)
};
let flag = if rw { OFlag::O_RDWR } else { OFlag::O_RDONLY };
let fd = nix::fcntl::openat(dirfd, path, flag | OFlag::O_CLOEXEC, Mode::empty())
.into_os_result("openat", Some(path), None)?;
let attr = fd_get_attr(fd.as_raw_fd())?;
let sz = if attr.is_block_device() {
let mut sz = 0_u64;
@@ -881,7 +874,7 @@ fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
let mut res = vec![];
let mut path = format!("/proc/{pid}/mountinfo");
if let Ok(file) = Utf8CStr::from_string(&mut path).open(O_RDONLY | O_CLOEXEC) {
if let Ok(file) = Utf8CStr::from_string(&mut path).open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
BufReader::new(file).for_each_line(|line| {
parse_mount_info_line(line)
.map(|info| res.push(info))
@@ -890,19 +883,3 @@ pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
}
res
}
pub struct PipeFd {
pub read: OwnedFd,
pub write: OwnedFd,
}
pub fn make_pipe(flags: i32) -> OsResult<'static, PipeFd> {
let mut pipefd: [RawFd; 2] = [0; 2];
unsafe {
libc::pipe2(pipefd.as_mut_ptr(), flags).check_os_err("pipe2", None, None)?;
Ok(PipeFd {
read: OwnedFd::from_raw_fd(pipefd[0]),
write: OwnedFd::from_raw_fd(pipefd[1]),
})
}
}