Add support to handle symbolic link file

This commit is contained in:
LI Qing 2020-06-11 13:40:37 +08:00
parent 3cd46fd224
commit 1ad8f22170
16 changed files with 418 additions and 56 deletions

2
deps/sefs vendored

@ -1 +1 @@
Subproject commit cc6f694c0ac27755a1b0e1546022d598eb53ca76 Subproject commit 2420622f050efda627ccebcff6e86a71dcf405f5

@ -95,6 +95,7 @@ impl ToErrno for rcore_fs::vfs::FsError {
FsError::WrProtected => EROFS, FsError::WrProtected => EROFS,
FsError::NoIntegrity => EIO, FsError::NoIntegrity => EIO,
FsError::PermError => EPERM, FsError::PermError => EPERM,
FsError::NameTooLong => ENAMETOOLONG,
} }
} }
} }

@ -49,7 +49,7 @@ pub fn do_faccessat(
); );
if Path::new(path).is_absolute() { if Path::new(path).is_absolute() {
// Path is absolute, so dirfd is ignored // Path is absolute, so dirfd is ignored
return Ok(do_access(path, mode)?); return Ok(do_access(path, mode, flags)?);
} }
let path = match dirfd { let path = match dirfd {
DirFd::Fd(dirfd) => { DirFd::Fd(dirfd) => {
@ -58,14 +58,22 @@ pub fn do_faccessat(
} }
DirFd::Cwd => path.to_owned(), 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 inode = {
let current = current!(); let current = current!();
let fs = current.fs().lock().unwrap(); let fs = current.fs().lock().unwrap();
if flags.contains(AccessibilityCheckFlags::AT_SYMLINK_NOFOLLOW) {
fs.lookup_inode_no_follow(path)?
} else {
fs.lookup_inode(path)? fs.lookup_inode(path)?
}
}; };
if mode.test_for_exist() { if mode.test_for_exist() {
return Ok(()); return Ok(());

@ -1,8 +1,17 @@
use super::*; use super::*;
pub fn do_chown(path: &str, uid: u32, gid: u32) -> Result<()> { pub fn do_chown(path: &str, uid: u32, gid: u32) -> Result<()> {
warn!("chown is partial implemented as lchown"); debug!("chown: path: {:?}, uid: {}, gid: {}", path, uid, gid);
do_lchown(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<()> { 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 inode = {
let current = current!(); let current = current!();
let fs = current.fs().lock().unwrap(); let fs = current.fs().lock().unwrap();
fs.lookup_inode(path)? fs.lookup_inode_no_follow(path)?
}; };
let mut info = inode.metadata()?; let mut info = inode.metadata()?;
info.uid = uid as usize; info.uid = uid as usize;

@ -71,6 +71,10 @@ impl CreationFlags {
pub fn is_exclusive(&self) -> bool { pub fn is_exclusive(&self) -> bool {
self.contains(CreationFlags::O_EXCL) self.contains(CreationFlags::O_EXCL)
} }
pub fn no_follow_symlink(&self) -> bool {
self.contains(CreationFlags::O_NOFOLLOW)
}
} }
bitflags! { bitflags! {
@ -99,4 +103,8 @@ impl StatusFlags {
pub fn always_append(&self) -> bool { pub fn always_append(&self) -> bool {
self.contains(StatusFlags::O_APPEND) self.contains(StatusFlags::O_APPEND)
} }
pub fn is_fast_open(&self) -> bool {
self.contains(StatusFlags::O_PATH)
}
} }

@ -7,7 +7,7 @@ pub fn do_link(oldpath: &str, newpath: &str) -> Result<()> {
let (inode, new_dir_inode) = { let (inode, new_dir_inode) = {
let current = current!(); let current = current!();
let fs = current.fs().lock().unwrap(); 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)?; let new_dir_inode = fs.lookup_inode(new_dir_path)?;
(inode, new_dir_inode) (inode, new_dir_inode)
}; };

@ -25,7 +25,7 @@ pub use self::rename::do_rename;
pub use self::rmdir::do_rmdir; pub use self::rmdir::do_rmdir;
pub use self::sendfile::do_sendfile; pub use self::sendfile::do_sendfile;
pub use self::stat::{do_fstat, do_fstatat, do_lstat, Stat, StatFlags}; 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::truncate::{do_ftruncate, do_truncate};
pub use self::unlink::do_unlink; pub use self::unlink::do_unlink;
pub use self::write::{do_pwrite, do_write, do_writev}; pub use self::write::{do_pwrite, do_write, do_writev};

@ -139,7 +139,7 @@ fn do_stat(path: &str) -> Result<Stat> {
let inode = { let inode = {
let current = current!(); let current = current!();
let fs = current.fs().lock().unwrap(); let fs = current.fs().lock().unwrap();
fs.lookup_inode_follow(&path)? fs.lookup_inode(&path)?
}; };
let stat = Stat::from(inode.metadata()?); let stat = Stat::from(inode.metadata()?);
Ok(stat) Ok(stat)
@ -149,7 +149,6 @@ pub fn do_fstat(fd: u32) -> Result<Stat> {
debug!("fstat: fd: {}", fd); debug!("fstat: fd: {}", fd);
let file_ref = current!().file(fd as FileDesc)?; let file_ref = current!().file(fd as FileDesc)?;
let stat = Stat::from(file_ref.metadata()?); let stat = Stat::from(file_ref.metadata()?);
// TODO: handle symlink
Ok(stat) Ok(stat)
} }
@ -158,7 +157,7 @@ pub fn do_lstat(path: &str) -> Result<Stat> {
let inode = { let inode = {
let current = current!(); let current = current!();
let fs = current.fs().lock().unwrap(); let fs = current.fs().lock().unwrap();
fs.lookup_inode(&path)? fs.lookup_inode_no_follow(&path)?
}; };
let stat = Stat::from(inode.metadata()?); let stat = Stat::from(inode.metadata()?);
Ok(stat) Ok(stat)
@ -185,9 +184,14 @@ pub fn do_fstatat(dirfd: DirFd, path: &str, flags: StatFlags) -> Result<Stat> {
} else { } else {
let dir_path = get_dir_path(dirfd)?; let dir_path = get_dir_path(dirfd)?;
let path = dir_path + "/" + path; let path = dir_path + "/" + path;
if !flags.contains(StatFlags::AT_SYMLINK_NOFOLLOW) {
do_stat(&path) 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),
} }
} }

@ -18,11 +18,60 @@ pub fn do_readlink(path: &str, buf: &mut [u8]) -> Result<usize> {
return_errno!(EINVAL, "not a normal file link") return_errno!(EINVAL, "not a normal file link")
} }
} else { } else {
// TODO: support symbolic links let inode = {
return_errno!(EINVAL, "not a symbolic link") 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()); let len = file_path.len().min(buf.len());
buf[0..len].copy_from_slice(&file_path.as_bytes()[0..len]); buf[0..len].copy_from_slice(&file_path.as_bytes()[0..len]);
Ok(len) Ok(len)
} }
fn do_symlink(target: &str, link_path: &str) -> Result<usize> {
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<usize> {
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)
}

@ -53,64 +53,120 @@ impl FsView {
return Ok(Box::new(DevSgx)); return Ok(Box::new(DevSgx));
} }
let creation_flags = CreationFlags::from_bits_truncate(flags); let creation_flags = CreationFlags::from_bits_truncate(flags);
let inode = if creation_flags.can_create() { let inode = if creation_flags.no_follow_symlink() {
let (dir_path, file_name) = split_path(&path); match self.lookup_inode_no_follow(path) {
let dir_inode = self.lookup_inode(dir_path)?; Ok(inode) => {
match dir_inode.find(file_name) { let status_flags = StatusFlags::from_bits_truncate(flags);
Ok(file_inode) => { if inode.metadata()?.type_ == FileType::SymLink && !status_flags.is_fast_open()
if creation_flags.is_exclusive() { {
return_errno!(ELOOP, "file is a symlink");
}
if creation_flags.can_create() && creation_flags.is_exclusive() {
return_errno!(EEXIST, "file exists"); 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()? { if !dir_inode.allow_write()? {
return_errno!(EPERM, "file cannot be created"); return_errno!(EPERM, "file cannot be created");
} }
dir_inode.create(file_name, FileType::File, mode)? dir_inode.create(file_name, FileType::File, mode)?
} }
Err(e) => return Err(Error::from(e)), Err(e) => return Err(e),
} }
} else { } 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); let abs_path = self.convert_to_abs_path(&path);
Ok(Box::new(INodeFile::open(inode, &abs_path, flags)?)) Ok(Box::new(INodeFile::open(inode, &abs_path, flags)?))
} }
/// Lookup INode from the cwd of the process /// Recursively lookup the real path of giving path, dereference symlinks
pub fn lookup_inode(&self, path: &str) -> Result<Arc<dyn INode>> { pub fn lookup_real_path(&self, path: &str) -> Result<String> {
self.lookup_inode_follow_with_max_times(path, 0) 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 /// Lookup INode from the cwd of the process. If path is a symlink, do not dereference it
pub fn lookup_inode_follow(&self, path: &str) -> Result<Arc<dyn INode>> { pub fn lookup_inode_no_follow(&self, path: &str) -> Result<Arc<dyn INode>> {
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<Arc<dyn INode>> {
// Linux uses 40 as the upper limit for resolving symbolic links, // Linux uses 40 as the upper limit for resolving symbolic links,
// so Occlum use it as a reasonable value // so Occlum use it as a reasonable value
const MAX_SYMLINKS: usize = 40; 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<Arc<dyn INode>> {
debug!( debug!(
"lookup_inode_follow_with_max_times: cwd: {:?}, path: {:?}, max_times: {}", "lookup_inode_follow: cwd: {:?}, path: {:?}",
self.cwd(), self.cwd(),
path, path
max_times
); );
if path.len() > 0 && path.as_bytes()[0] == b'/' { if path.len() > 0 && path.as_bytes()[0] == b'/' {
// absolute path // absolute path
let abs_path = path.trim_start_matches('/'); 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) Ok(inode)
} else { } else {
// relative path // relative path
let cwd = self.cwd().trim_start_matches('/'); 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) Ok(inode)
} }
} }

@ -127,7 +127,10 @@ impl INode for HNode {
FileType::File => { FileType::File => {
try_std!(fs::File::create(&new_path)); 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 { Ok(Arc::new(HNode {
path: new_path, path: new_path,

@ -1,7 +1,7 @@
use super::*; use super::*;
use process; 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;
use std::any::Any; use std::any::Any;
use std::fmt; use std::fmt;

@ -388,6 +388,34 @@ pub fn do_readlink(path: *const i8, buf: *mut u8, size: usize) -> Result<isize>
Ok(len as isize) Ok(len as isize)
} }
pub fn do_symlink(target: *const i8, link_path: *const i8) -> Result<isize> {
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<isize> {
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<isize> { pub fn do_chmod(path: *const i8, mode: u16) -> Result<isize> {
let path = from_user::clone_cstring_safely(path)? let path = from_user::clone_cstring_safely(path)?
.to_string_lossy() .to_string_lossy()

@ -64,7 +64,7 @@ pub fn load_file_to_vec(file_path: &str, current_ref: &ThreadRef) -> Result<Vec<
.fs() .fs()
.lock() .lock()
.unwrap() .unwrap()
.lookup_inode_follow(file_path) .lookup_inode(file_path)
.map_err(|e| errno!(e.errno(), "cannot find the file"))?; .map_err(|e| errno!(e.errno(), "cannot find the file"))?;
let file_mode = { let file_mode = {
let info = inode.metadata()?; let info = inode.metadata()?;

@ -22,8 +22,9 @@ use crate::fs::{
do_eventfd2, do_faccessat, do_fchmod, do_fchown, do_fcntl, do_fdatasync, do_fstat, do_fstatat, 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_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_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_sync, do_truncate, do_readlink, do_readv, do_rename, do_rmdir, do_sendfile, do_stat, do_symlink, do_symlinkat,
do_unlink, do_write, do_writev, iovec_t, File, FileDesc, FileRef, HostStdioFds, Stat, do_sync, do_truncate, do_unlink, do_write, do_writev, iovec_t, File, FileDesc, FileRef,
HostStdioFds, Stat,
}; };
use crate::misc::{resource_t, rlimit_t, sysinfo_t, utsname_t}; use crate::misc::{resource_t, rlimit_t, sysinfo_t, utsname_t};
use crate::net::{ use crate::net::{
@ -165,7 +166,7 @@ macro_rules! process_syscall_table_with_callback {
(Creat = 85) => handle_unsupported(), (Creat = 85) => handle_unsupported(),
(Link = 86) => do_link(oldpath: *const i8, newpath: *const i8), (Link = 86) => do_link(oldpath: *const i8, newpath: *const i8),
(Unlink = 87) => do_unlink(path: *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), (Readlink = 89) => do_readlink(path: *const i8, buf: *mut u8, size: usize),
(Chmod = 90) => do_chmod(path: *const i8, mode: u16), (Chmod = 90) => do_chmod(path: *const i8, mode: u16),
(Fchmod = 91) => do_fchmod(fd: FileDesc, 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(), (Unlinkat = 263) => handle_unsupported(),
(Renameat = 264) => handle_unsupported(), (Renameat = 264) => handle_unsupported(),
(Linkat = 265) => 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(), (Readlinkat = 267) => handle_unsupported(),
(Fchmodat = 268) => handle_unsupported(), (Fchmodat = 268) => handle_unsupported(),
(Faccessat = 269) => do_faccessat(dirfd: i32, path: *const i8, mode: u32, flags: u32), (Faccessat = 269) => do_faccessat(dirfd: i32, path: *const i8, mode: u32, flags: u32),

@ -7,6 +7,7 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <errno.h>
#include "test.h" #include "test.h"
// ============================================================================ // ============================================================================
@ -84,7 +85,6 @@ static int __test_realpath(const char *file_path) {
char dirc[128] = { 0 }; char dirc[128] = { 0 };
char basec[128] = { 0 }; char basec[128] = { 0 };
char *dir_name, *file_name, *res; char *dir_name, *file_name, *res;
int ret;
if (snprintf(dirc, sizeof(dirc), "%s", file_path) < 0 || if (snprintf(dirc, sizeof(dirc), "%s", file_path) < 0 ||
snprintf(basec, 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); dir_name = dirname(dirc);
file_name = basename(basec); file_name = basename(basec);
ret = chdir(dir_name); if (chdir(dir_name) < 0) {
if (ret < 0) {
THROW_ERROR("failed to chdir to %s", dir_name); THROW_ERROR("failed to chdir to %s", dir_name);
} }
res = realpath(file_name, buf); 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) { if (strncmp(buf, file_path, strlen(buf)) != 0) {
THROW_ERROR("check the realpath for '%s' failed", file_name); THROW_ERROR("check the realpath for '%s' failed", file_name);
} }
if (chdir("/") < 0) {
THROW_ERROR("failed to chdir to '/'");
}
return 0; return 0;
} }
@ -136,7 +138,6 @@ static int test_realpath() {
return test_readlink_framework(__test_realpath); return test_readlink_framework(__test_realpath);
} }
static int test_readlink_from_proc_self_exe() { static int test_readlink_from_proc_self_exe() {
char exe_buf[128] = { 0 }; char exe_buf[128] = { 0 };
char absolute_path[128] = { 0 }; char absolute_path[128] = { 0 };
@ -160,6 +161,193 @@ static int test_readlink_from_proc_self_exe() {
return 0; 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 // Test suite main
// ============================================================================ // ============================================================================
@ -168,6 +356,13 @@ static test_case_t test_cases[] = {
TEST_CASE(test_readlink_from_proc_self_fd), TEST_CASE(test_readlink_from_proc_self_fd),
TEST_CASE(test_realpath), TEST_CASE(test_realpath),
TEST_CASE(test_readlink_from_proc_self_exe), 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[]) { int main(int argc, const char *argv[]) {