Skip to content

Commit

Permalink
Add a readlinkat_raw function. (#923)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sunfishcode committed Nov 7, 2023
1 parent 15278a1 commit 1edcdfe
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 14 deletions.
8 changes: 2 additions & 6 deletions src/backend/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

use crate::backend::c;
#[cfg(any(
apple,
linux_kernel,
not(target_os = "redox"),
feature = "alloc",
all(linux_kernel, feature = "procfs")
))]
Expand Down Expand Up @@ -275,10 +274,7 @@ pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
}
}

#[cfg(all(
any(feature = "alloc", all(linux_kernel, feature = "procfs")),
not(target_os = "redox")
))]
#[cfg(not(target_os = "redox"))]
#[inline]
pub(crate) fn readlinkat(
dirfd: BorrowedFd<'_>,
Expand Down
1 change: 0 additions & 1 deletion src/backend/linux_raw/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,6 @@ pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
}
}

#[cfg(any(feature = "alloc", feature = "procfs"))]
#[inline]
pub(crate) fn readlinkat(
dirfd: BorrowedFd<'_>,
Expand Down
54 changes: 47 additions & 7 deletions src/fs/at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! [`cwd`]: crate::fs::CWD

use crate::fd::OwnedFd;
use crate::ffi::CStr;
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
use crate::fs::Access;
#[cfg(not(target_os = "espidf"))]
Expand All @@ -22,14 +23,11 @@ use crate::fs::{Dev, FileType};
use crate::fs::{Gid, Uid};
use crate::fs::{Mode, OFlags};
use crate::{backend, io, path};
use backend::fd::AsFd;
use backend::fd::{AsFd, BorrowedFd};
use core::mem::MaybeUninit;
use core::slice;
#[cfg(feature = "alloc")]
use {
crate::ffi::{CStr, CString},
crate::path::SMALL_PATH_BUFFER_SIZE,
alloc::vec::Vec,
backend::fd::BorrowedFd,
};
use {crate::ffi::CString, crate::path::SMALL_PATH_BUFFER_SIZE, alloc::vec::Vec};
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
use {crate::fs::Timestamps, crate::timespec::Nsecs};

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

/// `readlinkat(fd, path)`—Reads the contents of a symlink, without
/// allocating.
///
/// This is the "raw" version which avoids allocating, but which is
/// significantly trickier to use; most users should use plain [`readlinkat`].
///
/// This version writes bytes into the buffer and returns two slices, one
/// containing the written bytes, and one containint the remaining
/// uninitialized space. If the number of written bytes is equal to the length
/// of the buffer, it means the buffer wasn't big enough to hold the full
/// string, and callers should try again with a bigger buffer.
///
/// # References
/// - [POSIX]
/// - [Linux]
///
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
#[inline]
pub fn readlinkat_raw<P: path::Arg, Fd: AsFd>(
dirfd: Fd,
path: P,
buf: &mut [MaybeUninit<u8>],
) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
path.into_with_c_str(|path| _readlinkat_raw(dirfd.as_fd(), path, buf))
}

#[allow(unsafe_code)]
fn _readlinkat_raw<'a>(
dirfd: BorrowedFd<'_>,
path: &CStr,
buf: &'a mut [MaybeUninit<u8>],
) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
let n = backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buf)?;
unsafe {
Ok((
slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), n),
&mut buf[n..],
))
}
}

/// `mkdirat(fd, path, mode)`—Creates a directory.
///
/// # References
Expand Down
46 changes: 46 additions & 0 deletions tests/fs/readlinkat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,49 @@ fn test_readlinkat() {
let target = readlinkat(&dir, "another", Vec::new()).unwrap();
assert_eq!(target.to_string_lossy(), "link");
}

#[cfg(not(target_os = "redox"))]
#[test]
fn test_readlinkat_raw() {
use core::mem::MaybeUninit;
use rustix::fs::{openat, readlinkat_raw, symlinkat, Mode, OFlags, CWD};
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;

let tmp = tempfile::tempdir().unwrap();
let dir = openat(CWD, tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();

let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::RUSR).unwrap();
symlinkat("foo", &dir, "link").unwrap();

let mut some = [MaybeUninit::<u8>::new(0); 32];
let mut short = [MaybeUninit::<u8>::new(0); 2];
readlinkat_raw(&dir, "absent", &mut some).unwrap_err();
readlinkat_raw(&dir, "foo", &mut some).unwrap_err();

let (yes, no) = readlinkat_raw(&dir, "link", &mut some).unwrap();
assert_eq!(OsStr::from_bytes(yes).to_string_lossy(), "foo");
assert!(!no.is_empty());

let (yes, no) = readlinkat_raw(&dir, "link", &mut short).unwrap();
assert_eq!(yes, &[b'f', b'o']);
assert!(no.is_empty());

symlinkat("link", &dir, "another").unwrap();

let (yes, no) = readlinkat_raw(&dir, "link", &mut some).unwrap();
assert_eq!(OsStr::from_bytes(yes).to_string_lossy(), "foo");
assert!(!no.is_empty());

let (yes, no) = readlinkat_raw(&dir, "link", &mut short).unwrap();
assert_eq!(yes, &[b'f', b'o']);
assert!(no.is_empty());

let (yes, no) = readlinkat_raw(&dir, "another", &mut some).unwrap();
assert_eq!(OsStr::from_bytes(yes).to_string_lossy(), "link");
assert!(!no.is_empty());

let (yes, no) = readlinkat_raw(&dir, "another", &mut short).unwrap();
assert_eq!(yes, &[b'l', b'i']);
assert!(no.is_empty());
}

0 comments on commit 1edcdfe

Please sign in to comment.