Merge branch 'readlinkat' into 'master'
Implement readlinkat See merge request redox-os/relibc!708
This commit is contained in:
@@ -14,7 +14,8 @@ use crate::{
|
||||
error::{Errno, ResultExt},
|
||||
header::{
|
||||
crypt::{crypt_data, crypt_r},
|
||||
errno, fcntl, limits,
|
||||
errno::{self, ENAMETOOLONG},
|
||||
fcntl, limits,
|
||||
stdlib::getenv,
|
||||
sys_ioctl, sys_resource, sys_time, sys_utsname, termios,
|
||||
time::timespec,
|
||||
@@ -835,6 +836,25 @@ pub unsafe extern "C" fn readlink(
|
||||
.or_minus_one_errno()
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/readlink.html>.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn readlinkat(
|
||||
dirfd: c_int,
|
||||
pathname: *const c_char,
|
||||
buf: *mut c_char,
|
||||
len: size_t,
|
||||
) -> ssize_t {
|
||||
let pathname = CStr::from_ptr(pathname);
|
||||
let mut buf = slice::from_raw_parts_mut(buf.cast(), len);
|
||||
Sys::readlinkat(dirfd, pathname, &mut buf)
|
||||
.map(|read| {
|
||||
read.try_into()
|
||||
.map_err(|_| Errno(ENAMETOOLONG))
|
||||
.or_minus_one_errno()
|
||||
})
|
||||
.or_minus_one_errno()
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/rmdir.html>.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rmdir(path: *const c_char) -> c_int {
|
||||
|
||||
@@ -585,6 +585,18 @@ impl Pal for Sys {
|
||||
})
|
||||
}
|
||||
|
||||
fn readlinkat(dirfd: c_int, pathname: CStr, out: &mut [u8]) -> Result<usize> {
|
||||
e_raw(unsafe {
|
||||
syscall!(
|
||||
READLINKAT,
|
||||
dirfd,
|
||||
pathname.as_ptr(),
|
||||
out.as_mut_ptr(),
|
||||
out.len()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn rename(old: CStr, new: CStr) -> Result<()> {
|
||||
e_raw(unsafe { syscall!(RENAMEAT, AT_FDCWD, old.as_ptr(), AT_FDCWD, new.as_ptr()) })
|
||||
.map(|_| ())
|
||||
|
||||
@@ -228,6 +228,8 @@ pub trait Pal {
|
||||
|
||||
fn readlink(pathname: CStr, out: &mut [u8]) -> Result<usize>;
|
||||
|
||||
fn readlinkat(dirfd: c_int, pathname: CStr, out: &mut [u8]) -> Result<usize>;
|
||||
|
||||
fn rename(old: CStr, new: CStr) -> Result<()>;
|
||||
|
||||
fn rmdir(path: CStr) -> Result<()>;
|
||||
|
||||
+17
-42
@@ -25,8 +25,7 @@ use crate::{
|
||||
EBADF, EBADFD, EBADR, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM, ENOSYS,
|
||||
EOPNOTSUPP, EPERM, ERANGE,
|
||||
},
|
||||
fcntl::{self, AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, O_NOFOLLOW, O_PATH, O_RDONLY},
|
||||
limits,
|
||||
fcntl, limits,
|
||||
sys_mman::{MAP_ANONYMOUS, MAP_FAILED, PROT_READ, PROT_WRITE},
|
||||
sys_random,
|
||||
sys_resource::{rlimit, rusage, RLIM_INFINITY},
|
||||
@@ -86,7 +85,10 @@ macro_rules! path_from_c_str {
|
||||
}};
|
||||
}
|
||||
|
||||
use self::{exec::Executable, path::canonicalize};
|
||||
use self::{
|
||||
exec::Executable,
|
||||
path::{canonicalize, cap_path_at},
|
||||
};
|
||||
|
||||
static CLONE_LOCK: RwLock<()> = RwLock::new(());
|
||||
|
||||
@@ -285,48 +287,15 @@ impl Pal for Sys {
|
||||
}
|
||||
|
||||
unsafe fn fstatat(
|
||||
fildes: c_int,
|
||||
dirfd: c_int,
|
||||
path: *const c_char,
|
||||
buf: *mut stat,
|
||||
flags: c_int,
|
||||
) -> Result<()> {
|
||||
let path =
|
||||
match CStr::from_nullable_ptr(path).and_then(|cs| str::from_utf8(cs.to_bytes()).ok()) {
|
||||
Some(path) if !path.is_empty() => Ok(path),
|
||||
_ if flags & AT_EMPTY_PATH == AT_EMPTY_PATH => return Sys::fstat(fildes, buf),
|
||||
_ => Err(Errno(ENOENT)),
|
||||
}?;
|
||||
|
||||
let oflags = if flags & AT_SYMLINK_NOFOLLOW == AT_SYMLINK_NOFOLLOW {
|
||||
O_RDONLY | O_PATH | O_NOFOLLOW
|
||||
} else {
|
||||
O_RDONLY
|
||||
};
|
||||
|
||||
// Absolute paths are passed to fstat without processing.
|
||||
// canonicalize_using_cwd checks that path is absolute so a third branch that does so here
|
||||
// isn't needed.
|
||||
let path = if fildes == AT_FDCWD {
|
||||
// The special constant AT_FDCWD indicates that we should use the cwd.
|
||||
let mut buf = [0; limits::PATH_MAX];
|
||||
let len = path::getcwd(&mut buf).ok_or(Errno(ENAMETOOLONG))?;
|
||||
// SAFETY: Redox's cwd is stored as a str.
|
||||
let cwd = unsafe { str::from_utf8_unchecked(&buf[..len]) };
|
||||
|
||||
path::canonicalize_using_cwd(Some(cwd), path).ok_or(Errno(EBADF))?
|
||||
} else {
|
||||
let mut buf = [0; limits::PATH_MAX];
|
||||
let len = Sys::fpath(fildes, &mut buf)?;
|
||||
// SAFETY: fpath checks then copies valid UTF8.
|
||||
let dir = unsafe { str::from_utf8_unchecked(&buf[..len]) };
|
||||
|
||||
path::canonicalize_using_cwd(Some(dir), path).ok_or(Errno(EBADF))?
|
||||
};
|
||||
let path = CString::new(path).map_err(|_| Errno(ENOENT))?;
|
||||
|
||||
// TODO:
|
||||
// * Switch open to openat.
|
||||
let file = File::open(path.as_c_str().into(), oflags)?;
|
||||
let path = CStr::from_nullable_ptr(path)
|
||||
.and_then(|cs| str::from_utf8(cs.to_bytes()).ok())
|
||||
.ok_or(Errno(ENOENT))?;
|
||||
let file = cap_path_at(dirfd, path, flags, 0)?;
|
||||
Sys::fstat(*file, buf)
|
||||
}
|
||||
|
||||
@@ -568,7 +537,7 @@ impl Pal for Sys {
|
||||
}
|
||||
|
||||
unsafe fn setrlimit(resource: c_int, rlim: *const rlimit) -> Result<()> {
|
||||
//TOOD
|
||||
// TODO
|
||||
eprintln!(
|
||||
"relibc setrlimit({}, {:p}): not implemented",
|
||||
resource, rlim
|
||||
@@ -900,6 +869,12 @@ impl Pal for Sys {
|
||||
Self::read(*file, out)
|
||||
}
|
||||
|
||||
fn readlinkat(dirfd: c_int, path: CStr, out: &mut [u8]) -> Result<usize> {
|
||||
let path = str::from_utf8(path.to_bytes()).map_err(|_| Errno(ENOENT))?;
|
||||
let file = cap_path_at(dirfd, path, 0, fcntl::O_SYMLINK)?;
|
||||
Sys::read(*file, out)
|
||||
}
|
||||
|
||||
fn rename(oldpath: CStr, newpath: CStr) -> Result<()> {
|
||||
let newpath = newpath.to_str().map_err(|_| Errno(EINVAL))?;
|
||||
let newpath = canonicalize(newpath).map_err(|_| Errno(EINVAL))?;
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
use alloc::{
|
||||
borrow::ToOwned,
|
||||
boxed::Box,
|
||||
ffi::CString,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::{ffi::c_int, str};
|
||||
use redox_rt::signal::tmp_disable_signals;
|
||||
use syscall::{data::Stat, error::*, flag::*};
|
||||
|
||||
use super::{libcscheme, FdGuard};
|
||||
use crate::sync::Mutex;
|
||||
use super::{libcscheme, FdGuard, Pal, Sys};
|
||||
use crate::{
|
||||
error::Errno,
|
||||
fs::File,
|
||||
header::{fcntl, limits},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
pub use redox_path::{canonicalize_using_cwd, RedoxPath};
|
||||
|
||||
@@ -205,3 +212,48 @@ fn get_parent_path(path: &str) -> Option<&str> {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Fold into openat or something.
|
||||
pub fn cap_path_at(
|
||||
dirfd: c_int,
|
||||
path: &str,
|
||||
at_flags: c_int,
|
||||
oflags: c_int,
|
||||
) -> Result<File, Errno> {
|
||||
// Ideally, the function calling this fn would check AT_EMPTY_PATH and just call fstat or
|
||||
// whatever with the fd.
|
||||
if path.is_empty() && at_flags & fcntl::AT_EMPTY_PATH != fcntl::AT_EMPTY_PATH {
|
||||
return Err(Errno(ENOENT));
|
||||
}
|
||||
|
||||
let oflags = if at_flags & fcntl::AT_SYMLINK_NOFOLLOW == fcntl::AT_SYMLINK_NOFOLLOW {
|
||||
fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW | fcntl::O_PATH | fcntl::O_SYMLINK | oflags
|
||||
} else {
|
||||
fcntl::O_CLOEXEC | fcntl::O_RDONLY | oflags
|
||||
};
|
||||
|
||||
// Absolute paths are passed without processing unless RESOLVE_BENEATH is used.
|
||||
// canonicalize_using_cwd checks that path is absolute so a third branch that does so here
|
||||
// isn't needed.
|
||||
let path = if dirfd == fcntl::AT_FDCWD {
|
||||
// The special constant AT_FDCWD indicates that we should use the cwd.
|
||||
let mut buf = [0; limits::PATH_MAX];
|
||||
let len = getcwd(&mut buf).ok_or(Errno(ENAMETOOLONG))?;
|
||||
// SAFETY: Redox's cwd is stored as a str.
|
||||
let cwd = unsafe { str::from_utf8_unchecked(&buf[..len]) };
|
||||
|
||||
canonicalize_using_cwd(Some(cwd), path).ok_or(Errno(EBADF))?
|
||||
} else {
|
||||
let mut buf = [0; limits::PATH_MAX];
|
||||
let len = Sys::fpath(dirfd, &mut buf)?;
|
||||
// SAFETY: fpath checks then copies valid UTF8.
|
||||
let dir = unsafe { str::from_utf8_unchecked(&buf[..len]) };
|
||||
|
||||
canonicalize_using_cwd(Some(dir), path).ok_or(Errno(EBADF))?
|
||||
};
|
||||
let path = CString::new(path).map_err(|_| Errno(ENOENT))?;
|
||||
|
||||
// TODO:
|
||||
// * Switch open to openat.
|
||||
File::open(path.as_c_str().into(), oflags)
|
||||
}
|
||||
|
||||
+2
-1
@@ -135,11 +135,12 @@ EXPECT_NAMES=\
|
||||
unistd/fsync \
|
||||
unistd/ftruncate \
|
||||
unistd/getopt \
|
||||
unistd/getopt_long \
|
||||
unistd/pipe \
|
||||
unistd/readlinkat \
|
||||
unistd/rmdir \
|
||||
unistd/sleep \
|
||||
unistd/swab \
|
||||
unistd/getopt_long \
|
||||
unistd/write \
|
||||
wchar/fgetwc \
|
||||
wchar/fwide \
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
__attribute__((nonnull))
|
||||
static int run_test(
|
||||
int dir,
|
||||
const char name[],
|
||||
char buf[PATH_MAX],
|
||||
const char expected[]
|
||||
) {
|
||||
if (readlinkat(dir, name, buf, PATH_MAX) == -1) {
|
||||
perror("readlinkat");
|
||||
return -1;
|
||||
}
|
||||
if (strncmp(expected, buf, PATH_MAX) != 0) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"readlinkat mismatch\n\tExpected: %s\n\tActual: %s\n",
|
||||
expected,
|
||||
buf
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int status = EXIT_FAILURE;
|
||||
|
||||
char template[] = "/tmp/rlatest.XXXXXX";
|
||||
if (!mkdtemp(template)) {
|
||||
perror("mkdtemp");
|
||||
goto bye;
|
||||
}
|
||||
int dir = open(template, O_DIRECTORY);
|
||||
if (dir == -1) {
|
||||
perror("open");
|
||||
goto rmtempdir;
|
||||
}
|
||||
|
||||
// Set up file and link.
|
||||
size_t len = sizeof(template) - 1;
|
||||
const char file_name[] = "miku";
|
||||
char file_path[PATH_MAX] = {0};
|
||||
memcpy(file_path, template, len);
|
||||
file_path[len] = '/';
|
||||
memcpy(&file_path[len + 1], file_name, sizeof(file_name));
|
||||
int file = open(file_path, O_CREAT | O_WRONLY);
|
||||
if (file == -1) {
|
||||
perror("open");
|
||||
goto close_dir;
|
||||
}
|
||||
|
||||
// Writing a sentinel message to the target file is useful in case
|
||||
// readlinkat reads the file instead of the link. If that happens,
|
||||
// it will self-evident instead of printing an empty string.
|
||||
// (n.b. this totally happened to me so I KNOW it's useful)
|
||||
const char msg[] = "readlinkat read the file instead of the link...oops";
|
||||
const ssize_t msg_len = sizeof(msg);
|
||||
assert(msg_len < PATH_MAX);
|
||||
if (write(file, msg, msg_len) < msg_len) {
|
||||
perror("write");
|
||||
goto rmfiles;
|
||||
}
|
||||
if (close(file) == -1) {
|
||||
perror("close");
|
||||
goto rmfiles;
|
||||
}
|
||||
file = -1;
|
||||
|
||||
const char link_name[] = "link";
|
||||
char link_path[PATH_MAX] = {0};
|
||||
memcpy(link_path, template, len);
|
||||
link_path[len] = '/';
|
||||
memcpy(&link_path[len + 1], link_name, len);
|
||||
if (symlink(file_path, link_path) == -1) {
|
||||
perror("symlink");
|
||||
goto rmfiles;
|
||||
}
|
||||
|
||||
// Relative path
|
||||
char buf[PATH_MAX] = {0};
|
||||
if (run_test(dir, link_name, buf, file_path) == -1) {
|
||||
fputs("Context: Basic test (relative path)\n", stderr);
|
||||
goto rmfiles;
|
||||
}
|
||||
|
||||
// Absolute path
|
||||
memset(buf, 0, PATH_MAX);
|
||||
if (run_test(dir, link_path, buf, file_path) == -1) {
|
||||
fputs("Context: Absolute path\n", stderr);
|
||||
goto rmfiles;
|
||||
}
|
||||
|
||||
// AT_FDCWD
|
||||
memset(buf, 0, PATH_MAX);
|
||||
char old_cwd[PATH_MAX] = {0};
|
||||
if (!getcwd(old_cwd, PATH_MAX)) {
|
||||
perror("getcwd");
|
||||
goto rmfiles;
|
||||
}
|
||||
if (chdir(template) == -1) {
|
||||
perror("chdir");
|
||||
goto rmfiles;
|
||||
}
|
||||
if (run_test(AT_FDCWD, link_name, buf, file_path) == -1) {
|
||||
fputs("Context: AT_FDCWD\n", stderr);
|
||||
goto rmfiles;
|
||||
}
|
||||
if (chdir(old_cwd) == -1) {
|
||||
perror("chdir");
|
||||
goto rmfiles;
|
||||
}
|
||||
|
||||
// Not a dir
|
||||
memset(buf, 0, PATH_MAX);
|
||||
file = open(file_path, O_PATH);
|
||||
if (file == -1) {
|
||||
perror("open");
|
||||
goto rmfiles;
|
||||
}
|
||||
if (readlinkat(file, "", buf, PATH_MAX) != -1) {
|
||||
fputs("Context: Using a file for dirfd should fail\n", stderr);
|
||||
fprintf(stderr, "readlinkat wrote this into buf: %s\n", buf);
|
||||
goto close_file;
|
||||
}
|
||||
|
||||
status = EXIT_SUCCESS;
|
||||
close_file:
|
||||
close(file);
|
||||
rmfiles:
|
||||
unlink(file_path);
|
||||
unlink(link_path);
|
||||
close_dir:
|
||||
close(dir);
|
||||
rmtempdir:
|
||||
rmdir(template);
|
||||
bye:
|
||||
return status;
|
||||
}
|
||||
Reference in New Issue
Block a user