Skip to content

Commit f405ff0

Browse files
wan9chicodex
andcommitted
feat(fspy-nostd): add Unix file lifecycle operations
Co-authored-by: GPT-5 Codex <codex@openai.com>
1 parent e41e074 commit f405ff0

5 files changed

Lines changed: 73 additions & 31 deletions

File tree

‎crates/fspy_nostd/src/fs/linux.rs‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustix::fd::FromRawFd as _;
44

55
use crate::{
66
AsRawFd as _, BorrowedFd, CStr, Error, Fat, OwnedFd, Result, Thin,
7-
fs::{Mode, OFlags},
7+
fs::{AtFlags, Mode, OFlags},
88
};
99

1010
// Linux UAPI `PATH_MAX`.
@@ -40,6 +40,22 @@ pub(super) fn openat<R>(
4040
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
4141
}
4242

43+
#[expect(clippy::needless_pass_by_value, reason = "CStr is a borrowed value type")]
44+
pub(super) fn unlinkat<R>(dirfd: BorrowedFd<'_>, path: CStr<'_, R>, flags: AtFlags) -> Result<()> {
45+
// SAFETY: `dirfd` remains borrowed and `path` is NUL-terminated for the
46+
// syscall.
47+
unsafe {
48+
syscalls::syscall3(
49+
syscalls::Sysno::unlinkat,
50+
syscall_fd(dirfd)?,
51+
path.as_ptr().addr(),
52+
usize::try_from(flags.bits()).map_err(|_| Error::INVAL)?,
53+
)
54+
}
55+
.map_err(|errno| Error::from_raw_os_error(errno.into_raw()))?;
56+
Ok(())
57+
}
58+
4359
/// Reads the target of `path` relative to `dirfd` into `buf`.
4460
///
4561
/// The returned bytes borrow the initialized prefix of `buf`. As with the

‎crates/fspy_nostd/src/fs/mac.rs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use core::{mem::MaybeUninit, slice};
22

33
use rustix::{
44
fd::{AsFd as _, AsRawFd as _, FromRawFd as _, OwnedFd},
5-
fs::{Mode, OFlags},
5+
fs::{AtFlags, Mode, OFlags},
66
};
77

88
use crate::{BorrowedFd, CStr, CWD, Error, Fat, Result, Thin};
@@ -37,6 +37,21 @@ pub(super) fn openat<R>(
3737
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
3838
}
3939

40+
#[expect(clippy::needless_pass_by_value, reason = "CStr is a borrowed value type")]
41+
pub(super) fn unlinkat<R>(dirfd: BorrowedFd<'_>, path: CStr<'_, R>, flags: AtFlags) -> Result<()> {
42+
// SAFETY: `dirfd` remains borrowed and `path` is NUL-terminated for the
43+
// call.
44+
let result = unsafe {
45+
libc::unlinkat(dirfd.as_raw_fd(), path.as_ptr().cast(), flags.bits().cast_signed())
46+
};
47+
if result == -1 {
48+
// SAFETY: libSystem stored this call's error before returning -1.
49+
Err(Error::from_raw_os_error(unsafe { *libc::__error() }))
50+
} else {
51+
Ok(())
52+
}
53+
}
54+
4055
/// Gets the path associated with `fd`.
4156
///
4257
/// `F_GETPATH` writes a NUL-terminated path into its `MAXPATHLEN` buffer but

‎crates/fspy_nostd/src/fs/mod.rs‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use core::mem::MaybeUninit;
44

5-
pub use rustix::fs::{Mode, OFlags, fstat};
5+
pub use rustix::fs::{AtFlags, Mode, OFlags, fstat, ftruncate};
66

77
use crate::{BorrowedFd, CStr, Fat, OwnedFd, Result};
88

@@ -37,6 +37,15 @@ pub fn openat<R>(
3737
imp::openat(dirfd, path, flags, mode)
3838
}
3939

40+
/// Removes `path` relative to `dirfd`.
41+
///
42+
/// # Errors
43+
///
44+
/// Returns the error reported by `unlinkat`.
45+
pub fn unlinkat<R>(dirfd: BorrowedFd<'_>, path: CStr<'_, R>, flags: AtFlags) -> Result<()> {
46+
imp::unlinkat(dirfd, path, flags)
47+
}
48+
4049
/// Writes the absolute pathname of the current working directory into `buf`.
4150
///
4251
/// The returned C string borrows `buf`, starts at the same address as `buf`,

‎crates/fspy_nostd/src/lib.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ macro_rules! wide_cstr {
6565

6666
#[cfg(unix)]
6767
pub use rustix::{
68-
fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
68+
fd::{AsRawFd, BorrowedFd, OwnedFd},
6969
fs::CWD,
7070
io::Errno as Error,
7171
};

‎crates/fspy_shm/src/unix.rs‎

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
use std::{
55
env::temp_dir,
66
ffi::OsStr,
7-
fs::{self, File, OpenOptions},
87
io,
98
num::NonZeroUsize,
10-
os::unix::{ffi::OsStrExt as _, fs::OpenOptionsExt as _, io::IntoRawFd as _},
9+
os::unix::ffi::OsStrExt as _,
1110
path::PathBuf,
1211
ptr::{self, NonNull},
1312
};
@@ -75,19 +74,20 @@ pub fn create(size: usize) -> io::Result<(ShmKeeper, ShmHandle)> {
7574
let path = std::path::absolute(temp_dir())?
7675
.join(format!("{BACKING_PREFIX}{}.shm", Uuid::new_v4().simple()));
7776

78-
let file = OpenOptions::new()
79-
.read(true)
80-
.write(true)
81-
.create_new(true)
77+
let file = open_file(
78+
path.as_os_str(),
79+
fspy_nostd::fs::OFlags::RDWR
80+
| fspy_nostd::fs::OFlags::CREATE
81+
| fspy_nostd::fs::OFlags::EXCL
82+
| fspy_nostd::fs::OFlags::CLOEXEC,
8283
// Only the creating user may open the mapping.
83-
.mode(0o600)
84-
.open(&path)?;
84+
fspy_nostd::fs::Mode::RUSR | fspy_nostd::fs::Mode::WUSR,
85+
)?;
8586
// The keeper exists from here on, so every error path below cleans up.
8687
let keeper = ShmKeeper { path };
8788

8889
// Every byte reads as zero because the file is all holes.
89-
file.set_len(size_u64)?;
90-
let file = into_nostd_fd(file);
90+
fspy_nostd::fs::ftruncate(&file, size_u64).map_err(error_to_io)?;
9191

9292
Ok((keeper, ShmHandle { file, size }))
9393
}
@@ -102,7 +102,11 @@ pub fn create(size: usize) -> io::Result<(ShmKeeper, ShmHandle)> {
102102
/// Returns an error if the shared memory is unavailable, which is the common
103103
/// case once its keeper has been dropped.
104104
pub fn open(id: &OsStr) -> io::Result<ShmHandle> {
105-
let file = open_file(id)?;
105+
let file = open_file(
106+
id,
107+
fspy_nostd::fs::OFlags::RDWR | fspy_nostd::fs::OFlags::CLOEXEC,
108+
fspy_nostd::fs::Mode::empty(),
109+
)?;
106110
// If another process shrinks the file before `map`, mapping fails. If it
107111
// resizes afterwards, nothing here touches the mapped pages. A concurrent
108112
// resize cannot make a mapping access invalid memory.
@@ -113,16 +117,21 @@ pub fn open(id: &OsStr) -> io::Result<ShmHandle> {
113117
Ok(ShmHandle { file, size })
114118
}
115119

116-
fn open_file(path: &OsStr) -> io::Result<fspy_nostd::OwnedFd> {
120+
fn open_file(
121+
path: &OsStr,
122+
flags: fspy_nostd::fs::OFlags,
123+
mode: fspy_nostd::fs::Mode,
124+
) -> io::Result<fspy_nostd::OwnedFd> {
117125
let mut buf = [0_u8; fspy_nostd::fs::PATH_MAX];
118126
let path = copy_path(path, &mut buf)?;
119-
fspy_nostd::fs::openat(
120-
fspy_nostd::CWD,
121-
path,
122-
fspy_nostd::fs::OFlags::RDWR | fspy_nostd::fs::OFlags::CLOEXEC,
123-
fspy_nostd::fs::Mode::empty(),
124-
)
125-
.map_err(error_to_io)
127+
fspy_nostd::fs::openat(fspy_nostd::CWD, path, flags, mode).map_err(error_to_io)
128+
}
129+
130+
fn remove_file(path: &OsStr) -> io::Result<()> {
131+
let mut buf = [0_u8; fspy_nostd::fs::PATH_MAX];
132+
let path = copy_path(path, &mut buf)?;
133+
fspy_nostd::fs::unlinkat(fspy_nostd::CWD, path, fspy_nostd::fs::AtFlags::empty())
134+
.map_err(error_to_io)
126135
}
127136

128137
fn copy_path<'buf>(
@@ -150,16 +159,9 @@ fn error_to_io(error: fspy_nostd::Error) -> io::Error {
150159
io::Error::from_raw_os_error(error.raw_os_error())
151160
}
152161

153-
fn into_nostd_fd(file: File) -> fspy_nostd::OwnedFd {
154-
let fd = file.into_raw_fd();
155-
// SAFETY: ownership of `file`'s descriptor transfers without closing or
156-
// duplicating it.
157-
unsafe { fspy_nostd::FromRawFd::from_raw_fd(fd) }
158-
}
159-
160162
impl Drop for ShmKeeper {
161163
fn drop(&mut self) {
162-
let _ = fs::remove_file(&self.path);
164+
let _ = remove_file(self.path.as_os_str());
163165
}
164166
}
165167

0 commit comments

Comments
 (0)