From 1ad8f22170171dfab592f1a9a318d37de5aedff5 Mon Sep 17 00:00:00 2001 From: LI Qing Date: Thu, 11 Jun 2020 13:40:37 +0800 Subject: [PATCH] Add support to handle symbolic link file --- deps/sefs | 2 +- src/libos/src/error/to_errno.rs | 1 + src/libos/src/fs/file_ops/access.rs | 16 +- src/libos/src/fs/file_ops/chown.rs | 15 +- src/libos/src/fs/file_ops/file_flags.rs | 8 + src/libos/src/fs/file_ops/link.rs | 2 +- src/libos/src/fs/file_ops/mod.rs | 2 +- src/libos/src/fs/file_ops/stat.rs | 14 +- src/libos/src/fs/file_ops/symlink.rs | 53 ++++- src/libos/src/fs/fs_view.rs | 112 +++++++--- src/libos/src/fs/hostfs.rs | 5 +- src/libos/src/fs/mod.rs | 2 +- src/libos/src/fs/syscalls.rs | 28 +++ src/libos/src/process/do_spawn/exec_loader.rs | 2 +- src/libos/src/syscall/mod.rs | 9 +- test/symlink/main.c | 203 +++++++++++++++++- 16 files changed, 418 insertions(+), 56 deletions(-) diff --git a/deps/sefs b/deps/sefs index cc6f694c..2420622f 160000 --- a/deps/sefs +++ b/deps/sefs @@ -1 +1 @@ -Subproject commit cc6f694c0ac27755a1b0e1546022d598eb53ca76 +Subproject commit 2420622f050efda627ccebcff6e86a71dcf405f5 diff --git a/src/libos/src/error/to_errno.rs b/src/libos/src/error/to_errno.rs index 5735459e..a8376e15 100644 --- a/src/libos/src/error/to_errno.rs +++ b/src/libos/src/error/to_errno.rs @@ -95,6 +95,7 @@ impl ToErrno for rcore_fs::vfs::FsError { FsError::WrProtected => EROFS, FsError::NoIntegrity => EIO, FsError::PermError => EPERM, + FsError::NameTooLong => ENAMETOOLONG, } } } diff --git a/src/libos/src/fs/file_ops/access.rs b/src/libos/src/fs/file_ops/access.rs index 4bd6e5f6..0473be30 100644 --- a/src/libos/src/fs/file_ops/access.rs +++ b/src/libos/src/fs/file_ops/access.rs @@ -49,7 +49,7 @@ pub fn do_faccessat( ); if Path::new(path).is_absolute() { // Path is absolute, so dirfd is ignored - return Ok(do_access(path, mode)?); + return Ok(do_access(path, mode, flags)?); } let path = match dirfd { DirFd::Fd(dirfd) => { @@ -58,14 +58,22 @@ pub fn do_faccessat( } DirFd::Cwd => path.to_owned(), }; - do_access(&path, mode) + do_access(&path, mode, flags) } -fn do_access(path: &str, mode: AccessibilityCheckMode) -> Result<()> { +fn do_access( + path: &str, + mode: AccessibilityCheckMode, + flags: AccessibilityCheckFlags, +) -> Result<()> { let inode = { let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode(path)? + if flags.contains(AccessibilityCheckFlags::AT_SYMLINK_NOFOLLOW) { + fs.lookup_inode_no_follow(path)? + } else { + fs.lookup_inode(path)? + } }; if mode.test_for_exist() { return Ok(()); diff --git a/src/libos/src/fs/file_ops/chown.rs b/src/libos/src/fs/file_ops/chown.rs index e0123473..3a9d95fd 100644 --- a/src/libos/src/fs/file_ops/chown.rs +++ b/src/libos/src/fs/file_ops/chown.rs @@ -1,8 +1,17 @@ use super::*; pub fn do_chown(path: &str, uid: u32, gid: u32) -> Result<()> { - warn!("chown is partial implemented as lchown"); - do_lchown(path, uid, gid) + debug!("chown: path: {:?}, uid: {}, gid: {}", path, uid, gid); + let inode = { + let current = current!(); + let fs = current.fs().lock().unwrap(); + fs.lookup_inode(path)? + }; + let mut info = inode.metadata()?; + info.uid = uid as usize; + info.gid = gid as usize; + inode.set_metadata(&info)?; + Ok(()) } pub fn do_fchown(fd: FileDesc, uid: u32, gid: u32) -> Result<()> { @@ -20,7 +29,7 @@ pub fn do_lchown(path: &str, uid: u32, gid: u32) -> Result<()> { let inode = { let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode(path)? + fs.lookup_inode_no_follow(path)? }; let mut info = inode.metadata()?; info.uid = uid as usize; diff --git a/src/libos/src/fs/file_ops/file_flags.rs b/src/libos/src/fs/file_ops/file_flags.rs index 06a0b96a..39e9d427 100644 --- a/src/libos/src/fs/file_ops/file_flags.rs +++ b/src/libos/src/fs/file_ops/file_flags.rs @@ -71,6 +71,10 @@ impl CreationFlags { pub fn is_exclusive(&self) -> bool { self.contains(CreationFlags::O_EXCL) } + + pub fn no_follow_symlink(&self) -> bool { + self.contains(CreationFlags::O_NOFOLLOW) + } } bitflags! { @@ -99,4 +103,8 @@ impl StatusFlags { pub fn always_append(&self) -> bool { self.contains(StatusFlags::O_APPEND) } + + pub fn is_fast_open(&self) -> bool { + self.contains(StatusFlags::O_PATH) + } } diff --git a/src/libos/src/fs/file_ops/link.rs b/src/libos/src/fs/file_ops/link.rs index 160cf9a5..f8ce893a 100644 --- a/src/libos/src/fs/file_ops/link.rs +++ b/src/libos/src/fs/file_ops/link.rs @@ -7,7 +7,7 @@ pub fn do_link(oldpath: &str, newpath: &str) -> Result<()> { let (inode, new_dir_inode) = { let current = current!(); let fs = current.fs().lock().unwrap(); - let inode = fs.lookup_inode(&oldpath)?; + let inode = 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/mod.rs b/src/libos/src/fs/file_ops/mod.rs index 225e7888..83c73a66 100644 --- a/src/libos/src/fs/file_ops/mod.rs +++ b/src/libos/src/fs/file_ops/mod.rs @@ -25,7 +25,7 @@ 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_fstatat, do_lstat, Stat, StatFlags}; -pub use self::symlink::do_readlink; +pub use self::symlink::{do_readlink, do_symlinkat}; pub use self::truncate::{do_ftruncate, do_truncate}; pub use self::unlink::do_unlink; pub use self::write::{do_pwrite, do_write, do_writev}; diff --git a/src/libos/src/fs/file_ops/stat.rs b/src/libos/src/fs/file_ops/stat.rs index e7121213..af4010df 100644 --- a/src/libos/src/fs/file_ops/stat.rs +++ b/src/libos/src/fs/file_ops/stat.rs @@ -139,7 +139,7 @@ fn do_stat(path: &str) -> Result { let inode = { let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode_follow(&path)? + fs.lookup_inode(&path)? }; let stat = Stat::from(inode.metadata()?); Ok(stat) @@ -149,7 +149,6 @@ pub fn do_fstat(fd: u32) -> Result { debug!("fstat: fd: {}", fd); let file_ref = current!().file(fd as FileDesc)?; let stat = Stat::from(file_ref.metadata()?); - // TODO: handle symlink Ok(stat) } @@ -158,7 +157,7 @@ pub fn do_lstat(path: &str) -> Result { let inode = { let current = current!(); let fs = current.fs().lock().unwrap(); - fs.lookup_inode(&path)? + fs.lookup_inode_no_follow(&path)? }; let stat = Stat::from(inode.metadata()?); Ok(stat) @@ -185,9 +184,14 @@ pub fn do_fstatat(dirfd: DirFd, path: &str, flags: StatFlags) -> Result { } else { let dir_path = get_dir_path(dirfd)?; let path = dir_path + "/" + path; - do_stat(&path) + if !flags.contains(StatFlags::AT_SYMLINK_NOFOLLOW) { + do_stat(&path) + } else { + do_lstat(&path) + } } } - DirFd::Cwd => do_stat(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 d447c764..8d756040 100644 --- a/src/libos/src/fs/file_ops/symlink.rs +++ b/src/libos/src/fs/file_ops/symlink.rs @@ -18,11 +18,60 @@ pub fn do_readlink(path: &str, buf: &mut [u8]) -> Result { return_errno!(EINVAL, "not a normal file link") } } else { - // TODO: support symbolic links - return_errno!(EINVAL, "not a symbolic link") + let inode = { + let current = current!(); + let fs = current.fs().lock().unwrap(); + fs.lookup_inode_no_follow(path)? + }; + if inode.metadata()?.type_ != FileType::SymLink { + return_errno!(EINVAL, "not a symbolic link"); + } + let mut content = vec![0u8; PATH_MAX]; + let len = inode.read_at(0, &mut content)?; + let path = std::str::from_utf8(&content[..len]) + .map_err(|_| errno!(EINVAL, "invalid symlink"))?; + String::from(path) } }; let len = file_path.len().min(buf.len()); buf[0..len].copy_from_slice(&file_path.as_bytes()[0..len]); Ok(len) } + +fn do_symlink(target: &str, link_path: &str) -> Result { + let (dir_path, link_name) = split_path(&link_path); + let dir_inode = { + let current = current!(); + let fs = current.fs().lock().unwrap(); + fs.lookup_inode(dir_path)? + }; + if !dir_inode.allow_write()? { + return_errno!(EPERM, "symlink cannot be created"); + } + let link_inode = dir_inode.create(link_name, FileType::SymLink, 0o0777)?; + let data = target.as_bytes(); + link_inode.resize(data.len())?; + 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/fs_view.rs b/src/libos/src/fs/fs_view.rs index c9b8444f..1b69fe97 100644 --- a/src/libos/src/fs/fs_view.rs +++ b/src/libos/src/fs/fs_view.rs @@ -53,64 +53,120 @@ impl FsView { return Ok(Box::new(DevSgx)); } let creation_flags = CreationFlags::from_bits_truncate(flags); - let inode = if creation_flags.can_create() { - let (dir_path, file_name) = split_path(&path); - let dir_inode = self.lookup_inode(dir_path)?; - match dir_inode.find(file_name) { - Ok(file_inode) => { - if creation_flags.is_exclusive() { + let inode = if creation_flags.no_follow_symlink() { + match self.lookup_inode_no_follow(path) { + Ok(inode) => { + let status_flags = StatusFlags::from_bits_truncate(flags); + if inode.metadata()?.type_ == FileType::SymLink && !status_flags.is_fast_open() + { + return_errno!(ELOOP, "file is a symlink"); + } + if creation_flags.can_create() && creation_flags.is_exclusive() { return_errno!(EEXIST, "file exists"); } - file_inode + inode } - Err(FsError::EntryNotFound) => { + Err(e) if e.errno() == ENOENT && creation_flags.can_create() => { + let (dir_path, file_name) = split_path(&path); + let dir_inode = self.lookup_inode(dir_path)?; if !dir_inode.allow_write()? { return_errno!(EPERM, "file cannot be created"); } dir_inode.create(file_name, FileType::File, mode)? } - Err(e) => return Err(Error::from(e)), + Err(e) => return Err(e), } } else { - self.lookup_inode(&path)? + match self.lookup_inode(path) { + Ok(inode) => { + if creation_flags.can_create() && creation_flags.is_exclusive() { + return_errno!(EEXIST, "file exists"); + } + inode + } + Err(e) if e.errno() == ENOENT && creation_flags.can_create() => { + let real_path = self.lookup_real_path(&path)?; + let (dir_path, file_name) = split_path(&real_path); + let dir_inode = self.lookup_inode(dir_path)?; + if !dir_inode.allow_write()? { + return_errno!(EPERM, "file cannot be created"); + } + dir_inode.create(file_name, FileType::File, mode)? + } + Err(e) => return Err(e), + } }; let abs_path = self.convert_to_abs_path(&path); Ok(Box::new(INodeFile::open(inode, &abs_path, flags)?)) } - /// Lookup INode from the cwd of the process - pub fn lookup_inode(&self, path: &str) -> Result> { - self.lookup_inode_follow_with_max_times(path, 0) + /// Recursively lookup the real path of giving path, dereference symlinks + pub fn lookup_real_path(&self, path: &str) -> Result { + let (dir_path, file_name) = split_path(&path); + let dir_inode = self.lookup_inode(dir_path)?; + match dir_inode.find(file_name) { + // Handle symlink + Ok(inode) if inode.metadata()?.type_ == FileType::SymLink => { + let new_path = { + let mut content = vec![0u8; PATH_MAX]; + let len = inode.read_at(0, &mut content)?; + let path = std::str::from_utf8(&content[..len]) + .map_err(|_| errno!(ENOENT, "invalid symlink content"))?; + let path = String::from(path); + match path.chars().next() { + None => unreachable!(), + // absolute path + Some('/') => path, + // relative path + _ => { + let dir_path = if dir_path.ends_with("/") { + String::from(dir_path) + } else { + String::from(dir_path) + "/" + }; + dir_path + &path + } + } + }; + self.lookup_real_path(&new_path) + } + Err(FsError::EntryNotFound) | Ok(_) => { + debug!("real_path: cwd: {:?}, path: {:?}", self.cwd(), path); + Ok(String::from(path)) + } + Err(e) => return Err(Error::from(e)), + } } - /// Lookup INode from the cwd of the process, follow symlinks - pub fn lookup_inode_follow(&self, path: &str) -> Result> { + /// Lookup INode from the cwd of the process. If path is a symlink, do not dereference it + pub fn lookup_inode_no_follow(&self, path: &str) -> Result> { + debug!("lookup_inode: cwd: {:?}, path: {:?}", self.cwd(), path); + let (dir_path, file_name) = split_path(&path); + let dir_inode = self.lookup_inode(dir_path)?; + Ok(dir_inode.lookup(file_name)?) + } + + /// Lookup INode from the cwd of the process, dereference symlink + pub fn lookup_inode(&self, path: &str) -> Result> { // Linux uses 40 as the upper limit for resolving symbolic links, // so Occlum use it as a reasonable value const MAX_SYMLINKS: usize = 40; - self.lookup_inode_follow_with_max_times(path, MAX_SYMLINKS) - } - - fn lookup_inode_follow_with_max_times( - &self, - path: &str, - max_times: usize, - ) -> Result> { debug!( - "lookup_inode_follow_with_max_times: cwd: {:?}, path: {:?}, max_times: {}", + "lookup_inode_follow: cwd: {:?}, path: {:?}", self.cwd(), - path, - max_times + path ); if path.len() > 0 && path.as_bytes()[0] == b'/' { // absolute path let abs_path = path.trim_start_matches('/'); - let inode = ROOT_INODE.lookup_follow(abs_path, max_times)?; + let inode = ROOT_INODE.lookup_follow(abs_path, MAX_SYMLINKS)?; Ok(inode) } else { // relative path let cwd = self.cwd().trim_start_matches('/'); - let inode = ROOT_INODE.lookup(cwd)?.lookup_follow(path, max_times)?; + let inode = ROOT_INODE + .lookup_follow(cwd, MAX_SYMLINKS)? + .lookup_follow(path, MAX_SYMLINKS)?; Ok(inode) } } diff --git a/src/libos/src/fs/hostfs.rs b/src/libos/src/fs/hostfs.rs index 2d023110..ebf14610 100644 --- a/src/libos/src/fs/hostfs.rs +++ b/src/libos/src/fs/hostfs.rs @@ -127,7 +127,10 @@ impl INode for HNode { FileType::File => { try_std!(fs::File::create(&new_path)); } - _ => unimplemented!("only support creating files in HostFS"), + _ => { + warn!("only support creating regular files in HostFS"); + return Err(FsError::PermError); + } } Ok(Arc::new(HNode { path: new_path, diff --git a/src/libos/src/fs/mod.rs b/src/libos/src/fs/mod.rs index a0758095..6be7bb09 100644 --- a/src/libos/src/fs/mod.rs +++ b/src/libos/src/fs/mod.rs @@ -1,7 +1,7 @@ use super::*; use process; -use rcore_fs::vfs::{FileSystem, FileType, FsError, INode, Metadata, Timespec}; +use rcore_fs::vfs::{FileSystem, FileType, FsError, INode, Metadata, Timespec, PATH_MAX}; use std; use std::any::Any; use std::fmt; diff --git a/src/libos/src/fs/syscalls.rs b/src/libos/src/fs/syscalls.rs index b1f3962f..29135ccd 100644 --- a/src/libos/src/fs/syscalls.rs +++ b/src/libos/src/fs/syscalls.rs @@ -388,6 +388,34 @@ pub fn do_readlink(path: *const i8, buf: *mut u8, size: usize) -> Result 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) +} + +pub fn do_symlinkat(target: *const i8, new_dirfd: i32, 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(); + 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)?; + Ok(0) +} + pub fn do_chmod(path: *const i8, mode: u16) -> Result { let path = from_user::clone_cstring_safely(path)? .to_string_lossy() diff --git a/src/libos/src/process/do_spawn/exec_loader.rs b/src/libos/src/process/do_spawn/exec_loader.rs index b0a937fc..0bae60a5 100644 --- a/src/libos/src/process/do_spawn/exec_loader.rs +++ b/src/libos/src/process/do_spawn/exec_loader.rs @@ -64,7 +64,7 @@ pub fn load_file_to_vec(file_path: &str, current_ref: &ThreadRef) -> Result handle_unsupported(), (Link = 86) => do_link(oldpath: *const i8, newpath: *const i8), (Unlink = 87) => do_unlink(path: *const i8), - (Symlink = 88) => handle_unsupported(), + (Symlink = 88) => do_symlink(target: *const i8, link_path: *const i8), (Readlink = 89) => do_readlink(path: *const i8, buf: *mut u8, size: usize), (Chmod = 90) => do_chmod(path: *const i8, mode: u16), (Fchmod = 91) => do_fchmod(fd: FileDesc, mode: u16), @@ -343,7 +344,7 @@ macro_rules! process_syscall_table_with_callback { (Unlinkat = 263) => handle_unsupported(), (Renameat = 264) => handle_unsupported(), (Linkat = 265) => handle_unsupported(), - (Symlinkat = 266) => handle_unsupported(), + (Symlinkat = 266) => do_symlinkat(target: *const i8, new_dirfd: i32, link_path: *const i8), (Readlinkat = 267) => handle_unsupported(), (Fchmodat = 268) => handle_unsupported(), (Faccessat = 269) => do_faccessat(dirfd: i32, path: *const i8, mode: u32, flags: u32), diff --git a/test/symlink/main.c b/test/symlink/main.c index 6a819cd7..79c302a6 100644 --- a/test/symlink/main.c +++ b/test/symlink/main.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "test.h" // ============================================================================ @@ -84,7 +85,6 @@ static int __test_realpath(const char *file_path) { char dirc[128] = { 0 }; char basec[128] = { 0 }; char *dir_name, *file_name, *res; - int ret; if (snprintf(dirc, sizeof(dirc), "%s", file_path) < 0 || snprintf(basec, sizeof(dirc), "%s", file_path) < 0) { @@ -92,8 +92,7 @@ static int __test_realpath(const char *file_path) { } dir_name = dirname(dirc); file_name = basename(basec); - ret = chdir(dir_name); - if (ret < 0) { + if (chdir(dir_name) < 0) { THROW_ERROR("failed to chdir to %s", dir_name); } res = realpath(file_name, buf); @@ -106,6 +105,9 @@ static int __test_realpath(const char *file_path) { if (strncmp(buf, file_path, strlen(buf)) != 0) { THROW_ERROR("check the realpath for '%s' failed", file_name); } + if (chdir("/") < 0) { + THROW_ERROR("failed to chdir to '/'"); + } return 0; } @@ -136,7 +138,6 @@ static int test_realpath() { return test_readlink_framework(__test_realpath); } - static int test_readlink_from_proc_self_exe() { char exe_buf[128] = { 0 }; char absolute_path[128] = { 0 }; @@ -160,6 +161,193 @@ static int test_readlink_from_proc_self_exe() { return 0; } +// ============================================================================ +// Test cases for symlink +// ============================================================================ + +static int __test_symlink(const char *target, const char *link_path) { + char dir_buf[128] = { 0 }; + char *dir_name; + char target_path[256] = { 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); + snprintf(target_path, sizeof(target_path), "%s/%s", dir_name, target); + } + if (create_file(target_path) < 0) { + THROW_ERROR("failed to create target file"); + } + + int fd = open(target_path, 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 (symlink(target, link_path) < 0) { + THROW_ERROR("failed to create symlink"); + } + + fd = open(link_path, O_RDONLY | O_NOFOLLOW); + if (fd >= 0 || errno != ELOOP) { + THROW_ERROR("failed to check open file with O_NOFOLLOW flags"); + } + + 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); + + char readlink_buf[256] = { 0 }; + if (readlink(link_path, readlink_buf, sizeof(readlink_buf)) < 0) { + THROW_ERROR("readlink failed"); + } + if (strcmp(target, readlink_buf) != 0) { + THROW_ERROR("check readlink result failed"); + } + + if (remove_file(target_path) < 0) { + THROW_ERROR("failed to delete target file"); + } + + return 0; +} + +static int __test_create_file_from_symlink(const char *target, const char *link_path) { + char dir_buf[128] = { 0 }; + char *dir_name; + char target_path[256] = { 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); + snprintf(target_path, sizeof(target_path), "%s/%s", dir_name, target); + } + + if (symlink(target, link_path) < 0) { + THROW_ERROR("failed to create symlink"); + } + + int fd = open(link_path, O_RDONLY, 00666); + if (fd >= 0 || errno != ENOENT) { + THROW_ERROR("failed to check open a dangling symbolic link"); + } + + if (create_file(link_path) < 0) { + THROW_ERROR("failed to create link file"); + } + struct stat stat_buf; + if (stat(target_path, &stat_buf) < 0) { + THROW_ERROR("failed to stat the target file"); + } + + if (remove_file(target_path) < 0) { + THROW_ERROR("failed to delete target file"); + } + + return 0; +} + +typedef int(*test_symlink_func_t)(const char *, const char *); + +static int test_symlink_framework(test_symlink_func_t fn, const char *target, + const char *link) { + if (fn(target, link) < 0) { + return -1; + } + if (remove_file(link) < 0) { + return -1; + } + + return 0; +} + +static int test_symlink_to_absolute_target() { + char *target = "/root/test_symlink.file"; + char *link = "/root/test_symlink.link"; + return test_symlink_framework(__test_symlink, target, link); +} + +static int test_symlink_to_relative_target() { + char *target = "./test_symlink.file"; + char *link = "/root/test_symlink.link"; + if (test_symlink_framework(__test_symlink, target, link) < 0) { + return -1; + } + target = "../root/test_symlink.file"; + if (test_symlink_framework(__test_symlink, target, link) < 0) { + return -1; + } + return 0; +} + +static int test_symlink_from_ramfs() { + char *target = "/root/test_symlink.file"; + char *link = "/tmp/test_symlink.link"; + return test_symlink_framework(__test_symlink, target, link); +} + +static int test_symlink_to_ramfs() { + char *target = "/tmp/test_symlink.file"; + char *link = "/root/test_symlink.link"; + return test_symlink_framework(__test_symlink, target, link); +} + +static int test_symlink_with_empty_target_or_link_path() { + char *target = "/root/test_symlink.file"; + char *link_path = "/root/test_symlink.link"; + + int ret = symlink("", link_path); + if (ret >= 0 || errno != ENOENT) { + THROW_ERROR("failed to check symlink with empty target"); + } + ret = symlink(target, ""); + if (ret >= 0 || errno != ENOENT) { + THROW_ERROR("failed to check symlink with empty linkpath"); + } + return 0; +} + +static int test_create_file_from_symlink_to_absolute_target() { + char *target = "/root/test_symlink.file"; + char *link = "/root/test_symlink.link"; + return test_symlink_framework(__test_create_file_from_symlink, target, link); +} + +static int test_create_file_from_symlink_to_relative_target() { + char *target = "test_symlink.file"; + char *link = "/root/test_symlink.link"; + if (test_symlink_framework(__test_create_file_from_symlink, target, link) < 0) { + return -1; + } + target = "../root/test_symlink.file"; + if (test_symlink_framework(__test_create_file_from_symlink, target, link) < 0) { + return -1; + } + return 0; +} + // ============================================================================ // Test suite main // ============================================================================ @@ -168,6 +356,13 @@ 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_symlink_to_absolute_target), + TEST_CASE(test_symlink_to_relative_target), + TEST_CASE(test_symlink_from_ramfs), + TEST_CASE(test_symlink_to_ramfs), + TEST_CASE(test_symlink_with_empty_target_or_link_path), + TEST_CASE(test_create_file_from_symlink_to_absolute_target), + TEST_CASE(test_create_file_from_symlink_to_relative_target), }; int main(int argc, const char *argv[]) {