From 23817fc65910893b231c7b415b9ed2e9363983ab Mon Sep 17 00:00:00 2001 From: LI Qing Date: Tue, 3 Mar 2020 07:59:06 +0000 Subject: [PATCH] Add fstatat and openat system calls --- src/libos/src/fs/file_ops/access.rs | 8 +- src/libos/src/fs/file_ops/dirfd.rs | 40 ++++++ src/libos/src/fs/file_ops/mod.rs | 10 +- src/libos/src/fs/file_ops/open.rs | 26 +++- src/libos/src/fs/file_ops/stat.rs | 38 +++++- src/libos/src/fs/mod.rs | 1 + src/libos/src/fs/syscalls.rs | 39 ++++-- src/libos/src/syscall/mod.rs | 7 + test/Makefile | 4 +- test/open/Makefile | 5 + test/open/main.c | 111 ++++++++++++++++ test/stat/Makefile | 5 + test/stat/main.c | 196 ++++++++++++++++++++++++++++ 13 files changed, 461 insertions(+), 29 deletions(-) create mode 100644 src/libos/src/fs/file_ops/dirfd.rs create mode 100644 test/open/Makefile create mode 100644 test/open/main.c create mode 100644 test/stat/Makefile create mode 100644 test/stat/main.c diff --git a/src/libos/src/fs/file_ops/access.rs b/src/libos/src/fs/file_ops/access.rs index 5140096b..90de53af 100644 --- a/src/libos/src/fs/file_ops/access.rs +++ b/src/libos/src/fs/file_ops/access.rs @@ -27,10 +27,8 @@ impl AccessibilityCheckFlags { } } -pub const AT_FDCWD: i32 = -100; - pub fn do_faccessat( - dirfd: Option, + dirfd: DirFd, path: &str, mode: AccessibilityCheckMode, flags: AccessibilityCheckFlags, @@ -41,8 +39,8 @@ pub fn do_faccessat( ); match dirfd { // TODO: handle dirfd - Some(dirfd) => return_errno!(ENOSYS, "cannot accept dirfd"), - None => do_access(path, mode), + DirFd::Fd(dirfd) => return_errno!(ENOSYS, "cannot accept dirfd"), + DirFd::Cwd => do_access(path, mode), } } diff --git a/src/libos/src/fs/file_ops/dirfd.rs b/src/libos/src/fs/file_ops/dirfd.rs new file mode 100644 index 00000000..4ad21360 --- /dev/null +++ b/src/libos/src/fs/file_ops/dirfd.rs @@ -0,0 +1,40 @@ +use super::*; + +pub const AT_FDCWD: i32 = -100; + +#[derive(Debug)] +pub enum DirFd { + Cwd, + Fd(FileDesc), +} + +impl DirFd { + pub fn from_i32(fd: i32) -> Result { + let dirfd = if fd >= 0 { + DirFd::Fd(fd as FileDesc) + } else if fd == AT_FDCWD { + DirFd::Cwd + } else { + return_errno!(EINVAL, "invalid dirfd"); + }; + Ok(dirfd) + } +} + +// Get the absolute path of directory +pub fn get_dir_path(dirfd: FileDesc) -> Result { + let dir_path = { + let current_ref = process::get_current(); + let proc = current_ref.lock().unwrap(); + let file_ref = proc.get_files().lock().unwrap().get(dirfd)?; + if let Ok(inode_file) = file_ref.as_inode_file() { + if inode_file.metadata()?.type_ != FileType::Dir { + return_errno!(ENOTDIR, "not a directory"); + } + inode_file.get_abs_path().to_owned() + } else { + return_errno!(EBADF, "not an inode file"); + } + }; + Ok(dir_path) +} diff --git a/src/libos/src/fs/file_ops/mod.rs b/src/libos/src/fs/file_ops/mod.rs index aaad1bb2..fbe14111 100644 --- a/src/libos/src/fs/file_ops/mod.rs +++ b/src/libos/src/fs/file_ops/mod.rs @@ -2,12 +2,11 @@ use super::dev_fs::{DevNull, DevRandom, DevSgx, DevZero}; use super::*; use process::Process; -pub use self::access::{ - do_access, do_faccessat, AccessibilityCheckFlags, AccessibilityCheckMode, AT_FDCWD, -}; +pub use self::access::{do_access, do_faccessat, AccessibilityCheckFlags, AccessibilityCheckMode}; pub use self::chdir::do_chdir; pub use self::close::do_close; pub use self::dirent::do_getdents64; +pub use self::dirfd::{get_dir_path, DirFd}; pub use self::dup::{do_dup, do_dup2, do_dup3}; pub use self::fcntl::{do_fcntl, FcntlCmd}; pub use self::file_flags::{AccessMode, CreationFlags, StatusFlags}; @@ -17,12 +16,12 @@ pub use self::ioctl::{do_ioctl, IoctlCmd, StructuredIoctlArgType, StructuredIoct pub use self::link::do_link; pub use self::lseek::do_lseek; pub use self::mkdir::do_mkdir; -pub use self::open::do_open; +pub use self::open::do_openat; pub use self::read::{do_pread, do_read, do_readv}; pub use self::rename::do_rename; pub use self::rmdir::do_rmdir; pub use self::sendfile::do_sendfile; -pub use self::stat::{do_fstat, do_lstat, do_stat, Stat}; +pub use self::stat::{do_fstat, do_fstatat, do_lstat, Stat, StatFlags}; pub use self::symlink::do_readlink; pub use self::truncate::{do_ftruncate, do_truncate}; pub use self::unlink::do_unlink; @@ -32,6 +31,7 @@ mod access; mod chdir; mod close; mod dirent; +mod dirfd; mod dup; mod fcntl; mod file_flags; diff --git a/src/libos/src/fs/file_ops/open.rs b/src/libos/src/fs/file_ops/open.rs index d7474d44..46cd741d 100644 --- a/src/libos/src/fs/file_ops/open.rs +++ b/src/libos/src/fs/file_ops/open.rs @@ -1,11 +1,6 @@ use super::*; -pub fn do_open(path: &str, flags: u32, mode: u32) -> Result { - info!( - "open: path: {:?}, flags: {:#o}, mode: {:#o}", - path, flags, mode - ); - +fn do_open(path: &str, flags: u32, mode: u32) -> Result { let current_ref = process::get_current(); let mut proc = current_ref.lock().unwrap(); @@ -21,3 +16,22 @@ pub fn do_open(path: &str, flags: u32, mode: u32) -> Result { }; Ok(fd) } + +pub fn do_openat(dirfd: DirFd, path: &str, flags: u32, mode: u32) -> Result { + info!( + "openat: dirfd: {:?}, path: {:?}, flags: {:#o}, mode: {:#o}", + dirfd, path, flags, mode + ); + if Path::new(path).is_absolute() { + // Path is absolute, so dirfd is ignored + return Ok(do_open(path, flags, mode)?); + } + let path = match dirfd { + DirFd::Fd(dirfd) => { + let dir_path = get_dir_path(dirfd)?; + dir_path + "/" + path + } + DirFd::Cwd => path.to_owned(), + }; + do_open(&path, flags, mode) +} diff --git a/src/libos/src/fs/file_ops/stat.rs b/src/libos/src/fs/file_ops/stat.rs index 0d030284..d1abf74f 100644 --- a/src/libos/src/fs/file_ops/stat.rs +++ b/src/libos/src/fs/file_ops/stat.rs @@ -105,6 +105,14 @@ impl StatMode { } } +bitflags! { + pub struct StatFlags: u32 { + const AT_EMPTY_PATH = 1 << 12; + const AT_NO_AUTOMOUNT = 1 << 11; + const AT_SYMLINK_NOFOLLOW = 1 << 8; + } +} + impl From for Stat { fn from(info: Metadata) -> Self { Stat { @@ -126,7 +134,7 @@ impl From for Stat { } } -pub fn do_stat(path: &str) -> Result { +fn do_stat(path: &str) -> Result { warn!("stat is partial implemented as lstat"); do_lstat(path) } @@ -149,3 +157,31 @@ pub fn do_lstat(path: &str) -> Result { let stat = Stat::from(inode.metadata()?); Ok(stat) } + +pub fn do_fstatat(dirfd: DirFd, path: &str, flags: StatFlags) -> Result { + info!( + "fstatat: dirfd: {:?}, path: {:?}, flags: {:?}", + dirfd, path, flags + ); + if path.len() == 0 && !flags.contains(StatFlags::AT_EMPTY_PATH) { + return_errno!(ENOENT, "path is an empty string"); + } + if Path::new(path).is_absolute() { + // Path is absolute, so dirfd is ignored + return Ok(do_stat(path)?); + } + match dirfd { + DirFd::Fd(dirfd) => { + if path.len() == 0 { + // Path is an empty string, and the flags contiains AT_EMPTY_PATH, + // so the behavior of fstatat() is similar to that of fstat(). + do_fstat(dirfd) + } else { + let dir_path = get_dir_path(dirfd)?; + let path = dir_path + "/" + path; + do_stat(&path) + } + } + DirFd::Cwd => do_stat(path), + } +} diff --git a/src/libos/src/fs/mod.rs b/src/libos/src/fs/mod.rs index 88e64d17..7de987c7 100644 --- a/src/libos/src/fs/mod.rs +++ b/src/libos/src/fs/mod.rs @@ -7,6 +7,7 @@ use std::any::Any; use std::fmt; use std::io::{Read, Seek, SeekFrom, Write}; use std::mem::MaybeUninit; +use std::path::Path; pub use self::dev_fs::AsDevRandom; pub use self::file::{File, FileRef}; diff --git a/src/libos/src/fs/syscalls.rs b/src/libos/src/fs/syscalls.rs index 6fce9fdb..8ed5436c 100644 --- a/src/libos/src/fs/syscalls.rs +++ b/src/libos/src/fs/syscalls.rs @@ -1,5 +1,7 @@ use super::file_ops; -use super::file_ops::{AccessibilityCheckFlags, AccessibilityCheckMode, FcntlCmd, AT_FDCWD}; +use super::file_ops::{ + AccessibilityCheckFlags, AccessibilityCheckMode, DirFd, FcntlCmd, StatFlags, +}; use super::fs_ops; use super::*; use util::mem_util::from_user; @@ -14,7 +16,16 @@ pub fn do_open(path: *const i8, flags: u32, mode: u32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - let fd = file_ops::do_open(&path, flags, mode)?; + let fd = file_ops::do_openat(DirFd::Cwd, &path, flags, mode)?; + Ok(fd as isize) +} + +pub fn do_openat(dirfd: i32, path: *const i8, flags: u32, mode: u32) -> Result { + let dirfd = DirFd::from_i32(dirfd)?; + let path = from_user::clone_cstring_safely(path)? + .to_string_lossy() + .into_owned(); + let fd = file_ops::do_openat(dirfd, &path, flags, mode)?; Ok(fd as isize) } @@ -115,7 +126,7 @@ pub fn do_stat(path: *const i8, stat_buf: *mut Stat) -> Result { .into_owned(); from_user::check_mut_ptr(stat_buf)?; - let stat = file_ops::do_stat(&path)?; + let stat = file_ops::do_fstatat(DirFd::Cwd, &path, StatFlags::empty())?; unsafe { stat_buf.write(stat); } @@ -145,6 +156,20 @@ pub fn do_lstat(path: *const i8, stat_buf: *mut Stat) -> Result { Ok(0) } +pub fn do_fstatat(dirfd: i32, path: *const i8, stat_buf: *mut Stat, flags: u32) -> Result { + let dirfd = DirFd::from_i32(dirfd)?; + let path = from_user::clone_cstring_safely(path)? + .to_string_lossy() + .into_owned(); + from_user::check_mut_ptr(stat_buf)?; + let flags = StatFlags::from_bits_truncate(flags); + let stat = file_ops::do_fstatat(dirfd, &path, flags)?; + unsafe { + stat_buf.write(stat); + } + Ok(0) +} + pub fn do_access(path: *const i8, mode: u32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() @@ -154,13 +179,7 @@ pub fn do_access(path: *const i8, mode: u32) -> Result { } pub fn do_faccessat(dirfd: i32, path: *const i8, mode: u32, flags: u32) -> Result { - let dirfd = if dirfd >= 0 { - Some(dirfd as FileDesc) - } else if dirfd == AT_FDCWD { - None - } else { - return_errno!(EINVAL, "invalid dirfd"); - }; + let dirfd = DirFd::from_i32(dirfd)?; let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index e93e85b6..ac2d9de8 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -60,6 +60,7 @@ pub extern "C" fn dispatch_syscall( let ret = match syscall_num { // file SysOpen => fs::do_open(arg0 as *const i8, arg1 as u32, arg2 as u32), + SysOpenat => fs::do_openat(arg0 as i32, arg1 as *const i8, arg2 as u32, arg3 as u32), SysClose => fs::do_close(arg0 as FileDesc), SysRead => fs::do_read(arg0 as FileDesc, arg1 as *mut u8, arg2 as usize), SysWrite => fs::do_write(arg0 as FileDesc, arg1 as *const u8, arg2 as usize), @@ -80,6 +81,12 @@ pub extern "C" fn dispatch_syscall( SysStat => fs::do_stat(arg0 as *const i8, arg1 as *mut Stat), SysFstat => fs::do_fstat(arg0 as FileDesc, arg1 as *mut Stat), SysLstat => fs::do_lstat(arg0 as *const i8, arg1 as *mut Stat), + SysNewfstatat => fs::do_fstatat( + arg0 as i32, + arg1 as *const i8, + arg2 as *mut Stat, + arg3 as u32, + ), SysAccess => fs::do_access(arg0 as *const i8, arg1 as u32), SysFaccessat => fs::do_faccessat(arg0 as i32, arg1 as *const i8, arg2 as u32, arg3 as u32), SysLseek => fs::do_lseek(arg0 as FileDesc, arg1 as off_t, arg2 as i32), diff --git a/test/Makefile b/test/Makefile index 24e3def6..dbd867a8 100644 --- a/test/Makefile +++ b/test/Makefile @@ -11,8 +11,8 @@ endif # Dependencies: need to be compiled but not to run by any Makefile target TEST_DEPS := client data_sink # Tests: need to be compiled and run by test-% target -TESTS := empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \ - truncate readdir mkdir link symlink tls pthread uname rlimit server \ +TESTS ?= empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \ + truncate readdir mkdir open stat link symlink tls pthread uname rlimit server \ server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \ ioctl fcntl # Benchmarks: need to be compiled and run by bench-% target diff --git a/test/open/Makefile b/test/open/Makefile new file mode 100644 index 00000000..9e1b6dec --- /dev/null +++ b/test/open/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/open/main.c b/test/open/main.c new file mode 100644 index 00000000..48f92567 --- /dev/null +++ b/test/open/main.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include "test.h" + +// ============================================================================ +// Helper function +// ============================================================================ + +static int remove_file(const char *file_path) { + int ret; + + ret = unlink(file_path); + if (ret < 0) { + THROW_ERROR("failed to unlink the created file"); + } + return 0; +} + +// ============================================================================ +// Test cases for open +// ============================================================================ + +static int __test_open(const char *file_path, int flags, int mode) { + int fd = open(file_path, flags, mode); + if (fd < 0) { + THROW_ERROR("failed to open a file"); + } + close(fd); + return 0; +} + +static int __test_openat_with_abs_path(const char *file_path, int flags, int mode) { + int fd = openat(AT_FDCWD, file_path, flags, mode); + if (fd < 0) { + THROW_ERROR("failed to openat a file with abs path"); + } + close(fd); + return 0; +} + +static int __test_openat_with_dirfd(const char *file_path, int flags, int mode) { + char dir_buf[128] = { 0 }; + char base_buf[128] = { 0 }; + char *dir_name, *file_name; + int dirfd, fd, ret; + + ret = snprintf(dir_buf, sizeof(dir_buf), "%s", file_path); + if (ret >= sizeof(dir_buf) || ret < 0) { + THROW_ERROR("failed to copy file path to the dir buffer"); + } + ret = snprintf(base_buf, sizeof(base_buf), "%s", file_path); + if (ret >= sizeof(base_buf) || ret < 0) { + THROW_ERROR("failed to copy file path to the base buffer"); + } + dir_name = dirname(dir_buf); + file_name = basename(base_buf); + dirfd = open(dir_name, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + fd = openat(dirfd, file_name, flags, mode); + if (fd < 0) { + close(dirfd); + THROW_ERROR("failed to openat a file with dirfd"); + } + close(dirfd); + close(fd); + return 0; +} + +typedef int(*test_open_func_t)(const char *, int, int); + +static int test_open_framework(test_open_func_t fn) { + const char *file_path = "/root/test_filesystem_open.txt"; + int flags = O_RDONLY | O_CREAT| O_TRUNC; + int mode = 00666; + + if (fn(file_path, flags, mode) < 0) + return -1; + if (remove_file(file_path) < 0) + return -1; + return 0; +} + +static int test_open() { + return test_open_framework(__test_open); +} + +static int test_openat_with_abs_path() { + return test_open_framework(__test_openat_with_abs_path); +} + +static int test_openat_with_dirfd() { + return test_open_framework(__test_openat_with_dirfd); +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_open), + TEST_CASE(test_openat_with_abs_path), + TEST_CASE(test_openat_with_dirfd), +}; + +int main(int argc, const char *argv[]) { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +} diff --git a/test/stat/Makefile b/test/stat/Makefile new file mode 100644 index 00000000..9e1b6dec --- /dev/null +++ b/test/stat/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/stat/main.c b/test/stat/main.c new file mode 100644 index 00000000..fda24512 --- /dev/null +++ b/test/stat/main.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include +#include +#include "test.h" + +// ============================================================================ +// Helper function +// ============================================================================ + +static int create_file(const char *file_path) { + int fd; + int flags = O_RDONLY | O_CREAT| O_TRUNC; + int mode = 00666; + + fd = open(file_path, flags, mode); + if (fd < 0) { + THROW_ERROR("failed to create a file"); + } + close(fd); + return 0; +} + +static int remove_file(const char *file_path) { + int ret; + + ret = unlink(file_path); + if (ret < 0) { + THROW_ERROR("failed to unlink the created file"); + } + return 0; +} + +// ============================================================================ +// Test cases for stat +// ============================================================================ + +static int __test_stat(const char *file_path) { + struct stat stat_buf; + int ret; + + ret = stat(file_path, &stat_buf); + if (ret < 0) { + THROW_ERROR("failed to stat file"); + } + return 0; +} + +static int __test_fstat(const char *file_path) { + struct stat stat_buf; + int fd, ret; + int flags = O_RDONLY; + + fd = open(file_path, flags); + if (fd < 0) { + THROW_ERROR("failed to open file"); + } + ret = fstat(fd, &stat_buf); + if (ret < 0) { + close(fd); + THROW_ERROR("failed to fstat file"); + } + close(fd); + return 0; +} + +static int __test_lstat(const char *file_path) { + struct stat stat_buf; + int ret; + + ret = lstat(file_path, &stat_buf); + if (ret < 0) { + THROW_ERROR("failed to lstat file"); + } + return 0; +} + +static int __test_fstatat_with_abs_path(const char *file_path) { + struct stat stat_buf; + int ret; + + ret = fstatat(AT_FDCWD, file_path, &stat_buf, 0); + if (ret < 0) { + THROW_ERROR("failed to fstatat file with abs path"); + } + return 0; +} + +static int __test_fstatat_with_empty_path(const char *file_path) { + struct stat stat_buf; + int fd, ret; + + ret = fstatat(AT_FDCWD, "", &stat_buf, 0); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("fstatat with empty path should return ENOENT"); + } + + fd = open(file_path, O_RDONLY); + if (fd < 0) { + THROW_ERROR("failed to open file"); + } + ret = fstatat(fd, "", &stat_buf, AT_EMPTY_PATH); + if (ret < 0) { + close(fd); + THROW_ERROR("failed to fstatat empty path with AT_EMPTY_PATH flags"); + } + close(fd); + return 0; +} + +static int __test_fstatat_with_dirfd(const char *file_path) { + struct stat stat_buf; + char dir_buf[128] = { 0 }; + char base_buf[128] = { 0 }; + char *dir_name, *file_name; + int dirfd, ret; + + ret = snprintf(dir_buf, sizeof(dir_buf), "%s", file_path); + if (ret >= sizeof(dir_buf) || ret < 0) { + THROW_ERROR("failed to copy file path to the dir buffer"); + } + ret = snprintf(base_buf, sizeof(base_buf), "%s", file_path); + if (ret >= sizeof(base_buf) || ret < 0) { + THROW_ERROR("failed to copy file path to the base buffer"); + } + dir_name = dirname(dir_buf); + file_name = basename(base_buf); + dirfd = open(dir_name, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + ret = fstatat(dirfd, file_name, &stat_buf, 0); + if (ret < 0) { + close(dirfd); + THROW_ERROR("failed to fstatat file with dirfd"); + } + close(dirfd); + return 0; +} + +typedef int(*test_stat_func_t)(const char *); + +static int test_stat_framework(test_stat_func_t fn) { + const char *file_path = "/root/test_filesystem_stat.txt"; + + if (create_file(file_path) < 0) + return -1; + if (fn(file_path) < 0) + return -1; + if (remove_file(file_path) < 0) + return -1; + return 0; +} + +static int test_stat() { + return test_stat_framework(__test_stat); +} + +static int test_fstat() { + return test_stat_framework(__test_fstat); +} + +static int test_lstat() { + return test_stat_framework(__test_lstat); +} + +static int test_fstatat_with_abs_path() { + return test_stat_framework(__test_fstatat_with_abs_path); +} + +static int test_fstatat_with_empty_path() { + return test_stat_framework(__test_fstatat_with_empty_path); +} + +static int test_fstatat_with_dirfd() { + return test_stat_framework(__test_fstatat_with_dirfd); +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_stat), + TEST_CASE(test_fstat), + TEST_CASE(test_lstat), + TEST_CASE(test_fstatat_with_abs_path), + TEST_CASE(test_fstatat_with_empty_path), + TEST_CASE(test_fstatat_with_dirfd), +}; + +int main(int argc, const char *argv[]) { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +}