Skip to content

Commit 1edcdfe

Browse files
authored
Add a readlinkat_raw function. (#923)
This is similar to `readlinkat`, but instead of returning an owned and allocated `CString`, takes a `&mut [MaybeUninit<u8>]` and returns a `&mut [u8]` containing the written bytes, which may have been truncated. This is trickier to use, and most users should still just use `readlinkat`, but it is usable in contexts that can't do dynamic allocation.
1 parent 15278a1 commit 1edcdfe

File tree

4 files changed

+95
-14
lines changed

4 files changed

+95
-14
lines changed

src/backend/libc/fs/syscalls.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
33
use crate::backend::c;
44
#[cfg(any(
5-
apple,
6-
linux_kernel,
5+
not(target_os = "redox"),
76
feature = "alloc",
87
all(linux_kernel, feature = "procfs")
98
))]
@@ -275,10 +274,7 @@ pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
275274
}
276275
}
277276

278-
#[cfg(all(
279-
any(feature = "alloc", all(linux_kernel, feature = "procfs")),
280-
not(target_os = "redox")
281-
))]
277+
#[cfg(not(target_os = "redox"))]
282278
#[inline]
283279
pub(crate) fn readlinkat(
284280
dirfd: BorrowedFd<'_>,

src/backend/linux_raw/fs/syscalls.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,6 @@ pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
961961
}
962962
}
963963

964-
#[cfg(any(feature = "alloc", feature = "procfs"))]
965964
#[inline]
966965
pub(crate) fn readlinkat(
967966
dirfd: BorrowedFd<'_>,

src/fs/at.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! [`cwd`]: crate::fs::CWD
77
88
use crate::fd::OwnedFd;
9+
use crate::ffi::CStr;
910
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
1011
use crate::fs::Access;
1112
#[cfg(not(target_os = "espidf"))]
@@ -22,14 +23,11 @@ use crate::fs::{Dev, FileType};
2223
use crate::fs::{Gid, Uid};
2324
use crate::fs::{Mode, OFlags};
2425
use crate::{backend, io, path};
25-
use backend::fd::AsFd;
26+
use backend::fd::{AsFd, BorrowedFd};
27+
use core::mem::MaybeUninit;
28+
use core::slice;
2629
#[cfg(feature = "alloc")]
27-
use {
28-
crate::ffi::{CStr, CString},
29-
crate::path::SMALL_PATH_BUFFER_SIZE,
30-
alloc::vec::Vec,
31-
backend::fd::BorrowedFd,
32-
};
30+
use {crate::ffi::CString, crate::path::SMALL_PATH_BUFFER_SIZE, alloc::vec::Vec};
3331
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
3432
use {crate::fs::Timestamps, crate::timespec::Nsecs};
3533

@@ -134,6 +132,48 @@ fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::R
134132
}
135133
}
136134

135+
/// `readlinkat(fd, path)`—Reads the contents of a symlink, without
136+
/// allocating.
137+
///
138+
/// This is the "raw" version which avoids allocating, but which is
139+
/// significantly trickier to use; most users should use plain [`readlinkat`].
140+
///
141+
/// This version writes bytes into the buffer and returns two slices, one
142+
/// containing the written bytes, and one containint the remaining
143+
/// uninitialized space. If the number of written bytes is equal to the length
144+
/// of the buffer, it means the buffer wasn't big enough to hold the full
145+
/// string, and callers should try again with a bigger buffer.
146+
///
147+
/// # References
148+
/// - [POSIX]
149+
/// - [Linux]
150+
///
151+
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
152+
/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
153+
#[inline]
154+
pub fn readlinkat_raw<P: path::Arg, Fd: AsFd>(
155+
dirfd: Fd,
156+
path: P,
157+
buf: &mut [MaybeUninit<u8>],
158+
) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
159+
path.into_with_c_str(|path| _readlinkat_raw(dirfd.as_fd(), path, buf))
160+
}
161+
162+
#[allow(unsafe_code)]
163+
fn _readlinkat_raw<'a>(
164+
dirfd: BorrowedFd<'_>,
165+
path: &CStr,
166+
buf: &'a mut [MaybeUninit<u8>],
167+
) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
168+
let n = backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buf)?;
169+
unsafe {
170+
Ok((
171+
slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), n),
172+
&mut buf[n..],
173+
))
174+
}
175+
}
176+
137177
/// `mkdirat(fd, path, mode)`—Creates a directory.
138178
///
139179
/// # References

tests/fs/readlinkat.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,49 @@ fn test_readlinkat() {
5050
let target = readlinkat(&dir, "another", Vec::new()).unwrap();
5151
assert_eq!(target.to_string_lossy(), "link");
5252
}
53+
54+
#[cfg(not(target_os = "redox"))]
55+
#[test]
56+
fn test_readlinkat_raw() {
57+
use core::mem::MaybeUninit;
58+
use rustix::fs::{openat, readlinkat_raw, symlinkat, Mode, OFlags, CWD};
59+
use std::ffi::OsStr;
60+
use std::os::unix::ffi::OsStrExt;
61+
62+
let tmp = tempfile::tempdir().unwrap();
63+
let dir = openat(CWD, tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();
64+
65+
let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::RUSR).unwrap();
66+
symlinkat("foo", &dir, "link").unwrap();
67+
68+
let mut some = [MaybeUninit::<u8>::new(0); 32];
69+
let mut short = [MaybeUninit::<u8>::new(0); 2];
70+
readlinkat_raw(&dir, "absent", &mut some).unwrap_err();
71+
readlinkat_raw(&dir, "foo", &mut some).unwrap_err();
72+
73+
let (yes, no) = readlinkat_raw(&dir, "link", &mut some).unwrap();
74+
assert_eq!(OsStr::from_bytes(yes).to_string_lossy(), "foo");
75+
assert!(!no.is_empty());
76+
77+
let (yes, no) = readlinkat_raw(&dir, "link", &mut short).unwrap();
78+
assert_eq!(yes, &[b'f', b'o']);
79+
assert!(no.is_empty());
80+
81+
symlinkat("link", &dir, "another").unwrap();
82+
83+
let (yes, no) = readlinkat_raw(&dir, "link", &mut some).unwrap();
84+
assert_eq!(OsStr::from_bytes(yes).to_string_lossy(), "foo");
85+
assert!(!no.is_empty());
86+
87+
let (yes, no) = readlinkat_raw(&dir, "link", &mut short).unwrap();
88+
assert_eq!(yes, &[b'f', b'o']);
89+
assert!(no.is_empty());
90+
91+
let (yes, no) = readlinkat_raw(&dir, "another", &mut some).unwrap();
92+
assert_eq!(OsStr::from_bytes(yes).to_string_lossy(), "link");
93+
assert!(!no.is_empty());
94+
95+
let (yes, no) = readlinkat_raw(&dir, "another", &mut short).unwrap();
96+
assert_eq!(yes, &[b'l', b'i']);
97+
assert!(no.is_empty());
98+
}

0 commit comments

Comments
 (0)