From 292fc28340ab63d118a3b9f4f8cdf80774420c8b Mon Sep 17 00:00:00 2001 From: LI Qing Date: Tue, 11 Aug 2020 11:21:04 +0800 Subject: [PATCH] Add "*at()" system calls The syscalls implemented in this submission are as follows: fchmodat, fchownat, linkat, mkdirat, renameat, readlinkat, unlinkat --- src/libos/src/fs/file_ops/access.rs | 26 +-- src/libos/src/fs/file_ops/chmod.rs | 24 ++- src/libos/src/fs/file_ops/chown.rs | 37 ++-- src/libos/src/fs/file_ops/dirfd.rs | 38 ---- src/libos/src/fs/file_ops/fspath.rs | 107 +++++++++++ src/libos/src/fs/file_ops/link.rs | 22 ++- src/libos/src/fs/file_ops/mkdir.rs | 6 +- src/libos/src/fs/file_ops/mod.rs | 31 ++-- src/libos/src/fs/file_ops/open.rs | 25 +-- src/libos/src/fs/file_ops/rename.rs | 9 +- src/libos/src/fs/file_ops/stat.rs | 52 +----- src/libos/src/fs/file_ops/symlink.rs | 45 ++--- src/libos/src/fs/file_ops/unlink.rs | 19 +- src/libos/src/fs/syscalls.rs | 181 ++++++++++--------- src/libos/src/syscall/mod.rs | 25 +-- test/Makefile | 2 +- test/access/main.c | 22 +-- test/chmod/main.c | 42 ++++- test/chown/main.c | 84 ++++++++- test/fcntl/main.c | 4 +- test/file/main.c | 20 +-- test/fs_perms/main.c | 5 +- test/hostfs/main.c | 30 +--- test/hostfs/sample.txt | 1 - test/include/test_fs.h | 59 ++++++ test/link/main.c | 258 +++++++++++++++++++-------- test/mkdir/main.c | 238 ++++++++++++++++++------ test/open/main.c | 22 +-- test/readdir/main.c | 5 +- test/rename/Makefile | 5 + test/rename/main.c | 195 ++++++++++++++++++++ test/stat/main.c | 20 +-- test/symlink/main.c | 128 ++++++++++--- 33 files changed, 1225 insertions(+), 562 deletions(-) delete mode 100644 src/libos/src/fs/file_ops/dirfd.rs create mode 100644 src/libos/src/fs/file_ops/fspath.rs delete mode 100644 test/hostfs/sample.txt create mode 100644 test/include/test_fs.h create mode 100644 test/rename/Makefile create mode 100644 test/rename/main.c diff --git a/src/libos/src/fs/file_ops/access.rs b/src/libos/src/fs/file_ops/access.rs index 0a28759d..263e89e5 100644 --- a/src/libos/src/fs/file_ops/access.rs +++ b/src/libos/src/fs/file_ops/access.rs @@ -38,37 +38,23 @@ impl AccessibilityCheckFlags { } pub fn do_faccessat( - dirfd: DirFd, - path: &str, + fs_path: &FsPath, mode: AccessibilityCheckMode, flags: AccessibilityCheckFlags, ) -> Result<()> { debug!( - "faccessat: dirfd: {:?}, path: {:?}, mode: {:?}, flags: {:?}", - dirfd, path, mode, flags + "faccessat: fs_path: {:?}, mode: {:?}, flags: {:?}", + fs_path, mode, flags ); - let path = match dirfd { - DirFd::Fd(dirfd) => { - let dir_path = get_dir_path(dirfd)?; - dir_path + "/" + path - } - DirFd::Cwd => path.to_owned(), - }; - do_access(&path, mode, flags) -} -fn do_access( - path: &str, - mode: AccessibilityCheckMode, - flags: AccessibilityCheckFlags, -) -> Result<()> { let inode = { + let path = fs_path.to_abs_path()?; let current = current!(); let fs = current.fs().lock().unwrap(); if flags.contains(AccessibilityCheckFlags::AT_SYMLINK_NOFOLLOW) { - fs.lookup_inode_no_follow(path)? + fs.lookup_inode_no_follow(&path)? } else { - fs.lookup_inode(path)? + fs.lookup_inode(&path)? } }; if mode.test_for_exist() { diff --git a/src/libos/src/fs/file_ops/chmod.rs b/src/libos/src/fs/file_ops/chmod.rs index 3ee349c2..d31f4eef 100644 --- a/src/libos/src/fs/file_ops/chmod.rs +++ b/src/libos/src/fs/file_ops/chmod.rs @@ -55,12 +55,27 @@ impl FileMode { } } -pub fn do_chmod(path: &str, mode: FileMode) -> Result<()> { - debug!("chmod: path: {:?}, mode: {:?}", path, mode); +bitflags! { + pub struct ChmodFlags: i32 { + const AT_SYMLINK_NOFOLLOW = 0x100; + } +} + +pub fn do_fchmodat(fs_path: &FsPath, mode: FileMode, flags: ChmodFlags) -> Result<()> { + debug!( + "fchmodat: fs_path: {:?}, mode: {:#o}, flags: {:?}", + fs_path, mode, flags + ); + let inode = { + let path = fs_path.to_abs_path()?; let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode(path)? + if flags.contains(ChmodFlags::AT_SYMLINK_NOFOLLOW) { + fs.lookup_inode_no_follow(&path)? + } else { + fs.lookup_inode(&path)? + } }; let mut info = inode.metadata()?; info.mode = mode.bits(); @@ -69,7 +84,8 @@ pub fn do_chmod(path: &str, mode: FileMode) -> Result<()> { } pub fn do_fchmod(fd: FileDesc, mode: FileMode) -> Result<()> { - debug!("fchmod: fd: {}, mode: {:?}", fd, mode); + debug!("fchmod: fd: {}, mode: {:#o}", fd, mode); + let file_ref = current!().file(fd)?; let mut info = file_ref.metadata()?; info.mode = mode.bits(); diff --git a/src/libos/src/fs/file_ops/chown.rs b/src/libos/src/fs/file_ops/chown.rs index 3a9d95fd..c269ec8c 100644 --- a/src/libos/src/fs/file_ops/chown.rs +++ b/src/libos/src/fs/file_ops/chown.rs @@ -1,11 +1,27 @@ use super::*; -pub fn do_chown(path: &str, uid: u32, gid: u32) -> Result<()> { - debug!("chown: path: {:?}, uid: {}, gid: {}", path, uid, gid); +bitflags! { + pub struct ChownFlags: i32 { + const AT_EMPTY_PATH = 0x1000; + const AT_SYMLINK_NOFOLLOW = 0x100; + } +} + +pub fn do_fchownat(fs_path: &FsPath, uid: u32, gid: u32, flags: ChownFlags) -> Result<()> { + debug!( + "fchownat: fs_path: {:?}, uid: {}, gid: {}, flags: {:?}", + fs_path, uid, gid, flags + ); + let inode = { + let path = fs_path.to_abs_path()?; let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode(path)? + if flags.contains(ChownFlags::AT_SYMLINK_NOFOLLOW) { + fs.lookup_inode_no_follow(&path)? + } else { + fs.lookup_inode(&path)? + } }; let mut info = inode.metadata()?; info.uid = uid as usize; @@ -16,6 +32,7 @@ pub fn do_chown(path: &str, uid: u32, gid: u32) -> Result<()> { pub fn do_fchown(fd: FileDesc, uid: u32, gid: u32) -> Result<()> { debug!("fchown: fd: {}, uid: {}, gid: {}", fd, uid, gid); + let file_ref = current!().file(fd)?; let mut info = file_ref.metadata()?; info.uid = uid as usize; @@ -23,17 +40,3 @@ pub fn do_fchown(fd: FileDesc, uid: u32, gid: u32) -> Result<()> { file_ref.set_metadata(&info)?; Ok(()) } - -pub fn do_lchown(path: &str, uid: u32, gid: u32) -> Result<()> { - debug!("lchown: path: {:?}, uid: {}, gid: {}", path, uid, gid); - let inode = { - let current = current!(); - let fs = current.fs().lock().unwrap(); - fs.lookup_inode_no_follow(path)? - }; - let mut info = inode.metadata()?; - info.uid = uid as usize; - info.gid = gid as usize; - inode.set_metadata(&info)?; - Ok(()) -} diff --git a/src/libos/src/fs/file_ops/dirfd.rs b/src/libos/src/fs/file_ops/dirfd.rs deleted file mode 100644 index 8199db05..00000000 --- a/src/libos/src/fs/file_ops/dirfd.rs +++ /dev/null @@ -1,38 +0,0 @@ -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 file_ref = current!().file(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/fspath.rs b/src/libos/src/fs/file_ops/fspath.rs new file mode 100644 index 00000000..30c31548 --- /dev/null +++ b/src/libos/src/fs/file_ops/fspath.rs @@ -0,0 +1,107 @@ +use super::*; + +pub const AT_FDCWD: i32 = -100; + +pub struct FsPath<'a> { + inner: FsPathInner<'a>, +} + +#[derive(Debug)] +enum FsPathInner<'a> { + // absolute path + Absolute(&'a str), + // path is relative to Cwd + CwdRelative(&'a str), + // Cwd + Cwd, + // path is relative to DirFd + FdRelative(&'a str, FileDesc), + // Fd + Fd(FileDesc), +} + +impl<'a> FsPath<'a> { + /// Construct a FsPath + pub fn new(path: &'a str, fd: i32, allow_empty_path: bool) -> Result { + let fs_path_inner = if Path::new(path).is_absolute() { + FsPathInner::Absolute(path) + } else if fd >= 0 { + if path.is_empty() { + if !allow_empty_path { + return_errno!(ENOENT, "path is an empty string"); + } + FsPathInner::Fd(fd as FileDesc) + } else { + let file_ref = current!().file(fd as FileDesc)?; + let inode_file = file_ref + .as_inode_file() + .map_err(|_| errno!(EBADF, "dirfd is not an inode file"))?; + if inode_file.metadata()?.type_ != FileType::Dir { + return_errno!(ENOTDIR, "dirfd is not a directory"); + } + FsPathInner::FdRelative(path, fd as FileDesc) + } + } else if fd == AT_FDCWD { + if path.is_empty() { + if !allow_empty_path { + return_errno!(ENOENT, "path is an empty string"); + } + FsPathInner::Cwd + } else { + FsPathInner::CwdRelative(path) + } + } else { + return_errno!(EINVAL, "invalid dirfd number"); + }; + + Ok(FsPath { + inner: fs_path_inner, + }) + } + + /// Convert to absolute path + pub fn to_abs_path(&self) -> Result { + let abs_path = match &self.inner { + FsPathInner::Absolute(path) => (*path).to_owned(), + FsPathInner::FdRelative(path, dirfd) => { + let dir_path = get_abs_path_by_fd(*dirfd)?; + if dir_path.ends_with("/") { + dir_path + path + } else { + dir_path + "/" + path + } + } + FsPathInner::Fd(fd) => get_abs_path_by_fd(*fd)?, + FsPathInner::CwdRelative(path) => { + let current = current!(); + let fs = current.fs().lock().unwrap(); + fs.convert_to_abs_path(path) + } + FsPathInner::Cwd => { + let current = current!(); + let fs = current.fs().lock().unwrap(); + fs.cwd().to_owned() + } + }; + Ok(abs_path) + } +} + +impl<'a> Debug for FsPath<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "FsPath {{ {:?} }}", self.inner) + } +} + +/// Get the absolute path by file descriptor +fn get_abs_path_by_fd(fd: FileDesc) -> Result { + let path = { + let file_ref = current!().file(fd)?; + if let Ok(inode_file) = file_ref.as_inode_file() { + inode_file.get_abs_path().to_owned() + } else { + return_errno!(EBADF, "not an inode file"); + } + }; + Ok(path) +} diff --git a/src/libos/src/fs/file_ops/link.rs b/src/libos/src/fs/file_ops/link.rs index f8ce893a..d8a81176 100644 --- a/src/libos/src/fs/file_ops/link.rs +++ b/src/libos/src/fs/file_ops/link.rs @@ -1,13 +1,29 @@ use super::*; -pub fn do_link(oldpath: &str, newpath: &str) -> Result<()> { - debug!("link: oldpath: {:?}, newpath: {:?}", oldpath, newpath); +bitflags! { + pub struct LinkFlags: i32 { + const AT_EMPTY_PATH = 0x1000; + const AT_SYMLINK_FOLLOW = 0x400; + } +} +pub fn do_linkat(old_fs_path: &FsPath, new_fs_path: &FsPath, flags: LinkFlags) -> Result<()> { + debug!( + "linkat: old_fs_path: {:?}, new_fs_path: {:?}, flags:{:?}", + old_fs_path, new_fs_path, flags + ); + + let newpath = new_fs_path.to_abs_path()?; let (new_dir_path, new_file_name) = split_path(&newpath); let (inode, new_dir_inode) = { + let oldpath = old_fs_path.to_abs_path()?; let current = current!(); let fs = current.fs().lock().unwrap(); - let inode = fs.lookup_inode_no_follow(&oldpath)?; + let inode = if flags.contains(LinkFlags::AT_SYMLINK_FOLLOW) { + fs.lookup_inode(&oldpath)? + } else { + fs.lookup_inode_no_follow(&oldpath)? + }; let new_dir_inode = fs.lookup_inode(new_dir_path)?; (inode, new_dir_inode) }; diff --git a/src/libos/src/fs/file_ops/mkdir.rs b/src/libos/src/fs/file_ops/mkdir.rs index b5d9daae..11ffc2b4 100644 --- a/src/libos/src/fs/file_ops/mkdir.rs +++ b/src/libos/src/fs/file_ops/mkdir.rs @@ -1,9 +1,9 @@ use super::*; -pub fn do_mkdir(path: &str, mode: usize) -> Result<()> { - // TODO: check pathname - debug!("mkdir: path: {:?}, mode: {:#o}", path, mode); +pub fn do_mkdirat(fs_path: &FsPath, mode: usize) -> Result<()> { + debug!("mkdirat: fs_path: {:?}, mode: {:#o}", fs_path, mode); + let path = fs_path.to_abs_path()?; let (dir_path, file_name) = split_path(&path); let inode = { let current = current!(); diff --git a/src/libos/src/fs/file_ops/mod.rs b/src/libos/src/fs/file_ops/mod.rs index b02aec06..0171443a 100644 --- a/src/libos/src/fs/file_ops/mod.rs +++ b/src/libos/src/fs/file_ops/mod.rs @@ -3,32 +3,32 @@ use super::*; use process::Process; pub use self::access::{do_faccessat, AccessibilityCheckFlags, AccessibilityCheckMode}; -pub use self::chmod::{do_chmod, do_fchmod, FileMode}; -pub use self::chown::{do_chown, do_fchown, do_lchown}; +pub use self::chmod::{do_fchmod, do_fchmodat, ChmodFlags, FileMode}; +pub use self::chown::{do_fchown, do_fchownat, ChownFlags}; 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}; pub use self::flock::{Flock, FlockType}; +pub use self::fspath::{FsPath, AT_FDCWD}; pub use self::fsync::{do_fdatasync, do_fsync}; pub use self::ioctl::{ do_ioctl, occlum_ocall_ioctl, BuiltinIoctlNum, IfConf, IoctlCmd, StructuredIoctlArgType, StructuredIoctlNum, }; -pub use self::link::do_link; +pub use self::link::{do_linkat, LinkFlags}; pub use self::lseek::do_lseek; -pub use self::mkdir::do_mkdir; +pub use self::mkdir::do_mkdirat; 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::rename::do_renameat; pub use self::rmdir::do_rmdir; pub use self::sendfile::do_sendfile; -pub use self::stat::{do_fstat, do_fstatat, do_lstat, Stat, StatFlags}; -pub use self::symlink::{do_readlink, do_symlinkat}; +pub use self::stat::{do_fstat, do_fstatat, Stat, StatFlags}; +pub use self::symlink::{do_readlinkat, do_symlinkat}; pub use self::truncate::{do_ftruncate, do_truncate}; -pub use self::unlink::do_unlink; +pub use self::unlink::{do_unlinkat, UnlinkFlags}; pub use self::write::{do_pwrite, do_write, do_writev}; mod access; @@ -36,11 +36,11 @@ mod chmod; mod chown; mod close; mod dirent; -mod dirfd; mod dup; mod fcntl; mod file_flags; mod flock; +mod fspath; mod fsync; mod ioctl; mod link; @@ -56,14 +56,3 @@ mod symlink; mod truncate; mod unlink; mod write; - -/// Split a `path` str to `(base_path, file_name)` -pub fn split_path(path: &str) -> (&str, &str) { - let mut split = path.trim_end_matches('/').rsplitn(2, '/'); - let file_name = split.next().unwrap(); - let mut dir_path = split.next().unwrap_or("."); - if dir_path == "" { - dir_path = "/"; - } - (dir_path, file_name) -} diff --git a/src/libos/src/fs/file_ops/open.rs b/src/libos/src/fs/file_ops/open.rs index 73bf0795..6eae2d13 100644 --- a/src/libos/src/fs/file_ops/open.rs +++ b/src/libos/src/fs/file_ops/open.rs @@ -1,10 +1,16 @@ use super::*; -fn do_open(path: &str, flags: u32, mode: u32) -> Result { +pub fn do_openat(fs_path: &FsPath, flags: u32, mode: u32) -> Result { + debug!( + "openat: fs_path: {:?}, flags: {:#o}, mode: {:#o}", + fs_path, flags, mode + ); + + let path = fs_path.to_abs_path()?; let current = current!(); let fs = current.fs().lock().unwrap(); - let file = fs.open_file(path, flags, mode)?; + let file = fs.open_file(&path, flags, mode)?; let file_ref: Arc> = Arc::new(file); let fd = { @@ -13,18 +19,3 @@ 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 { - debug!( - "openat: dirfd: {:?}, path: {:?}, flags: {:#o}, mode: {:#o}", - dirfd, 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/rename.rs b/src/libos/src/fs/file_ops/rename.rs index e227225f..a673ab20 100644 --- a/src/libos/src/fs/file_ops/rename.rs +++ b/src/libos/src/fs/file_ops/rename.rs @@ -1,8 +1,13 @@ use super::*; -pub fn do_rename(oldpath: &str, newpath: &str) -> Result<()> { - debug!("rename: oldpath: {:?}, newpath: {:?}", oldpath, newpath); +pub fn do_renameat(old_fs_path: &FsPath, new_fs_path: &FsPath) -> Result<()> { + debug!( + "renameat: old_fs_path: {:?}, new_fs_path: {:?}", + old_fs_path, new_fs_path + ); + let oldpath = old_fs_path.to_abs_path()?; + let newpath = new_fs_path.to_abs_path()?; let current = current!(); let fs = current.fs().lock().unwrap(); diff --git a/src/libos/src/fs/file_ops/stat.rs b/src/libos/src/fs/file_ops/stat.rs index 2f5abc49..48ce754d 100644 --- a/src/libos/src/fs/file_ops/stat.rs +++ b/src/libos/src/fs/file_ops/stat.rs @@ -134,17 +134,6 @@ impl From for Stat { } } -fn do_stat(path: &str) -> Result { - debug!("stat: path: {}", path); - let inode = { - let current = current!(); - let fs = current.fs().lock().unwrap(); - fs.lookup_inode(&path)? - }; - let stat = Stat::from(inode.metadata()?); - Ok(stat) -} - pub fn do_fstat(fd: u32) -> Result { debug!("fstat: fd: {}", fd); let file_ref = current!().file(fd as FileDesc)?; @@ -152,42 +141,19 @@ pub fn do_fstat(fd: u32) -> Result { Ok(stat) } -pub fn do_lstat(path: &str) -> Result { - debug!("lstat: path: {}", path); +pub fn do_fstatat(fs_path: &FsPath, flags: StatFlags) -> Result { + debug!("fstatat: fs_path: {:?}, flags: {:?}", fs_path, flags); + let inode = { + let path = fs_path.to_abs_path()?; let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode_no_follow(&path)? + if flags.contains(StatFlags::AT_SYMLINK_NOFOLLOW) { + fs.lookup_inode_no_follow(&path)? + } else { + fs.lookup_inode(&path)? + } }; let stat = Stat::from(inode.metadata()?); Ok(stat) } - -pub fn do_fstatat(dirfd: DirFd, path: &str, flags: StatFlags) -> Result { - debug!( - "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"); - } - 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; - if !flags.contains(StatFlags::AT_SYMLINK_NOFOLLOW) { - do_stat(&path) - } else { - do_lstat(&path) - } - } - } - DirFd::Cwd if !flags.contains(StatFlags::AT_SYMLINK_NOFOLLOW) => do_stat(path), - DirFd::Cwd => do_lstat(path), - } -} diff --git a/src/libos/src/fs/file_ops/symlink.rs b/src/libos/src/fs/file_ops/symlink.rs index 8d756040..7dd59c62 100644 --- a/src/libos/src/fs/file_ops/symlink.rs +++ b/src/libos/src/fs/file_ops/symlink.rs @@ -1,15 +1,17 @@ use super::*; -pub fn do_readlink(path: &str, buf: &mut [u8]) -> Result { - debug!("readlink: path: {:?}", path); +pub fn do_readlinkat(fs_path: &FsPath, buf: &mut [u8]) -> Result { + debug!("readlinkat: fs_path: {:?}", fs_path); + + let path = fs_path.to_abs_path()?; let file_path = { - if path == "/proc/self/exe" { + if path.as_str() == "/proc/self/exe" { current!().process().exec_path().to_owned() } else if path.starts_with("/proc/self/fd") { let fd = path .trim_start_matches("/proc/self/fd/") .parse::() - .map_err(|e| errno!(EBADF, "Invalid file descriptor"))?; + .map_err(|_| errno!(EBADF, "Invalid file descriptor"))?; let file_ref = current!().file(fd)?; if let Ok(inode_file) = file_ref.as_inode_file() { inode_file.get_abs_path().to_owned() @@ -21,7 +23,7 @@ pub fn do_readlink(path: &str, buf: &mut [u8]) -> Result { let inode = { let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode_no_follow(path)? + fs.lookup_inode_no_follow(&path)? }; if inode.metadata()?.type_ != FileType::SymLink { return_errno!(EINVAL, "not a symbolic link"); @@ -38,7 +40,17 @@ pub fn do_readlink(path: &str, buf: &mut [u8]) -> Result { Ok(len) } -fn do_symlink(target: &str, link_path: &str) -> Result { +pub fn do_symlinkat(target: &str, link_path: &FsPath) -> Result { + debug!("symlinkat: target: {}, link_path: {:?}", target, link_path); + + if target.is_empty() { + return_errno!(ENOENT, "target is an empty string"); + } + if target.len() > PATH_MAX { + return_errno!(ENAMETOOLONG, "target is too long"); + } + + let link_path = link_path.to_abs_path()?; let (dir_path, link_name) = split_path(&link_path); let dir_inode = { let current = current!(); @@ -54,24 +66,3 @@ fn do_symlink(target: &str, link_path: &str) -> Result { link_inode.write_at(0, data)?; Ok(0) } - -pub fn do_symlinkat(target: &str, new_dirfd: DirFd, link_path: &str) -> Result { - debug!( - "symlinkat: target: {:?}, new_dirfd: {:?}, link_path: {:?}", - target, new_dirfd, link_path - ); - if target.is_empty() || link_path.is_empty() { - return_errno!(ENOENT, "target or linkpath is an empty string"); - } - if target.len() > PATH_MAX || link_path.len() > PATH_MAX { - return_errno!(ENAMETOOLONG, "target or linkpath is too long"); - } - let link_path = match new_dirfd { - DirFd::Fd(dirfd) => { - let dir_path = get_dir_path(dirfd)?; - dir_path + "/" + link_path - } - DirFd::Cwd => link_path.to_owned(), - }; - do_symlink(target, &link_path) -} diff --git a/src/libos/src/fs/file_ops/unlink.rs b/src/libos/src/fs/file_ops/unlink.rs index 80271760..bafcbe56 100644 --- a/src/libos/src/fs/file_ops/unlink.rs +++ b/src/libos/src/fs/file_ops/unlink.rs @@ -1,8 +1,12 @@ use super::*; -pub fn do_unlink(path: &str) -> Result<()> { - debug!("unlink: path: {:?}", path); +bitflags! { + pub struct UnlinkFlags: i32 { + const AT_REMOVEDIR = 0x200; + } +} +fn do_unlink(path: &str) -> Result<()> { let (dir_path, file_name) = split_path(&path); let dir_inode = { let current = current!(); @@ -21,3 +25,14 @@ pub fn do_unlink(path: &str) -> Result<()> { dir_inode.unlink(file_name)?; Ok(()) } + +pub fn do_unlinkat(fs_path: &FsPath, flags: UnlinkFlags) -> Result<()> { + debug!("unlinkat: fs_path: {:?}, flags: {:?}", fs_path, flags); + + let abs_path = fs_path.to_abs_path()?; + if flags.contains(UnlinkFlags::AT_REMOVEDIR) { + super::do_rmdir(&abs_path) + } else { + do_unlink(&abs_path) + } +} diff --git a/src/libos/src/fs/syscalls.rs b/src/libos/src/fs/syscalls.rs index ab694114..170d3b14 100644 --- a/src/libos/src/fs/syscalls.rs +++ b/src/libos/src/fs/syscalls.rs @@ -1,7 +1,8 @@ use super::event_file::EventCreationFlags; use super::file_ops; use super::file_ops::{ - AccessibilityCheckFlags, AccessibilityCheckMode, DirFd, FcntlCmd, StatFlags, + AccessibilityCheckFlags, AccessibilityCheckMode, ChmodFlags, ChownFlags, FcntlCmd, FsPath, + LinkFlags, StatFlags, UnlinkFlags, AT_FDCWD, }; use super::fs_ops; use super::*; @@ -35,24 +36,15 @@ pub fn do_eventfd2(init_val: u32, flags: i32) -> Result { } 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_openat(DirFd::Cwd, &path, flags, mode)?; - Ok(fd as isize) + self::do_openat(AT_FDCWD, path, flags, mode) } pub fn do_openat(dirfd: i32, path: *const i8, flags: u32, mode: u32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - let dirfd = if Path::new(&path).is_absolute() { - // Path is absolute, dirfd is treated as Cwd - DirFd::Cwd - } else { - DirFd::from_i32(dirfd)? - }; - let fd = file_ops::do_openat(dirfd, &path, flags, mode)?; + let fs_path = FsPath::new(&path, dirfd, false)?; + let fd = file_ops::do_openat(&fs_path, flags, mode)?; Ok(fd as isize) } @@ -147,19 +139,6 @@ pub fn do_pwrite(fd: FileDesc, buf: *const u8, size: usize, offset: off_t) -> Re Ok(len as isize) } -pub fn do_stat(path: *const i8, stat_buf: *mut Stat) -> Result { - let path = from_user::clone_cstring_safely(path)? - .to_string_lossy() - .into_owned(); - from_user::check_mut_ptr(stat_buf)?; - - let stat = file_ops::do_fstatat(DirFd::Cwd, &path, StatFlags::empty())?; - unsafe { - stat_buf.write(stat); - } - Ok(0) -} - pub fn do_fstat(fd: FileDesc, stat_buf: *mut Stat) -> Result { from_user::check_mut_ptr(stat_buf)?; @@ -170,32 +149,27 @@ pub fn do_fstat(fd: FileDesc, stat_buf: *mut Stat) -> Result { Ok(0) } -pub fn do_lstat(path: *const i8, stat_buf: *mut Stat) -> Result { - let path = from_user::clone_cstring_safely(path)? - .to_string_lossy() - .into_owned(); - from_user::check_mut_ptr(stat_buf)?; +pub fn do_stat(path: *const i8, stat_buf: *mut Stat) -> Result { + self::do_fstatat(AT_FDCWD, path, stat_buf, 0) +} - let stat = file_ops::do_lstat(&path)?; - unsafe { - stat_buf.write(stat); - } - Ok(0) +pub fn do_lstat(path: *const i8, stat_buf: *mut Stat) -> Result { + self::do_fstatat( + AT_FDCWD, + path, + stat_buf, + StatFlags::AT_SYMLINK_NOFOLLOW.bits(), + ) } pub fn do_fstatat(dirfd: i32, path: *const i8, stat_buf: *mut Stat, flags: u32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - let dirfd = if Path::new(&path).is_absolute() { - // Path is absolute, dirfd is treated as Cwd - DirFd::Cwd - } else { - DirFd::from_i32(dirfd)? - }; + let flags = StatFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?; + let fs_path = FsPath::new(&path, dirfd, flags.contains(StatFlags::AT_EMPTY_PATH))?; from_user::check_mut_ptr(stat_buf)?; - let flags = StatFlags::from_bits_truncate(flags); - let stat = file_ops::do_fstatat(dirfd, &path, flags)?; + let stat = file_ops::do_fstatat(&fs_path, flags)?; unsafe { stat_buf.write(stat); } @@ -203,27 +177,17 @@ pub fn do_fstatat(dirfd: i32, path: *const i8, stat_buf: *mut Stat, flags: u32) } pub fn do_access(path: *const i8, mode: u32) -> Result { - let path = from_user::clone_cstring_safely(path)? - .to_string_lossy() - .into_owned(); - let mode = AccessibilityCheckMode::from_u32(mode)?; - let flags = AccessibilityCheckFlags::empty(); - file_ops::do_faccessat(DirFd::Cwd, &path, mode, flags).map(|_| 0) + self::do_faccessat(AT_FDCWD, path, mode, 0) } pub fn do_faccessat(dirfd: i32, path: *const i8, mode: u32, flags: u32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - let dirfd = if Path::new(&path).is_absolute() { - // Path is absolute, dirfd is treated as Cwd - DirFd::Cwd - } else { - DirFd::from_i32(dirfd)? - }; + let fs_path = FsPath::new(&path, dirfd, false)?; let mode = AccessibilityCheckMode::from_u32(mode)?; let flags = AccessibilityCheckFlags::from_u32(flags)?; - file_ops::do_faccessat(dirfd, &path, mode, flags).map(|_| 0) + file_ops::do_faccessat(&fs_path, mode, flags).map(|_| 0) } pub fn do_lseek(fd: FileDesc, offset: off_t, whence: i32) -> Result { @@ -346,21 +310,37 @@ pub fn do_getcwd(buf_ptr: *mut u8, size: usize) -> Result { } pub fn do_rename(oldpath: *const i8, newpath: *const i8) -> Result { + self::do_renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath) +} + +pub fn do_renameat( + olddirfd: i32, + oldpath: *const i8, + newdirfd: i32, + newpath: *const i8, +) -> Result { let oldpath = from_user::clone_cstring_safely(oldpath)? .to_string_lossy() .into_owned(); let newpath = from_user::clone_cstring_safely(newpath)? .to_string_lossy() .into_owned(); - file_ops::do_rename(&oldpath, &newpath)?; + let old_fs_path = FsPath::new(&oldpath, olddirfd, false)?; + let new_fs_path = FsPath::new(&newpath, newdirfd, false)?; + file_ops::do_renameat(&old_fs_path, &new_fs_path)?; Ok(0) } pub fn do_mkdir(path: *const i8, mode: usize) -> Result { + self::do_mkdirat(AT_FDCWD, path, mode) +} + +pub fn do_mkdirat(dirfd: i32, path: *const i8, mode: usize) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - file_ops::do_mkdir(&path, mode)?; + let fs_path = FsPath::new(&path, dirfd, false)?; + file_ops::do_mkdirat(&fs_path, mode)?; Ok(0) } @@ -373,25 +353,49 @@ pub fn do_rmdir(path: *const i8) -> Result { } pub fn do_link(oldpath: *const i8, newpath: *const i8) -> Result { + self::do_linkat(AT_FDCWD, oldpath, AT_FDCWD, newpath, 0) +} + +pub fn do_linkat( + olddirfd: i32, + oldpath: *const i8, + newdirfd: i32, + newpath: *const i8, + flags: i32, +) -> Result { let oldpath = from_user::clone_cstring_safely(oldpath)? .to_string_lossy() .into_owned(); let newpath = from_user::clone_cstring_safely(newpath)? .to_string_lossy() .into_owned(); - file_ops::do_link(&oldpath, &newpath)?; + let flags = LinkFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?; + let old_fs_path = FsPath::new(&oldpath, olddirfd, flags.contains(LinkFlags::AT_EMPTY_PATH))?; + let new_fs_path = FsPath::new(&newpath, newdirfd, false)?; + file_ops::do_linkat(&old_fs_path, &new_fs_path, flags)?; Ok(0) } pub fn do_unlink(path: *const i8) -> Result { + self::do_unlinkat(AT_FDCWD, path, 0) +} + +pub fn do_unlinkat(dirfd: i32, path: *const i8, flags: i32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - file_ops::do_unlink(&path)?; + let fs_path = FsPath::new(&path, dirfd, false)?; + let flags = + UnlinkFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flag value"))?; + file_ops::do_unlinkat(&fs_path, flags)?; Ok(0) } pub fn do_readlink(path: *const i8, buf: *mut u8, size: usize) -> Result { + self::do_readlinkat(AT_FDCWD, path, buf, size) +} + +pub fn do_readlinkat(dirfd: i32, path: *const i8, buf: *mut u8, size: usize) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); @@ -399,19 +403,13 @@ pub fn do_readlink(path: *const i8, buf: *mut u8, size: usize) -> Result from_user::check_array(buf, size)?; unsafe { std::slice::from_raw_parts_mut(buf, size) } }; - let len = file_ops::do_readlink(&path, buf)?; + let fs_path = FsPath::new(&path, dirfd, false)?; + let len = file_ops::do_readlinkat(&fs_path, buf)?; Ok(len as isize) } pub fn do_symlink(target: *const i8, link_path: *const i8) -> Result { - let target = from_user::clone_cstring_safely(target)? - .to_string_lossy() - .into_owned(); - let link_path = from_user::clone_cstring_safely(link_path)? - .to_string_lossy() - .into_owned(); - file_ops::do_symlinkat(&target, DirFd::Cwd, &link_path)?; - Ok(0) + self::do_symlinkat(target, AT_FDCWD, link_path) } pub fn do_symlinkat(target: *const i8, new_dirfd: i32, link_path: *const i8) -> Result { @@ -421,23 +419,13 @@ pub fn do_symlinkat(target: *const i8, new_dirfd: i32, link_path: *const i8) -> let link_path = from_user::clone_cstring_safely(link_path)? .to_string_lossy() .into_owned(); - let new_dirfd = if Path::new(&link_path).is_absolute() { - // Link path is absolute, new_dirfd is treated as Cwd - DirFd::Cwd - } else { - DirFd::from_i32(new_dirfd)? - }; - file_ops::do_symlinkat(&target, new_dirfd, &link_path)?; + let fs_path = FsPath::new(&link_path, new_dirfd, false)?; + file_ops::do_symlinkat(&target, &fs_path)?; Ok(0) } pub fn do_chmod(path: *const i8, mode: u16) -> Result { - let path = from_user::clone_cstring_safely(path)? - .to_string_lossy() - .into_owned(); - let mode = FileMode::from_bits_truncate(mode); - file_ops::do_chmod(&path, mode)?; - Ok(0) + self::do_fchmodat(AT_FDCWD, path, mode, 0) } pub fn do_fchmod(fd: FileDesc, mode: u16) -> Result { @@ -446,27 +434,46 @@ pub fn do_fchmod(fd: FileDesc, mode: u16) -> Result { Ok(0) } -pub fn do_chown(path: *const i8, uid: u32, gid: u32) -> Result { +pub fn do_fchmodat(dirfd: i32, path: *const i8, mode: u16, flags: i32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - file_ops::do_chown(&path, uid, gid)?; + let mode = FileMode::from_bits_truncate(mode); + let fs_path = FsPath::new(&path, dirfd, false)?; + let flags = ChmodFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?; + file_ops::do_fchmodat(&fs_path, mode, flags)?; Ok(0) } +pub fn do_chown(path: *const i8, uid: u32, gid: u32) -> Result { + self::do_fchownat(AT_FDCWD, path, uid, gid, 0) +} + pub fn do_fchown(fd: FileDesc, uid: u32, gid: u32) -> Result { file_ops::do_fchown(fd, uid, gid)?; Ok(0) } -pub fn do_lchown(path: *const i8, uid: u32, gid: u32) -> Result { +pub fn do_fchownat(dirfd: i32, path: *const i8, uid: u32, gid: u32, flags: i32) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() .into_owned(); - file_ops::do_lchown(&path, uid, gid)?; + let flags = ChownFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?; + let fs_path = FsPath::new(&path, dirfd, flags.contains(ChownFlags::AT_EMPTY_PATH))?; + file_ops::do_fchownat(&fs_path, uid, gid, flags)?; Ok(0) } +pub fn do_lchown(path: *const i8, uid: u32, gid: u32) -> Result { + self::do_fchownat( + AT_FDCWD, + path, + uid, + gid, + ChownFlags::AT_SYMLINK_NOFOLLOW.bits(), + ) +} + pub fn do_sendfile( out_fd: FileDesc, in_fd: FileDesc, diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index 326b6fb6..bc8fcd95 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -23,11 +23,12 @@ use util::mem_util::from_user::*; use crate::exception::do_handle_exception; use crate::fs::{ do_access, do_chdir, do_chmod, do_chown, do_close, do_dup, do_dup2, do_dup3, do_eventfd, - do_eventfd2, do_faccessat, do_fchmod, do_fchown, do_fcntl, do_fdatasync, do_fstat, do_fstatat, - do_fsync, do_ftruncate, do_getcwd, do_getdents64, do_ioctl, do_lchown, do_link, do_lseek, - do_lstat, do_mkdir, do_open, do_openat, do_pipe, do_pipe2, do_pread, do_pwrite, do_read, - do_readlink, do_readv, do_rename, do_rmdir, do_sendfile, do_stat, do_symlink, do_symlinkat, - do_sync, do_truncate, do_unlink, do_write, do_writev, iovec_t, File, FileDesc, FileRef, + do_eventfd2, do_faccessat, do_fchmod, do_fchmodat, do_fchown, do_fchownat, do_fcntl, + do_fdatasync, do_fstat, do_fstatat, do_fsync, do_ftruncate, do_getcwd, do_getdents64, do_ioctl, + do_lchown, do_link, do_linkat, do_lseek, do_lstat, do_mkdir, do_mkdirat, do_open, do_openat, + do_pipe, do_pipe2, do_pread, do_pwrite, do_read, do_readlink, do_readlinkat, do_readv, + do_rename, do_renameat, do_rmdir, do_sendfile, do_stat, do_symlink, do_symlinkat, do_sync, + do_truncate, do_unlink, do_unlinkat, do_write, do_writev, iovec_t, File, FileDesc, FileRef, HostStdioFds, Stat, }; use crate::interrupt::{do_handle_interrupt, sgx_interrupt_info_t}; @@ -341,17 +342,17 @@ macro_rules! process_syscall_table_with_callback { (InotifyRmWatch = 255) => handle_unsupported(), (MigratePages = 256) => handle_unsupported(), (Openat = 257) => do_openat(dirfd: i32, path: *const i8, flags: u32, mode: u32), - (Mkdirat = 258) => handle_unsupported(), + (Mkdirat = 258) => do_mkdirat(dirfd: i32, path: *const i8, mode: usize), (Mknodat = 259) => handle_unsupported(), - (Fchownat = 260) => handle_unsupported(), + (Fchownat = 260) => do_fchownat(dirfd: i32, path: *const i8, uid: u32, gid: u32, flags: i32), (Futimesat = 261) => handle_unsupported(), (Fstatat = 262) => do_fstatat(dirfd: i32, path: *const i8, stat_buf: *mut Stat, flags: u32), - (Unlinkat = 263) => handle_unsupported(), - (Renameat = 264) => handle_unsupported(), - (Linkat = 265) => handle_unsupported(), + (Unlinkat = 263) => do_unlinkat(dirfd: i32, path: *const i8, flags: i32), + (Renameat = 264) => do_renameat(olddirfd: i32, oldpath: *const i8, newdirfd: i32, newpath: *const i8), + (Linkat = 265) => do_linkat(olddirfd: i32, oldpath: *const i8, newdirfd: i32, newpath: *const i8, flags: i32), (Symlinkat = 266) => do_symlinkat(target: *const i8, new_dirfd: i32, link_path: *const i8), - (Readlinkat = 267) => handle_unsupported(), - (Fchmodat = 268) => handle_unsupported(), + (Readlinkat = 267) => do_readlinkat(dirfd: i32, path: *const i8, buf: *mut u8, size: usize), + (Fchmodat = 268) => do_fchmodat(dirfd: i32, path: *const i8, mode: u16, flags: i32), (Faccessat = 269) => do_faccessat(dirfd: i32, path: *const i8, mode: u32, flags: u32), (Pselect6 = 270) => handle_unsupported(), (Ppoll = 271) => handle_unsupported(), diff --git a/test/Makefile b/test/Makefile index 9619acd5..deb7a8b5 100644 --- a/test/Makefile +++ b/test/Makefile @@ -18,7 +18,7 @@ TEST_DEPS := client data_sink TESTS ?= env empty hello_world malloc mmap file fs_perms getpid spawn sched pipe time \ truncate readdir mkdir open stat link symlink chmod chown tls pthread uname rlimit \ server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \ - ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl + ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl rename # Benchmarks: need to be compiled and run by bench-% target BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput diff --git a/test/access/main.c b/test/access/main.c index b76139c8..468098e8 100644 --- a/test/access/main.c +++ b/test/access/main.c @@ -1,10 +1,7 @@ #include #include #include -#include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper function @@ -75,21 +72,14 @@ static int __test_faccessat_with_abs_path(const char *file_path) { } static int __test_faccessat_with_dirfd(const char *file_path) { - char dir_buf[128] = { 0 }; - char base_buf[128] = { 0 }; + char dir_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; char *dir_name, *file_name; - int dirfd, ret; + int dirfd; - 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"); + if (fs_split_path(file_path, dir_buf, &dir_name, base_buf, &file_name) < 0) { + THROW_ERROR("failed to split path"); } - 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"); diff --git a/test/chmod/main.c b/test/chmod/main.c index b7b672ee..a5f8cbdf 100644 --- a/test/chmod/main.c +++ b/test/chmod/main.c @@ -1,9 +1,7 @@ #include #include #include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper function @@ -33,7 +31,7 @@ static int remove_file(const char *file_path) { } // ============================================================================ -// Test cases for stat +// Test cases for chmod // ============================================================================ static int __test_chmod(const char *file_path) { @@ -66,7 +64,6 @@ static int __test_fchmod(const char *file_path) { } ret = fchmod(fd, mode); if (ret < 0) { - close(fd); THROW_ERROR("failed to fchmod file"); } close(fd); @@ -80,6 +77,36 @@ static int __test_fchmod(const char *file_path) { return 0; } +static int __test_fchmodat(const char *file_path) { + struct stat stat_buf; + mode_t mode = 00664; + char dir_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; + char *dir_name, *file_name; + int dirfd, ret; + + if (fs_split_path(file_path, dir_buf, &dir_name, base_buf, &file_name) < 0) { + THROW_ERROR("failed to split path"); + } + dirfd = open(dir_name, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + ret = fchmodat(dirfd, file_name, mode, 0); + if (ret < 0) { + THROW_ERROR("failed to fchmodat file with dirfd"); + } + close(dirfd); + ret = stat(file_path, &stat_buf); + if (ret < 0) { + THROW_ERROR("failed to stat file"); + } + if ((stat_buf.st_mode & 07777) != mode) { + THROW_ERROR("check fchmodat result failed"); + } + return 0; +} + typedef int(*test_chmod_func_t)(const char *); static int test_chmod_framework(test_chmod_func_t fn) { @@ -105,6 +132,10 @@ static int test_fchmod() { return test_chmod_framework(__test_fchmod); } +static int test_fchmodat() { + return test_chmod_framework(__test_fchmodat); +} + // ============================================================================ // Test suite main // ============================================================================ @@ -112,6 +143,7 @@ static int test_fchmod() { static test_case_t test_cases[] = { TEST_CASE(test_chmod), TEST_CASE(test_fchmod), + TEST_CASE(test_fchmodat), }; int main(int argc, const char *argv[]) { diff --git a/test/chown/main.c b/test/chown/main.c index 0f4bba24..9c70132e 100644 --- a/test/chown/main.c +++ b/test/chown/main.c @@ -1,9 +1,7 @@ #include #include #include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper function @@ -33,7 +31,7 @@ static int remove_file(const char *file_path) { } // ============================================================================ -// Test cases for stat +// Test cases for chown // ============================================================================ static int __test_chown(const char *file_path) { @@ -88,7 +86,6 @@ static int __test_fchown(const char *file_path) { } ret = fchown(fd, uid, gid); if (ret < 0) { - close(fd); THROW_ERROR("failed to fchown file"); } close(fd); @@ -102,6 +99,73 @@ static int __test_fchown(const char *file_path) { return 0; } +static int __test_fchownat(const char *file_path) { + struct stat stat_buf; + uid_t uid = 100; + gid_t gid = 1000; + char dir_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; + char *dir_name, *file_name; + int dirfd, ret; + + if (fs_split_path(file_path, dir_buf, &dir_name, base_buf, &file_name) < 0) { + THROW_ERROR("failed to split path"); + } + dirfd = open(dir_name, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + ret = fchownat(dirfd, file_name, uid, gid, 0); + if (ret < 0) { + THROW_ERROR("failed to fchownat file with dirfd"); + } + close(dirfd); + ret = stat(file_path, &stat_buf); + if (ret < 0) { + THROW_ERROR("failed to stat file"); + } + if (stat_buf.st_uid != uid || stat_buf.st_gid != gid) { + THROW_ERROR("check fchownat result failed"); + } + return 0; +} + +static int __test_fchownat_with_empty_path(const char *file_path) { + struct stat stat_buf; + uid_t uid = 100; + gid_t gid = 1000; + char dir_buf[128] = { 0 }; + char *dir_name; + int dirfd, ret; + + if (fs_split_path(file_path, dir_buf, &dir_name, NULL, NULL) < 0) { + THROW_ERROR("failed to split path"); + } + dirfd = open(dir_name, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + + ret = fchownat(dirfd, "", uid, gid, 0); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("fchownat with empty path should return ENOENT"); + } + + ret = fchownat(dirfd, "", uid, gid, AT_EMPTY_PATH); + if (ret < 0) { + THROW_ERROR("failed to fchownat with empty path"); + } + close(dirfd); + ret = stat(dir_name, &stat_buf); + if (ret < 0) { + THROW_ERROR("failed to stat dir"); + } + if (stat_buf.st_uid != uid || stat_buf.st_gid != gid) { + THROW_ERROR("check fchownat result failed"); + } + return 0; +} + typedef int(*test_chown_func_t)(const char *); static int test_chown_framework(test_chown_func_t fn) { @@ -131,6 +195,14 @@ static int test_fchown() { return test_chown_framework(__test_fchown); } +static int test_fchownat() { + return test_chown_framework(__test_fchownat); +} + +static int test_fchownat_with_empty_path() { + return test_chown_framework(__test_fchownat_with_empty_path); +} + // ============================================================================ // Test suite main // ============================================================================ @@ -139,6 +211,8 @@ static test_case_t test_cases[] = { TEST_CASE(test_chown), TEST_CASE(test_lchown), TEST_CASE(test_fchown), + TEST_CASE(test_fchownat), + TEST_CASE(test_fchownat_with_empty_path), }; int main(int argc, const char *argv[]) { diff --git a/test/fcntl/main.c b/test/fcntl/main.c index 7dc149a9..e2db46d7 100644 --- a/test/fcntl/main.c +++ b/test/fcntl/main.c @@ -1,8 +1,6 @@ -#include #include #include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper macro diff --git a/test/file/main.c b/test/file/main.c index d372d4eb..a1860f81 100644 --- a/test/file/main.c +++ b/test/file/main.c @@ -2,10 +2,7 @@ #include #include #include -#include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper function @@ -38,7 +35,6 @@ static int remove_file(const char *file_path) { static int __test_write_read(const char *file_path) { char *write_str = "Hello World\n"; - char read_buf[128] = { 0 }; int fd; fd = open(file_path, O_WRONLY); @@ -49,17 +45,11 @@ static int __test_write_read(const char *file_path) { THROW_ERROR("failed to write"); } close(fd); - fd = open(file_path, O_RDONLY); - if (fd < 0) { - THROW_ERROR("failed to open a file to read"); + + if (fs_check_file_content(file_path, write_str) < 0) { + THROW_ERROR("failed to check file content"); } - if (read(fd, read_buf, sizeof(read_buf)) != strlen(write_str)) { - THROW_ERROR("failed to read"); - } - if (strcmp(write_str, read_buf) != 0) { - THROW_ERROR("the message read from the file is not as it was written"); - } - close(fd); + return 0; } diff --git a/test/fs_perms/main.c b/test/fs_perms/main.c index 16504fd4..1b8188d6 100644 --- a/test/fs_perms/main.c +++ b/test/fs_perms/main.c @@ -1,10 +1,7 @@ #include #include #include -#include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper macros diff --git a/test/hostfs/main.c b/test/hostfs/main.c index 9adfed1e..611c00fc 100644 --- a/test/hostfs/main.c +++ b/test/hostfs/main.c @@ -2,12 +2,8 @@ #include #include #include -#include -#include -#include #include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper function @@ -40,7 +36,6 @@ static int remove_file(const char *file_path) { static int __test_write_read(const char *file_path) { char *write_str = "Write to hostfs successfully!"; - char read_buf[128] = { 0 }; int fd; fd = open(file_path, O_WRONLY); @@ -51,17 +46,10 @@ static int __test_write_read(const char *file_path) { THROW_ERROR("failed to write to the file"); } close(fd); - fd = open(file_path, O_RDONLY); - if (fd < 0) { - THROW_ERROR("failed to open a file to read"); + + if (fs_check_file_content(file_path, write_str) < 0) { + THROW_ERROR("failed to check file content"); } - if (read(fd, read_buf, sizeof(read_buf)) != strlen(write_str)) { - THROW_ERROR("failed to read to the file"); - } - if (strcmp(write_str, read_buf) != 0) { - THROW_ERROR("the message read from the file is not as it was written"); - } - close(fd); return 0; } @@ -91,17 +79,13 @@ static int __test_rename(const char *file_path) { static int __test_readdir(const char *file_path) { struct dirent *dp; DIR *dirp; - char base_buf[128] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; char *base_name; bool found = false; - int ret; - 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"); + if (fs_split_path(file_path, NULL, NULL, base_buf, &base_name) < 0) { + THROW_ERROR("failed to split path"); } - base_name = basename(base_buf); - dirp = opendir("/host"); if (dirp == NULL) { THROW_ERROR("failed to open host directory"); diff --git a/test/hostfs/sample.txt b/test/hostfs/sample.txt deleted file mode 100644 index 750bc261..00000000 --- a/test/hostfs/sample.txt +++ /dev/null @@ -1 +0,0 @@ -HostFS works! \ No newline at end of file diff --git a/test/include/test_fs.h b/test/include/test_fs.h new file mode 100644 index 00000000..6b6ada31 --- /dev/null +++ b/test/include/test_fs.h @@ -0,0 +1,59 @@ +#ifndef __TEST_FS_H +#define __TEST_FS_H + +#include +#include +#include +#include +#include +#include "test.h" + +int fs_split_path(const char *path, char *dir_buf, char **dir_name, char *base_buf, + char **base_name) { + size_t ret; + + if (path == NULL) { + THROW_ERROR("input path is NULL"); + } + if (dir_buf != NULL) { + if (dir_name == NULL) { + THROW_ERROR("dir_name is NULL"); + } + ret = snprintf(dir_buf, PATH_MAX, "%s", path); + if (ret >= PATH_MAX || ret < 0) { + THROW_ERROR("failed to copy file path to the dir buffer"); + } + *dir_name = dirname(dir_buf); + } + if (base_buf != NULL) { + if (base_name == NULL) { + THROW_ERROR("base_name is NULL"); + } + ret = snprintf(base_buf, PATH_MAX, "%s", path); + if (ret >= PATH_MAX || ret < 0) { + THROW_ERROR("failed to copy file path to the base buffer"); + } + *base_name = basename(base_buf); + } + return 0; +} + +int fs_check_file_content(const char *path, const char *msg) { + char read_buf[128] = { 0 }; + + int fd = open(path, O_RDONLY); + if (fd < 0) { + THROW_ERROR("failed to open file"); + } + size_t len = read(fd, read_buf, sizeof(read_buf)); + if (len != strlen(msg)) { + THROW_ERROR("failed to read the msg from file"); + } + if (strcmp(msg, read_buf) != 0) { + THROW_ERROR("the message read from the file is not expected"); + } + close(fd); + return 0; +} + +#endif /* __TEST_FS_H */ diff --git a/test/link/main.c b/test/link/main.c index 6317404e..f1fbead9 100644 --- a/test/link/main.c +++ b/test/link/main.c @@ -1,91 +1,191 @@ -#include #include -#include -#include -#include -#include #include +#include +#include "test_fs.h" -int main(int argc, const char *argv[]) { - const char *file_name = "/root/test_filesystem_link.txt"; - const char *link_name = "/root/link.txt"; - const char *write_msg = "Hello World\n"; - char read_buf[128] = {0}; - int ret; +// ============================================================================ +// Helper function +// ============================================================================ - // create a file and write message +#define WRITE_MSG "Hello World" + +static int create_and_write_file(const char *file_path) { + int fd; int flags = O_WRONLY | O_CREAT | O_TRUNC; int mode = 00666; - int fd = open(file_name, flags, mode); + + fd = open(file_path, flags, mode); if (fd < 0) { - printf("ERROR: failed to open a file for write\n"); - return -1; + THROW_ERROR("failed to create a file"); } - int len = write(fd, write_msg, strlen(write_msg)); - if (len <= 0) { - printf("ERROR: failed to write to the file\n"); - return -1; + if (write(fd, WRITE_MSG, strlen(WRITE_MSG)) <= 0) { + THROW_ERROR("failed to write to the file"); } close(fd); - - // link - ret = link(file_name, link_name); - if (ret < 0) { - printf("ERROR: failed to link the file\n"); - return -1; - } - - // read the link file - fd = open(link_name, O_RDONLY, 00666); - if (fd < 0) { - printf("ERROR: failed to open the file for read\n"); - return -1; - } - len = read(fd, read_buf, sizeof(read_buf)); - if (len != strlen(write_msg)) { - printf("ERROR: failed to read to the file\n"); - return -1; - } - ret = strcmp(write_msg, read_buf); - if (ret != 0) { - printf("ERROR: the message read from the file is not as it was written\n"); - return -1; - } - - // unlink - ret = unlink(link_name); - if (ret < 0) { - printf("ERROR: failed to link the file\n"); - return -1; - } - - // stat - struct stat stat_buf; - ret = stat(link_name, &stat_buf); - if (!(ret < 0 && errno == ENOENT)) { - printf("ERROR: stat on \"%s\" should return ENOENT", link_name); - return -1; - } - - // rename - ret = rename(file_name, link_name); - if (ret < 0) { - printf("ERROR: failed to rename the file"); - return -1; - } - - // stat - ret = stat(file_name, &stat_buf); - if (!(ret < 0 && errno == ENOENT)) { - printf("ERROR: stat on \"%s\" should return ENOENT", file_name); - return -1; - } - ret = stat(link_name, &stat_buf); - if (ret < 0) { - printf("ERROR: failed to stat the file"); - return -1; - } - - printf("link, unlink, rename test successful\n"); 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 link +// ============================================================================ + +static int __test_link_then_unlink(const char *old_path, const char *new_path) { + struct stat stat_buf; + int ret; + + if (link(old_path, new_path) < 0) { + THROW_ERROR("failed to link file"); + } + + if (fs_check_file_content(new_path, WRITE_MSG) < 0) { + THROW_ERROR("failed to check file content"); + } + + if (unlink(new_path) < 0) { + THROW_ERROR("failed to unlink the link"); + } + ret = stat(new_path, &stat_buf); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("stat on \"%s\" should return ENOENT", new_path); + } + return 0; +} + +static int __test_linkat_then_unlinkat(const char *old_path, const char *new_path) { + struct stat stat_buf; + char old_dir_buf[PATH_MAX] = { 0 }; + char old_base_buf[PATH_MAX] = { 0 }; + char new_dir_buf[PATH_MAX] = { 0 }; + char new_base_buf[PATH_MAX] = { 0 }; + char *old_dir_name, *old_file_name, *new_dir_name, *new_file_name; + int old_dirfd, new_dirfd, ret; + + if (fs_split_path(old_path, old_dir_buf, &old_dir_name, old_base_buf, + &old_file_name) < 0) { + THROW_ERROR("failed to split old path"); + } + old_dirfd = open(old_dir_name, O_RDONLY); + if (old_dirfd < 0) { + THROW_ERROR("failed to open old dir"); + } + if (fs_split_path(new_path, new_dir_buf, &new_dir_name, new_base_buf, + &new_file_name) < 0) { + THROW_ERROR("failed to split new path"); + } + new_dirfd = open(new_dir_name, O_RDONLY); + if (new_dirfd < 0) { + THROW_ERROR("failed to open new dir"); + } + + if (linkat(old_dirfd, old_file_name, new_dirfd, new_file_name, 0) < 0) { + THROW_ERROR("failed to linkat with dirfd"); + } + close(old_dirfd); + + if (fs_check_file_content(new_path, WRITE_MSG) < 0) { + THROW_ERROR("failed to check file content"); + } + + if (unlinkat(new_dirfd, new_file_name, 0) < 0) { + THROW_ERROR("failed to unlinkat the link"); + } + close(new_dirfd); + ret = stat(new_path, &stat_buf); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("stat on \"%s\" should return ENOENT", new_path); + } + return 0; +} + +static int __test_linkat_with_empty_oldpath(const char *old_path, const char *new_path) { + char new_dir_buf[PATH_MAX] = { 0 }; + char new_base_buf[PATH_MAX] = { 0 }; + char *new_dir_name, *new_file_name; + int old_dirfd, new_dirfd, ret; + + old_dirfd = open(old_path, O_RDONLY); + if (old_dirfd < 0) { + THROW_ERROR("failed to open old dir"); + } + if (fs_split_path(new_path, new_dir_buf, &new_dir_name, new_base_buf, + &new_file_name) < 0) { + THROW_ERROR("failed to split new path"); + } + new_dirfd = open(new_dir_name, O_RDONLY); + if (new_dirfd < 0) { + THROW_ERROR("failed to open new dir"); + } + + ret = linkat(old_dirfd, "", new_dirfd, new_file_name, 0); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("linkat with empty oldpath should return ENOENT"); + } + if (linkat(old_dirfd, "", new_dirfd, new_file_name, AT_EMPTY_PATH) < 0) { + THROW_ERROR("failed to linkat with empty oldpath and AT_EMPTY_PATH flags"); + } + close(old_dirfd); + close(new_dirfd); + + if (fs_check_file_content(new_path, WRITE_MSG) < 0) { + THROW_ERROR("failed to check file content"); + } + + if (unlink(new_path) < 0) { + THROW_ERROR("failed to unlink the link"); + } + return 0; +} + +typedef int(*test_link_func_t)(const char *, const char *); + +static int test_link_framework(test_link_func_t fn) { + const char *old_path = "/root/test_filesystem_link_old.txt"; + const char *new_path = "/root/test_filesystem_link_new.txt"; + + if (create_and_write_file(old_path) < 0) { + return -1; + } + if (fn(old_path, new_path) < 0) { + return -1; + } + if (remove_file(old_path) < 0) { + return -1; + } + return 0; +} + +static int test_link_then_unlink() { + return test_link_framework(__test_link_then_unlink); +} + +static int test_linkat_then_unlinkat() { + return test_link_framework(__test_linkat_then_unlinkat); +} + +static int test_linkat_with_empty_oldpath() { + return test_link_framework(__test_linkat_with_empty_oldpath); +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_link_then_unlink), + TEST_CASE(test_linkat_then_unlinkat), + TEST_CASE(test_linkat_with_empty_oldpath), +}; + +int main(int argc, const char *argv[]) { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +} diff --git a/test/mkdir/main.c b/test/mkdir/main.c index 04486f78..dc030686 100644 --- a/test/mkdir/main.c +++ b/test/mkdir/main.c @@ -1,69 +1,189 @@ -#include #include -#include -#include -#include #include +#include +#include "test_fs.h" -int main(int argc, const char *argv[]) { +// ============================================================================ +// Helper function +// ============================================================================ - const int BUF_SIZE = 20; - char buf[10]; +static int create_dir(const char *dir_path) { int ret; - char *cwd = getcwd(buf, BUF_SIZE); - if (cwd != buf) { - printf("failed to getcwd\n"); - return -1; - } - - const char expect_cwd[] = "/"; - if (strcmp(buf, expect_cwd)) { - printf("incorrect cwd \"%s\". expect \"%s\".\n", buf, expect_cwd); - return -1; - } - - //const char DIR_NAME[] = "test_dir"; - //const char DIR_PATH[] = "/test_dir"; - const char DIR_NAME[] = "/root/test_dir"; - const char DIR_PATH[] = "/root/test_dir"; - const int DIR_MODE = 0664; - ret = mkdir(DIR_NAME, DIR_MODE); + ret = mkdir(dir_path, 00775); if (ret < 0) { - printf("failed to mkdir \"%s\"", DIR_NAME); - return ret; + THROW_ERROR("failed to create the dir"); } - - ret = chdir(DIR_NAME); - if (ret < 0) { - printf("failed to chdir to \"%s\"", DIR_NAME); - return ret; - } - - cwd = getcwd(buf, BUF_SIZE); - if (cwd != buf) { - printf("failed to getcwd\n"); - return -1; - } - - if (strcmp(buf, DIR_PATH)) { - printf("incorrect cwd \"%s\". expect \"%s\".\n", buf, DIR_PATH); - return -1; - } - - ret = rmdir(DIR_PATH); - if (ret < 0) { - printf("failed to rmdir \"%s\"", DIR_PATH); - return ret; - } - - struct stat stat_buf; - ret = stat(DIR_PATH, &stat_buf); - if (!(ret < 0 && errno == ENOENT)) { - printf("stat on \"%s\" should return ENOENT", DIR_PATH); - return ret; - } - - printf("getcwd, mkdir, rmdir, chdir test successful\n"); return 0; } + +static int remove_dir(const char *dir_path) { + int ret; + + ret = rmdir(dir_path); + if (ret < 0) { + THROW_ERROR("failed to remove the created dir"); + } + return 0; +} + +// ============================================================================ +// Test cases for mkdir +// ============================================================================ + +static int __test_mkdir(const char *dir_path) { + struct stat stat_buf; + mode_t mode = 00775; + + if (mkdir(dir_path, mode) < 0) { + THROW_ERROR("failed to mkdir"); + } + if (stat(dir_path, &stat_buf) < 0) { + THROW_ERROR("failed to stat dir"); + } + if (!S_ISDIR(stat_buf.st_mode)) { + THROW_ERROR("failed to check if it is dir"); + } + return 0; +} + +static int __test_mkdirat(const char *dir_path) { + struct stat stat_buf; + mode_t mode = 00775; + char dir_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; + char *dir_name, *last_name; + int dirfd; + + if (fs_split_path(dir_path, dir_buf, &dir_name, base_buf, &last_name) < 0) { + THROW_ERROR("failed to split path"); + } + dirfd = open(dir_name, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + if (mkdirat(dirfd, last_name, mode) < 0) { + THROW_ERROR("failed to mkdirat dir with dirfd"); + } + close(dirfd); + + if (stat(dir_path, &stat_buf) < 0) { + THROW_ERROR("failed to stat dir"); + } + if (!S_ISDIR(stat_buf.st_mode)) { + THROW_ERROR("failed to check if it is dir"); + } + return 0; +} + +typedef int(*test_mkdir_func_t)(const char *); + +static int test_mkdir_framework(test_mkdir_func_t fn) { + const char *dir_path = "/root/test_filesystem_mkdir"; + + if (fn(dir_path) < 0) { + return -1; + } + if (remove_dir(dir_path) < 0) { + return -1; + } + return 0; +} + +static int test_mkdir() { + return test_mkdir_framework(__test_mkdir); +} + +static int test_mkdirat() { + return test_mkdir_framework(__test_mkdirat); +} + +// ============================================================================ +// Test cases for chdir +// ============================================================================ + +static int __test_chdir(const char *dir_path) { + char buf[128] = { 0 }; + char *cwd; + + if (chdir(dir_path) < 0) { + THROW_ERROR("failed to chdir"); + } + cwd = getcwd(buf, sizeof(buf)); + if (cwd != buf) { + THROW_ERROR("failed to getcwd"); + } + if (strcmp(buf, dir_path)) { + THROW_ERROR("the cwd is incorrect after chdir"); + } + return 0; +} + +static int test_chdir_framework(test_mkdir_func_t fn) { + const char *dir_path = "/root/test_filesystem_chdir"; + + if (create_dir(dir_path) < 0) { + return -1; + } + if (fn(dir_path) < 0) { + return -1; + } + if (remove_dir(dir_path) < 0) { + return -1; + } + return 0; +} + +static int test_chdir() { + return test_chdir_framework(__test_chdir); +} + +// ============================================================================ +// Test cases for rmdir +// ============================================================================ + +static int __test_rmdir_via_unlinkat(const char *dir_path) { + struct stat stat_buf; + int ret; + + if (unlinkat(AT_FDCWD, dir_path, AT_REMOVEDIR) < 0) { + THROW_ERROR("failed to remove dir"); + } + + ret = stat(dir_path, &stat_buf); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("stat on \"%s\" should return ENOENT", dir_path); + } + return 0; +} + +static int test_rmdir_framework(test_mkdir_func_t fn) { + const char *dir_path = "/root/test_filesystem_rmdir"; + + if (create_dir(dir_path) < 0) { + return -1; + } + if (fn(dir_path) < 0) { + return -1; + } + return 0; +} + +static int test_rmdir_via_unlinkat() { + return test_rmdir_framework(__test_rmdir_via_unlinkat); +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_mkdir), + TEST_CASE(test_mkdirat), + TEST_CASE(test_chdir), + TEST_CASE(test_rmdir_via_unlinkat), +}; + +int main(int argc, const char *argv[]) { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +} diff --git a/test/open/main.c b/test/open/main.c index 30443b54..a74818b1 100644 --- a/test/open/main.c +++ b/test/open/main.c @@ -1,8 +1,5 @@ #include -#include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper function @@ -47,21 +44,14 @@ static int __test_openat_with_abs_path(const char *file_path, int flags, int mod } 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_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; char *dir_name, *file_name; - int dirfd, fd, ret; + int dirfd, fd; - 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"); + if (fs_split_path(file_path, dir_buf, &dir_name, base_buf, &file_name) < 0) { + THROW_ERROR("failed to split path"); } - 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"); diff --git a/test/readdir/main.c b/test/readdir/main.c index 5f62e054..ed55cc39 100644 --- a/test/readdir/main.c +++ b/test/readdir/main.c @@ -3,10 +3,7 @@ #include #include #include -#include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // The test case of readdir diff --git a/test/rename/Makefile b/test/rename/Makefile new file mode 100644 index 00000000..9e1b6dec --- /dev/null +++ b/test/rename/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/rename/main.c b/test/rename/main.c new file mode 100644 index 00000000..deae3f91 --- /dev/null +++ b/test/rename/main.c @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include "test_fs.h" + +// ============================================================================ +// Helper function +// ============================================================================ + +#define WRITE_MSG "Hello World" + +static int create_file_with_content(const char *file_path, const char *msg) { + int fd; + int flags = O_WRONLY | O_CREAT | O_TRUNC; + int mode = 00666; + + fd = open(file_path, flags, mode); + if (fd < 0) { + THROW_ERROR("failed to create a file"); + } + if (msg != NULL) { + if (write(fd, msg, strlen(msg)) <= 0) { + THROW_ERROR("failed to write to the file"); + } + } + close(fd); + return 0; +} + +// ============================================================================ +// Test cases for rename +// ============================================================================ + +static int __test_rename(const char *old_path, const char *new_path) { + struct stat stat_buf; + int ret; + + if (rename(old_path, new_path) < 0) { + THROW_ERROR("failed to rename file"); + } + + if (fs_check_file_content(new_path, WRITE_MSG) < 0) { + THROW_ERROR("failed to check file content"); + } + + ret = stat(old_path, &stat_buf); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("stat on old path should return ENOENT"); + } + if (unlink(new_path) < 0) { + THROW_ERROR("failed to remove the new file"); + } + return 0; +} + +static int __test_renameat(const char *old_path, const char *new_path) { + struct stat stat_buf; + char old_dir_buf[PATH_MAX] = { 0 }; + char old_base_buf[PATH_MAX] = { 0 }; + char new_dir_buf[PATH_MAX] = { 0 }; + char new_base_buf[PATH_MAX] = { 0 }; + char *old_dir_name, *old_file_name, *new_dir_name, *new_file_name; + int old_dirfd, new_dirfd, ret; + + if (fs_split_path(old_path, old_dir_buf, &old_dir_name, old_base_buf, + &old_file_name) < 0) { + THROW_ERROR("failed to split old path"); + } + old_dirfd = open(old_dir_name, O_RDONLY); + if (old_dirfd < 0) { + THROW_ERROR("failed to open old dir"); + } + if (fs_split_path(new_path, new_dir_buf, &new_dir_name, new_base_buf, + &new_file_name) < 0) { + THROW_ERROR("failed to split new path"); + } + new_dirfd = open(new_dir_name, O_RDONLY); + if (new_dirfd < 0) { + THROW_ERROR("failed to open new dir"); + } + if (renameat(old_dirfd, old_file_name, new_dirfd, new_file_name) < 0) { + THROW_ERROR("failed to rename with dirfd"); + } + close(old_dirfd); + close(new_dirfd); + + if (fs_check_file_content(new_path, WRITE_MSG) < 0) { + THROW_ERROR("failed to check file content"); + } + + ret = stat(old_path, &stat_buf); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("stat on old path should return ENOENT"); + } + if (unlink(new_path) < 0) { + THROW_ERROR("failed to remove the new file"); + } + return 0; +} + +typedef int(*test_rename_func_t)(const char *, const char *); + +static int test_rename_framework(test_rename_func_t fn, bool target_exist) { + const char *old_path = "/root/test_filesystem_rename_old.txt"; + const char *new_path = "/root/test_filesystem_rename_new.txt"; + + if (create_file_with_content(old_path, WRITE_MSG) < 0) { + THROW_ERROR("failed to create old file with content"); + } + if (target_exist) { + if (create_file_with_content(new_path, NULL) < 0) { + THROW_ERROR("failed to create new file"); + } + } + if (fn(old_path, new_path) < 0) { + return -1; + } + return 0; +} + +static int test_rename() { + return test_rename_framework(__test_rename, false); +} + +static int test_rename_with_target_exist() { + return test_rename_framework(__test_rename, true); +} + +static int test_renameat() { + return test_rename_framework(__test_renameat, false); +} + +static int test_rename_dir() { + const char *old_dir = "/root/test_old_dir"; + const char *new_dir = "/root/test_new_dir"; + const char *file_name = "test_file.txt"; + char file_buf[128] = { 0 }; + mode_t mode = 00775; + struct stat stat_buf; + int ret; + + if (mkdir(old_dir, mode) < 0) { + THROW_ERROR("failed to mkdir old dir"); + } + ret = snprintf(file_buf, sizeof(file_buf), "%s/%s", old_dir, file_name); + if (ret >= sizeof(file_buf) || ret < 0) { + THROW_ERROR("failed to copy file buf"); + } + + if (create_file_with_content(file_buf, WRITE_MSG) < 0) { + THROW_ERROR("failed to create file in old dir"); + } + + if (rename(old_dir, new_dir) < 0) { + THROW_ERROR("failed to rename dir"); + } + + ret = snprintf(file_buf, sizeof(file_buf), "%s/%s", new_dir, file_name); + if (ret >= sizeof(file_buf) || ret < 0) { + THROW_ERROR("failed to copy file buf"); + } + + if (fs_check_file_content(file_buf, WRITE_MSG) < 0) { + THROW_ERROR("failed to check file content"); + } + + ret = stat(old_dir, &stat_buf); + if (!(ret < 0 && errno == ENOENT)) { + THROW_ERROR("stat on old dir should return ENOENT"); + } + if (unlink(file_buf) < 0) { + THROW_ERROR("failed to remove the file in new dir"); + } + if (rmdir(new_dir) < 0) { + THROW_ERROR("failed to remove the new dir"); + } + return 0; +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + // TODO: test more corner cases + TEST_CASE(test_rename), + TEST_CASE(test_rename_with_target_exist), + TEST_CASE(test_renameat), + TEST_CASE(test_rename_dir), +}; + +int main(int argc, const char *argv[]) { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +} diff --git a/test/stat/main.c b/test/stat/main.c index caa42f9b..7c2659ff 100644 --- a/test/stat/main.c +++ b/test/stat/main.c @@ -1,10 +1,7 @@ #include #include #include -#include -#include -#include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper function @@ -114,21 +111,14 @@ static int __test_fstatat_with_empty_path(const char *file_path) { 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_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 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"); + if (fs_split_path(file_path, dir_buf, &dir_name, base_buf, &file_name) < 0) { + THROW_ERROR("failed to split path"); } - 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"); diff --git a/test/symlink/main.c b/test/symlink/main.c index 79c302a6..4936bd5d 100644 --- a/test/symlink/main.c +++ b/test/symlink/main.c @@ -1,14 +1,10 @@ #include #include #include -#include #include -#include -#include -#include #include #include -#include "test.h" +#include "test_fs.h" // ============================================================================ // Helper variable and function @@ -82,16 +78,13 @@ static int __test_readlink_from_proc_self_fd(const char *file_path) { static int __test_realpath(const char *file_path) { char buf[128] = { 0 }; - char dirc[128] = { 0 }; - char basec[128] = { 0 }; + char dir_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; char *dir_name, *file_name, *res; - if (snprintf(dirc, sizeof(dirc), "%s", file_path) < 0 || - snprintf(basec, sizeof(dirc), "%s", file_path) < 0) { - THROW_ERROR("failed to copy file path"); + if (fs_split_path(file_path, dir_buf, &dir_name, base_buf, &file_name) < 0) { + THROW_ERROR("failed to split path"); } - dir_name = dirname(dirc); - file_name = basename(basec); if (chdir(dir_name) < 0) { THROW_ERROR("failed to chdir to %s", dir_name); } @@ -112,6 +105,37 @@ static int __test_realpath(const char *file_path) { return 0; } +static int __test_readlinkat(const char *file_path) { + int dirfd; + size_t n; + char buf[128] = { 0 }; +#define LINK_DIR "/root" +#define LINK_NAME "test_symlink.link" + const char *link_path = LINK_DIR"/"LINK_NAME; + if (symlink(file_path, link_path) < 0) { + THROW_ERROR("failed to create symlink"); + } + + dirfd = open(LINK_DIR, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + n = readlinkat(dirfd, LINK_NAME, buf, sizeof(buf)); + if (n < 0) { + THROW_ERROR("failed to readlinkat from %s", link_path); + } else if (n != strlen(file_path)) { + THROW_ERROR("readlink from %s length is wrong", link_path); + } + if (strncmp(buf, file_path, n) != 0) { + THROW_ERROR("check the content from %s failed", link_path); + } + close(dirfd); + if (remove_file(link_path) < 0) { + THROW_ERROR("failed to delete link file"); + } + return 0; +} + typedef int(*test_readlink_func_t)(const char *); static int test_readlink_framework(test_readlink_func_t fn) { @@ -138,6 +162,10 @@ static int test_realpath() { return test_readlink_framework(__test_realpath); } +static int test_readlinkat() { + return test_readlink_framework(__test_readlinkat); +} + static int test_readlink_from_proc_self_exe() { char exe_buf[128] = { 0 }; char absolute_path[128] = { 0 }; @@ -165,18 +193,69 @@ static int test_readlink_from_proc_self_exe() { // Test cases for symlink // ============================================================================ +static int __test_symlinkat(const char *target, const char *link_path) { + char dir_buf[PATH_MAX] = { 0 }; + char base_buf[PATH_MAX] = { 0 }; + char *dir_name, *link_name; + + if (create_file(target) < 0) { + THROW_ERROR("failed to create target file"); + } + int fd = open(target, O_WRONLY); + if (fd < 0) { + THROW_ERROR("failed to open target to write"); + } + char *write_str = "Hello World\n"; + if (write(fd, write_str, strlen(write_str)) <= 0) { + THROW_ERROR("failed to write"); + } + close(fd); + + if (fs_split_path(link_path, dir_buf, &dir_name, base_buf, &link_name) < 0) { + THROW_ERROR("failed to split path"); + } + int dirfd = open(dir_name, O_RDONLY); + if (dirfd < 0) { + THROW_ERROR("failed to open dir"); + } + if (symlinkat(target, dirfd, link_name) < 0) { + THROW_ERROR("failed to create symlink"); + } + close(dirfd); + + fd = open(link_path, O_RDONLY); + if (fd < 0) { + THROW_ERROR("failed to open link file to read"); + } + char read_buf[128] = { 0 }; + if (read(fd, read_buf, sizeof(read_buf)) != strlen(write_str)) { + THROW_ERROR("failed to read"); + } + if (strcmp(write_str, read_buf) != 0) { + THROW_ERROR("the message read from the file is not as it was written"); + } + close(fd); + + if (remove_file(target) < 0) { + THROW_ERROR("failed to delete target file"); + } + return 0; +} + + static int __test_symlink(const char *target, const char *link_path) { - char dir_buf[128] = { 0 }; + char dir_buf[PATH_MAX] = { 0 }; char *dir_name; - char target_path[256] = { 0 }; + char target_path[PATH_MAX * 2] = { 0 }; if (target[0] == '/') { snprintf(target_path, sizeof(target_path), "%s", target); } else { // If `target` is not an absolute path, // it must be a path relative to the directory of the `link_path` - snprintf(dir_buf, sizeof(dir_buf), "%s", link_path); - dir_name = dirname(dir_buf); + if (fs_split_path(link_path, dir_buf, &dir_name, NULL, NULL) < 0) { + THROW_ERROR("failed to split path"); + } snprintf(target_path, sizeof(target_path), "%s/%s", dir_name, target); } if (create_file(target_path) < 0) { @@ -231,17 +310,18 @@ static int __test_symlink(const char *target, const char *link_path) { } static int __test_create_file_from_symlink(const char *target, const char *link_path) { - char dir_buf[128] = { 0 }; + char dir_buf[PATH_MAX] = { 0 }; char *dir_name; - char target_path[256] = { 0 }; + char target_path[PATH_MAX * 2] = { 0 }; if (target[0] == '/') { snprintf(target_path, sizeof(target_path), "%s", target); } else { // If `target` is not an absolute path, // it must be a path relative to the directory of the `link_path` - snprintf(dir_buf, sizeof(dir_buf), "%s", link_path); - dir_name = dirname(dir_buf); + if (fs_split_path(link_path, dir_buf, &dir_name, NULL, NULL) < 0) { + THROW_ERROR("failed to split path"); + } snprintf(target_path, sizeof(target_path), "%s/%s", dir_name, target); } @@ -283,6 +363,12 @@ static int test_symlink_framework(test_symlink_func_t fn, const char *target, return 0; } +static int test_symlinkat() { + char *target = "/root/test_symlink.file"; + char *link = "/root/test_symlink.link"; + return test_symlink_framework(__test_symlinkat, target, link); +} + static int test_symlink_to_absolute_target() { char *target = "/root/test_symlink.file"; char *link = "/root/test_symlink.link"; @@ -356,6 +442,8 @@ static test_case_t test_cases[] = { TEST_CASE(test_readlink_from_proc_self_fd), TEST_CASE(test_realpath), TEST_CASE(test_readlink_from_proc_self_exe), + TEST_CASE(test_readlinkat), + TEST_CASE(test_symlinkat), TEST_CASE(test_symlink_to_absolute_target), TEST_CASE(test_symlink_to_relative_target), TEST_CASE(test_symlink_from_ramfs),