diff --git a/src/header/fcntl/redox.rs b/src/header/fcntl/redox.rs index f7ff4db45c..ee006a8850 100644 --- a/src/header/fcntl/redox.rs +++ b/src/header/fcntl/redox.rs @@ -30,3 +30,5 @@ pub const O_NDELAY: c_int = O_NONBLOCK; // Flags for capability based "at" functions pub const AT_FDCWD: c_int = -100; +pub const AT_SYMLINK_NOFOLLOW: c_int = 0x0200; +pub const AT_EMPTY_PATH: c_int = 0x4000; diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 48c0df314e..b83431b2ef 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -25,7 +25,7 @@ use crate::{ EBADF, EBADFD, EBADR, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM, ENOSYS, EOPNOTSUPP, EPERM, ERANGE, }, - fcntl::{self, AT_FDCWD, O_RDONLY}, + fcntl::{self, AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, O_NOFOLLOW, O_PATH, O_RDONLY}, limits, sys_mman::{MAP_ANONYMOUS, MAP_FAILED, PROT_READ, PROT_WRITE}, sys_random, @@ -292,19 +292,16 @@ impl Pal for Sys { ) -> Result<()> { let path = match CStr::from_nullable_ptr(path).and_then(|cs| str::from_utf8(cs.to_bytes()).ok()) { - // TODO: AT_EMPTY_PATH Some(path) if !path.is_empty() => Ok(path), - None | Some(_) => Err(Errno(ENOENT)), + _ if flags & AT_EMPTY_PATH == AT_EMPTY_PATH => return Sys::fstat(fildes, buf), + _ => Err(Errno(ENOENT)), }?; - // TODO: We need an AT_SYMLINK_NOFOLLOW constant for Redox. Unlike AT_FDCWD, it varies - // across OSes, so I don't want to define it here myself. - // O_NOFOLLOW may be incorrect too (see the rename.c unit test). - // let oflags = if flags & AT_SYMLINK_NOFOLLOW { - // O_RDONLY | O_NOFOLLOW - // } else { - // O_RDONLY - // }; - let oflags = O_RDONLY; + + 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 @@ -328,7 +325,6 @@ impl Pal for Sys { let path = CString::new(path).map_err(|_| Errno(ENOENT))?; // TODO: - // * If AT_SYMLINK_NOFOLLOW is set, call lstat instead. // * Switch open to openat. let file = File::open(path.as_c_str().into(), oflags)?; Sys::fstat(*file, buf) diff --git a/tests/sys_stat/fstatat.c b/tests/sys_stat/fstatat.c index 99a6a118c2..296af3eebf 100644 --- a/tests/sys_stat/fstatat.c +++ b/tests/sys_stat/fstatat.c @@ -1,3 +1,5 @@ +#define _GNU_SOURCE // Linux for CI + #include #include #include @@ -49,7 +51,7 @@ static const char* mktestfile( return path; } -// Create a link, `dir/name`, to `target` and return its path. +// Create a symlink, `dir/name`, to `target` and return its path. __attribute__((nonnull)) static const char* mktestlink( const char dir[], @@ -66,8 +68,8 @@ static const char* mktestlink( strcat(path, "/"); strcat(path, name); - if (link(target, path) == -1) { - perror("link"); + if (symlink(target, path) == -1) { + perror("symlink"); free(path); return NULL; } @@ -81,14 +83,15 @@ static int run_test( int flags, const char name[], const char path[], - size_t expected + size_t expected, + mode_t mode ) { struct stat stat = {0}; - if (fstatat(fd, name, &stat, flags) == -1) { perror("fstatat"); return -1; } + if ((size_t) stat.st_size != expected) { fprintf( stderr, @@ -99,6 +102,32 @@ static int run_test( ); return -1; } + if ((stat.st_mode & S_IFMT) != mode) { + fprintf( + stderr, + "fstatat invalid mode\n\tFile: %s\n", + path + ); + return -1; + } + + // We create the temp dirs/files therefore we should own them. + uid_t uid = getuid(); + gid_t gid = getgid(); + if (stat.st_uid != uid || stat.st_gid != gid) { + fputs("fstatat invalid UID or GID", stderr); + fprintf( + stderr, + "\n\tExpected UID: %u\n\tActual UID: %u\n", + uid, stat.st_uid + ); + fprintf( + stderr, + "\n\tExpected GID: %u\n\tActual GID: %u\n", + gid, stat.st_gid + ); + + } return 0; } @@ -157,24 +186,37 @@ int main(void) { // fstatat works (basic) size_t len_cont_A = strlen(cont_A); - if (run_test(dir_a_fd, 0, name, file_A, len_cont_A) == -1) { + if (run_test(dir_a_fd, 0, name, file_A, len_cont_A, S_IFREG) == -1) { goto close_dir_a_fd; } // fstatat follows symlinks (same dir) - if (run_test(dir_a_fd, 0, link_A_name, link_A, len_cont_A) == -1) { + if (run_test(dir_a_fd, 0, link_A_name, link_A, len_cont_A, S_IFREG) == -1) { fprintf(stderr, "Context: link %s -> %s\n", link_A, file_A); goto close_dir_a_fd; } // fstatat follows symlinks (to diff dir) size_t len_cont_B = strlen(cont_B); - if (run_test(dir_a_fd, 0, link_B_name, link_B, len_cont_B) == -1) { + if (run_test(dir_a_fd, 0, link_B_name, link_B, len_cont_B, S_IFREG) == -1) { fprintf(stderr, "Context: link %s -> %s\n", link_B, file_B); goto close_dir_a_fd; } - // TODO: AT_SYMLINK_NOFOLLOW (no Redox support) + // AT_SYMLINK_NOFOLLOW + if ( + run_test( + dir_a_fd, + AT_SYMLINK_NOFOLLOW, + link_A_name, + link_A, + strlen(file_A), + S_IFLNK + ) == -1 + ) { + fprintf(stderr, "Context: link %s (no follow)\n", link_A); + goto close_dir_a_fd; + } // TODO: O_SEARCH (no Redox support) @@ -188,7 +230,7 @@ int main(void) { perror("chdir"); goto close_dir_a_fd; } - if (run_test(AT_FDCWD, 0, name, "./", len_cont_A) == -1) { + if (run_test(AT_FDCWD, 0, name, "./", len_cont_A, S_IFREG) == -1) { fputs("Context: AT_FDCWD\n", stderr); goto close_dir_a_fd; } @@ -198,7 +240,7 @@ int main(void) { } // Absolute path - if (run_test(dir_a_fd, 0, file_A, file_A, len_cont_A) == -1) { + if (run_test(dir_a_fd, 0, file_A, file_A, len_cont_A, S_IFREG) == -1) { fprintf(stderr, "Context: absolute path %s\n", file_A); goto close_dir_a_fd; } @@ -228,7 +270,16 @@ int main(void) { } char rel_nested[sizeof(name) + 5] = "../"; strcat(rel_nested, name); - if (run_test(nested_fd, 0, rel_nested, rel_nested, len_cont_A) == -1) { + if ( + run_test( + nested_fd, + 0, + rel_nested, + rel_nested, + len_cont_A, + S_IFREG + ) == -1 + ) { fprintf( stderr, "Context: relative path from %s to ../%s\n", @@ -240,13 +291,37 @@ int main(void) { // TODO: AT_RESOLVE_BENEATH - // TODO: AT_EMPTY_PATH + // AT_EMPTY_PATH (empty path) + struct stat stat = {0}; + if (fstatat(dir_a_fd, "", &stat, AT_EMPTY_PATH) == -1) { + fputs("Context: AT_EMPTY_PATH with empty path\n", stderr); + goto close_dir_a_fd; + } + if ((stat.st_mode & S_IFMT) != S_IFDIR) { + fputs("Context: AT_EMPTY_PATH should stat dir (empty path)\n", stderr); + goto close_dir_a_fd; + } + + // AT_EMPTY_PATH (non-empty path) + if ( + run_test( + dir_a_fd, + AT_EMPTY_PATH, + link_A_name, + link_A, + len_cont_A, + S_IFREG + ) == -1 + ) { + fputs("Context: AT_EMPTY_PATH with path should stat path\n", stderr); + goto close_dir_a_fd; + } // TODO: Swapped directories resolves correctly // Failure conditions: - // Empty path - struct stat stat = {0}; + // Empty path without AT_EMPTY_PATH + stat = (struct stat) {0}; if (fstatat(dir_a_fd, "", &stat, 0) == 0) { fputs("Context: empty path should fail\n", stderr); goto close_dir_a_fd;