From 61cf75e68b5a46ede1e970b73e5651777113f2c0 Mon Sep 17 00:00:00 2001 From: LI Qing Date: Fri, 13 Dec 2019 02:51:11 +0000 Subject: [PATCH] Add readlink from /proc/self/fd/ to get file paths * Fix readlink from `/proc/self/exe` to get absolute path of the executable file * Add readlink from`/proc/self/fd/` to get the file's real path Note that for now we only support read links _statically_, meaning that even if the file or any of its ancestors is moved after the file is opened, the absolute paths obtained from the API does not change. --- src/libos/src/fs/inode_file.rs | 23 +++- src/libos/src/fs/mod.rs | 59 ++++++++-- src/libos/src/process/mod.rs | 1 + src/libos/src/process/process.rs | 6 + src/libos/src/process/spawn/mod.rs | 2 +- src/libos/src/process/thread.rs | 3 +- test/Makefile | 2 +- test/include/test.h | 6 +- test/symlink/Makefile | 5 + test/symlink/main.c | 173 +++++++++++++++++++++++++++++ 10 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 test/symlink/Makefile create mode 100644 test/symlink/main.c diff --git a/src/libos/src/fs/inode_file.rs b/src/libos/src/fs/inode_file.rs index eb9d1694..a267ac24 100644 --- a/src/libos/src/fs/inode_file.rs +++ b/src/libos/src/fs/inode_file.rs @@ -5,6 +5,7 @@ use std::fmt; pub struct INodeFile { inode: Arc, + abs_path: String, offset: SgxMutex, access_mode: AccessMode, status_flags: SgxRwLock, @@ -164,7 +165,7 @@ impl File for INodeFile { } impl INodeFile { - pub fn open(inode: Arc, flags: u32) -> Result { + pub fn open(inode: Arc, abs_path: &str, flags: u32) -> Result { let access_mode = AccessMode::from_u32(flags)?; if (access_mode.readable() && !inode.allow_read()?) { return_errno!(EBADF, "File not readable"); @@ -175,18 +176,24 @@ impl INodeFile { let status_flags = StatusFlags::from_bits_truncate(flags); Ok(INodeFile { inode, + abs_path: abs_path.to_owned(), offset: SgxMutex::new(0), access_mode, status_flags: SgxRwLock::new(status_flags), }) } + + pub fn get_abs_path(&self) -> &str { + &self.abs_path + } } impl Debug for INodeFile { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "INodeFile {{ inode: ???, pos: {}, access_mode: {:?}, status_flags: {:#o} }}", + "INodeFile {{ inode: ???, abs_path: {}, pos: {}, access_mode: {:?}, status_flags: {:#o} }}", + self.abs_path, *self.offset.lock().unwrap(), self.access_mode, *self.status_flags.read().unwrap() @@ -225,3 +232,15 @@ impl INodeExt for INode { Ok(readable) } } + +pub trait AsINodeFile { + fn as_inode_file(&self) -> Result<&INodeFile>; +} + +impl AsINodeFile for FileRef { + fn as_inode_file(&self) -> Result<&INodeFile> { + self.as_any() + .downcast_ref::() + .ok_or_else(|| errno!(EBADF, "not an inode file")) + } +} diff --git a/src/libos/src/fs/mod.rs b/src/libos/src/fs/mod.rs index 80490f20..27be5bfe 100644 --- a/src/libos/src/fs/mod.rs +++ b/src/libos/src/fs/mod.rs @@ -16,7 +16,7 @@ pub use self::fcntl::FcntlCmd; pub use self::file::{File, FileRef, SgxFile, StdinFile, StdoutFile}; pub use self::file_flags::{AccessMode, CreationFlags, StatusFlags}; pub use self::file_table::{FileDesc, FileTable}; -pub use self::inode_file::{INodeExt, INodeFile}; +pub use self::inode_file::{AsINodeFile, INodeExt, INodeFile}; pub use self::io_multiplexing::*; pub use self::ioctl::*; pub use self::pipe::Pipe; @@ -319,6 +319,7 @@ pub fn do_rename(oldpath: &str, newpath: &str) -> Result<()> { let (new_dir_path, new_file_name) = split_path(&newpath); let old_dir_inode = current_process.lookup_inode(old_dir_path)?; let new_dir_inode = current_process.lookup_inode(new_dir_path)?; + // TODO: support to modify file's absolute path old_dir_inode.move_(old_file_name, &new_dir_inode, new_file_name)?; Ok(()) } @@ -475,7 +476,8 @@ impl Process { } else { self.lookup_inode(&path)? }; - Ok(Box::new(INodeFile::open(inode, flags)?)) + 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 @@ -493,6 +495,27 @@ impl Process { Ok(inode) } } + + /// Convert the path to be absolute + pub fn convert_to_abs_path(&self, path: &str) -> String { + debug!( + "convert_to_abs_path: cwd: {:?}, path: {:?}", + self.get_cwd(), + path + ); + if path.len() > 0 && path.as_bytes()[0] == b'/' { + // path is absolute path already + return path.to_owned(); + } + let cwd = { + if !self.get_cwd().ends_with("/") { + self.get_cwd().to_owned() + "/" + } else { + self.get_cwd().to_owned() + } + }; + cwd + path + } } /// Split a `path` str to `(base_path, file_name)` @@ -703,19 +726,31 @@ pub fn do_fcntl(fd: FileDesc, cmd: &FcntlCmd) -> Result { pub fn do_readlink(path: &str, buf: &mut [u8]) -> Result { info!("readlink: path: {:?}", path); - match path { - "/proc/self/exe" => { - // get cwd + let file_path = { + if path == "/proc/self/exe" { let current_ref = process::get_current(); let current = current_ref.lock().unwrap(); - let cwd = current.get_cwd(); - let len = cwd.len().min(buf.len()); - buf[0..len].copy_from_slice(&cwd.as_bytes()[0..len]); - Ok(0) - } - _ => { + current.get_elf_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"))?; + let current_ref = process::get_current(); + let current = current_ref.lock().unwrap(); + let file_ref = current.get_files().lock().unwrap().get(fd)?; + if let Ok(inode_file) = file_ref.as_inode_file() { + inode_file.get_abs_path().to_owned() + } else { + // TODO: support special device files + return_errno!(EINVAL, "not a normal file link") + } + } else { // TODO: support symbolic links return_errno!(EINVAL, "not a symbolic link") } - } + }; + let len = file_path.len().min(buf.len()); + buf[0..len].copy_from_slice(&file_path.as_bytes()[0..len]); + Ok(len) } diff --git a/src/libos/src/process/mod.rs b/src/libos/src/process/mod.rs index 2dcced64..572340c8 100644 --- a/src/libos/src/process/mod.rs +++ b/src/libos/src/process/mod.rs @@ -26,6 +26,7 @@ pub struct Process { // TODO: move cwd, root_inode into a FileSystem structure // TODO: should cwd be a String or INode? cwd: String, + elf_path: String, clear_child_tid: Option<*mut pid_t>, parent: Option, children: Vec, diff --git a/src/libos/src/process/process.rs b/src/libos/src/process/process.rs index c71302d4..cf6840cc 100644 --- a/src/libos/src/process/process.rs +++ b/src/libos/src/process/process.rs @@ -15,6 +15,7 @@ lazy_static! { host_tid: 0, exit_status: 0, cwd: "/".to_owned(), + elf_path: "/".to_owned(), clear_child_tid: None, parent: None, children: Vec::new(), @@ -29,6 +30,7 @@ lazy_static! { impl Process { pub fn new( cwd: &str, + elf_path: &str, task: Task, vm_ref: ProcessVMRef, file_table_ref: FileTableRef, @@ -43,6 +45,7 @@ impl Process { tgid: new_pid, host_tid: 0, cwd: cwd.to_owned(), + elf_path: elf_path.to_owned(), clear_child_tid: None, exit_status: 0, parent: None, @@ -87,6 +90,9 @@ impl Process { pub fn get_cwd(&self) -> &str { &self.cwd } + pub fn get_elf_path(&self) -> &str { + &self.elf_path + } pub fn get_vm(&self) -> &ProcessVMRef { &self.vm } diff --git a/src/libos/src/process/spawn/mod.rs b/src/libos/src/process/spawn/mod.rs index 97c9b4eb..999ac1a0 100644 --- a/src/libos/src/process/spawn/mod.rs +++ b/src/libos/src/process/spawn/mod.rs @@ -82,7 +82,7 @@ pub fn do_spawn( Arc::new(SgxMutex::new(files)) }; let rlimits_ref = Default::default(); - Process::new(&cwd, task, vm_ref, files_ref, rlimits_ref)? + Process::new(&cwd, elf_path, task, vm_ref, files_ref, rlimits_ref)? }; parent_adopts_new_child(&parent_ref, &new_process_ref); process_table::put(new_pid, new_process_ref.clone()); diff --git a/src/libos/src/process/thread.rs b/src/libos/src/process/thread.rs index 7e8cc361..e4eaf120 100644 --- a/src/libos/src/process/thread.rs +++ b/src/libos/src/process/thread.rs @@ -77,8 +77,9 @@ pub fn do_clone( }; let files_ref = current.get_files().clone(); let rlimits_ref = current.get_rlimits().clone(); + let elf_path = ¤t.elf_path; let cwd = ¤t.cwd; - Process::new(cwd, task, vm_ref, files_ref, rlimits_ref)? + Process::new(cwd, elf_path, task, vm_ref, files_ref, rlimits_ref)? }; if let Some(ctid) = ctid { diff --git a/test/Makefile b/test/Makefile index 2153bdfa..0d90a9fc 100644 --- a/test/Makefile +++ b/test/Makefile @@ -6,7 +6,7 @@ BUILD_DIR := $(PROJECT_DIR)/build TEST_DEPS := client data_sink # Tests: need to be compiled and run by test-% target TESTS := empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \ - truncate readdir mkdir link tls pthread uname rlimit server \ + truncate readdir mkdir link symlink tls pthread uname rlimit server \ server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \ ioctl fcntl # Benchmarks: need to be compiled and run by bench-% target diff --git a/test/include/test.h b/test/include/test.h index a04149c1..b188414d 100644 --- a/test/include/test.h +++ b/test/include/test.h @@ -19,9 +19,9 @@ typedef struct { #define TEST_CASE(name) { STR(name), name } -#define THROW_ERROR(msg) do { \ - printf("\t\tERROR: %s in func %s at line %d of file %s\n", \ - (msg), __func__, __LINE__, __FILE__); \ +#define THROW_ERROR(fmt, ...) do { \ + printf("\t\tERROR:" fmt " in func %s at line %d of file %s\n", \ + ##__VA_ARGS__, __func__, __LINE__, __FILE__); \ return -1; \ } while (0) diff --git a/test/symlink/Makefile b/test/symlink/Makefile new file mode 100644 index 00000000..9e1b6dec --- /dev/null +++ b/test/symlink/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/symlink/main.c b/test/symlink/main.c new file mode 100644 index 00000000..5ad5ed96 --- /dev/null +++ b/test/symlink/main.c @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +// ============================================================================ +// Helper variable and function +// ============================================================================ +const char **g_argv; + +static ssize_t get_path_by_fd(int fd, char *buf, ssize_t buf_len) { + char proc_fd[64] = { 0 }; + int n; + + n = snprintf(proc_fd, sizeof(proc_fd), "/proc/self/fd/%d", fd); + if (n < 0) { + THROW_ERROR("failed to call snprintf for %d", fd); + } + + return readlink(proc_fd, buf, buf_len); +} + +static int create_file(const char *file_path) { + int fd; + int flags = O_RDONLY | O_CREAT| O_TRUNC; + int mode = 00666; + + fd = open(file_path, flags, mode); + if (fd < 0) { + THROW_ERROR("failed to create a file"); + } + close(fd); + + return 0; +} + +static int remove_file(const char *file_path) { + int ret; + + ret = unlink(file_path); + if (ret < 0) { + THROW_ERROR("failed to unlink the created file"); + } + + return 0; +} + +// ============================================================================ +// Test cases for readlink +// ============================================================================ + +static int __test_readlink_from_proc_self_fd(const char *file_path) { + char buf[128] = { 0 }; + int fd; + ssize_t n; + + fd = open(file_path, O_RDONLY); + if (fd < 0) { + THROW_ERROR("failed to open `%s` for read", file_path); + } + n = get_path_by_fd(fd, buf, sizeof(buf)); + close(fd); + if (n < 0) { + THROW_ERROR("failed to readlink for `%s`", file_path); + } + if (n != strlen(file_path)) { + THROW_ERROR("readlink for `%s` length is wrong", file_path); + } + if (strncmp(buf, file_path, n) != 0) { + THROW_ERROR("check the path for `%s` failed", file_path); + } + + return 0; +} + +static int __test_realpath(const char *file_path) { + char buf[128] = { 0 }; + 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) { + THROW_ERROR("failed to copy file path"); + } + dir_name = dirname(dirc); + file_name = basename(basec); + ret = chdir(dir_name); + if (ret < 0) { + THROW_ERROR("failed to chdir to %s", dir_name); + } + res = realpath(file_name, buf); + if (res == NULL) { + THROW_ERROR("failed to get the realpath for `%s`", file_name); + } + if (strlen(buf) != strlen(file_path)) { + THROW_ERROR("realpath for '%s' length is wrong", file_name); + } + if (strncmp(buf, file_path, strlen(buf)) != 0) { + THROW_ERROR("check the realpath for '%s' failed", file_name); + } + + return 0; +} + +typedef int(*test_readlink_func_t)(const char *); + +static int test_readlink_framework(test_readlink_func_t fn) { + const char *file_path = "/root/test_filesystem_symlink.txt"; + + if (create_file(file_path) < 0) + return -1; + if (fn(file_path) < 0) + return -1; + if (remove_file(file_path) < 0) + return -1; + + return 0; +} + +static int test_readlink_from_proc_self_fd() { + return test_readlink_framework(__test_readlink_from_proc_self_fd); +} + +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 }; + const char *proc_exe = "/proc/self/exe"; + ssize_t n; + + n = snprintf(absolute_path, sizeof(absolute_path), "/bin/%s", *g_argv); + if (n < 0) { + THROW_ERROR("failed to call snprintf"); + } + n = readlink(proc_exe, exe_buf, sizeof(exe_buf)); + if (n < 0) { + THROW_ERROR("failed to readlink from %s", proc_exe); + } else if (n != strlen(absolute_path)) { + THROW_ERROR("readlink from %s length is wrong", proc_exe); + } + if (strncmp(exe_buf, absolute_path, n) != 0) { + THROW_ERROR("check the absolute path from %s failed", proc_exe); + } + + return 0; +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +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), +}; + +int main(int argc, const char *argv[]) { + g_argv = argv; + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +}