From 7a5c5e8f128c70184b60e57c839f938da152ecbd Mon Sep 17 00:00:00 2001 From: Bobby Marinov Date: Fri, 5 Jan 2024 03:29:42 +0000 Subject: [PATCH] Add support for symlinks for regular files - added support for system calls: link, linkat, symlink & symlinkat - added set_link() operation to libos_d_ops - implemented set_link() for chroot/encrypted & tmpfs filesystems - implemented follow_link() for chroot/encrypted & tmpfs - added symlink support to the chroot/encrypted filesystem - added hardlink and symlink support to tmp filesystem - added regression unit tests for link and symlinks - updated the copyright in the modified source files - added symlink information to the documentation Signed-off-by: Bobby Marinov --- Documentation/devel/features.md | 35 +- Documentation/devel/new-syscall.rst | 6 +- common/include/pal_error.h | 2 + common/src/pal_error.c | 2 + libos/include/libos_fs.h | 13 + libos/include/libos_table.h | 5 + libos/src/arch/x86_64/libos_table.c | 8 +- libos/src/fs/chroot/encrypted.c | 153 ++++++- libos/src/fs/chroot/fs.c | 20 +- libos/src/fs/dev/fs.c | 2 +- libos/src/fs/libos_fs_encrypted.c | 2 +- libos/src/fs/libos_namei.c | 9 + libos/src/fs/shm/fs.c | 2 +- libos/src/fs/tmpfs/fs.c | 128 ++++++ libos/src/ipc/libos_ipc.c | 2 +- libos/src/libos_init.c | 4 +- libos/src/libos_parser.c | 12 +- libos/src/libos_pollable_event.c | 2 +- libos/src/net/unix.c | 4 +- libos/src/sys/libos_eventfd.c | 2 +- libos/src/sys/libos_file.c | 186 +++++++- libos/src/sys/libos_pipe.c | 2 +- libos/test/regression/link_symlink.c | 556 +++++++++++++++++++++++ libos/test/regression/meson.build | 1 + libos/test/regression/test_libos.py | 117 +++++ libos/test/regression/tests.toml | 1 + libos/test/regression/tests_musl.toml | 1 + pal/include/pal/pal.h | 7 +- pal/include/pal_internal.h | 12 +- pal/regression/Directory.c | 16 +- pal/regression/File.c | 14 +- pal/regression/File2.c | 6 +- pal/regression/HelloWorld.c | 2 +- pal/regression/Pie.c | 2 +- pal/regression/Pipe.c | 4 +- pal/regression/Process4.c | 4 +- pal/regression/send_handle.c | 6 +- pal/src/host/linux-sgx/pal_console.c | 4 +- pal/src/host/linux-sgx/pal_devices.c | 3 +- pal/src/host/linux-sgx/pal_eventfd.c | 4 +- pal/src/host/linux-sgx/pal_files.c | 77 ++-- pal/src/host/linux-sgx/pal_linux_error.h | 4 + pal/src/host/linux-sgx/pal_pipes.c | 3 +- pal/src/host/linux/pal_console.c | 4 +- pal/src/host/linux/pal_devices.c | 3 +- pal/src/host/linux/pal_eventfd.c | 4 +- pal/src/host/linux/pal_files.c | 66 +-- pal/src/host/linux/pal_linux_error.h | 4 + pal/src/host/linux/pal_pipes.c | 3 +- pal/src/host/skeleton/pal_console.c | 3 +- pal/src/host/skeleton/pal_devices.c | 2 +- pal/src/host/skeleton/pal_eventfd.c | 3 +- pal/src/host/skeleton/pal_files.c | 4 +- pal/src/host/skeleton/pal_pipes.c | 2 +- pal/src/pal_main.c | 2 +- pal/src/pal_rtld.c | 2 +- pal/src/pal_streams.c | 14 +- 57 files changed, 1404 insertions(+), 157 deletions(-) create mode 100644 libos/test/regression/link_symlink.c diff --git a/Documentation/devel/features.md b/Documentation/devel/features.md index 265cd9b427..7279bb1cc4 100644 --- a/Documentation/devel/features.md +++ b/Documentation/devel/features.md @@ -419,13 +419,13 @@ The below list is generated from the [syscall table of Linux - ☑ `creat()` [9a](#file-system-operations) -- ☒ `link()` +- ☑ `link()` [9d](#hard-links-and-soft-links-symbolic-links) - ☑ `unlink()` [9a](#file-system-operations) -- ☒ `symlink()` +- ☑ `symlink()` [9d](#hard-links-and-soft-links-symbolic-links) - ▣ `readlink()` @@ -966,10 +966,10 @@ The below list is generated from the [syscall table of Linux - ▣ `renameat()` [9a](#file-system-operations) -- ☒ `linkat()` +- ☑ `linkat()` [9d](#hard-links-and-soft-links-symbolic-links) -- ☒ `symlinkat()` +- ☑ `symlinkat()` [9d](#hard-links-and-soft-links-symbolic-links) - ▣ `readlinkat()` @@ -2067,6 +2067,9 @@ Gramine supports creating files and directories (via `creat()`, `mkdir()`, `mkdi calls), reading directories (via `getdents()`), deleting files and directories (via `unlink()`, `unlinkat()`, `rmdir()`), renaming files and directories (via `rename()` and `renameat()`). +Gramine has a limited support for creating symbolic links for files and directories +(via `link()`, `linkat()`, `symlink()` and `symlinkat()` system calls). + Gramine supports read and write operations on files. Appending to files is currently unsupported. Writing to trusted files is prohibited. @@ -2293,21 +2296,27 @@ There are two notions that must be discussed separately: 1. Host OS's links: Gramine sees them as normal files. On Linux host, these links are currently always followed during directory/file lookup. -2. In-Gramine links: Gramine has no support for links (i.e., applications cannot create links). - - There is one exception: some pseudo-files like `/proc/[pid]/cwd` and `/proc/self`. +2. In-Gramine links: Gramine has a limited support for symlinks (i.e., applications can create +hard and soft links in certain file systems). + - The `chroot/encrypted` symlinks are passthrough symlinks that are stored on the host + filesystem encrypted. The `tmpfs` symlinks are stored inside the enclave memory and are + not visible from the outside. + - Hard links are only implemented for `tmpfs`. They are stored inside the enclave memory and + are not visible from the outside. + - Some pseudo-files like `/proc/[pid]/cwd` and `/proc/self` provide sym-links support foo but + those sym-links cannot be created nor destroyed. -The above means that Gramine does not implement `link()` and `symlink()` system calls. Support for -`readlink()` system call is limited to only pseudo-files' links mentioned above. +Support for `readlink()` system call is limited to only pseudo-files' links mentioned above. -Gramine may implement hard and soft links in the future. +Gramine may implement a complete support for hard and soft links in the future.
Related system calls -- ☒ `link()` -- ☒ `symlink()` +- ▣ `link()`: see note above +- ▣ `symlink()` - ▣ `readlink()`: see note above -- ☒ `linkat()` -- ☒ `symlinkat()` +- ▣ `linkat()` +- ▣ `symlinkat()` - ▣ `readlinkat()`: see note above - ☒ `lchown()` diff --git a/Documentation/devel/new-syscall.rst b/Documentation/devel/new-syscall.rst index 63711e849d..98dedac724 100644 --- a/Documentation/devel/new-syscall.rst +++ b/Documentation/devel/new-syscall.rst @@ -53,9 +53,9 @@ call. 4. Export new PAL calls from PAL binaries (optional) ---------------------------------------------------- -For each directory in :file:`PAL/host/`, there is a :file:`pal.map` file. This -file lists all the symbols accessible to the library OS. The new PAL call needs -to be listed here in order to be used by your system call implementation. +The :file:`pal/src/pal_symbols` file lists all the symbols accessible to the library +OS. The new PAL call needs to be listed here in order to be used by your system +call implementation. 5. Implement new PAL calls (optional) ------------------------------------- diff --git a/common/include/pal_error.h b/common/include/pal_error.h index b71a9ccc07..3ac4f995ab 100644 --- a/common/include/pal_error.h +++ b/common/include/pal_error.h @@ -34,6 +34,8 @@ typedef enum _pal_error_t { PAL_ERROR_CONNFAILED, PAL_ERROR_ADDRNOTEXIST, PAL_ERROR_AFNOSUPPORT, + PAL_ERROR_LOOP, + PAL_ERROR_NO_PERMISSION, PAL_ERROR_CONNFAILED_PIPE, #define PAL_ERROR_NATIVE_COUNT PAL_ERROR_CONNFAILED_PIPE diff --git a/common/src/pal_error.c b/common/src/pal_error.c index dfc2c0fe19..b2b31702cc 100644 --- a/common/src/pal_error.c +++ b/common/src/pal_error.c @@ -29,6 +29,8 @@ static const char* g_pal_error_list[] = { [PAL_ERROR_CONNFAILED] = "Connection failed (PAL_ERROR_CONNFAILED)", [PAL_ERROR_ADDRNOTEXIST] = "Resource address does not exist (PAL_ERROR_ADDRNOTEXIST)", [PAL_ERROR_AFNOSUPPORT] = "Address family not supported by protocol (PAL_ERROR_AFNOSUPPORT)", + [PAL_ERROR_LOOP] = "Symbolic link path with O_NOFOLLOW or too many symbolic links encountered (PAL_ERROR_LOOP)", + [PAL_ERROR_NO_PERMISSION] = "Operation not permitted (PAL_ERROR_NO_PERMISSION)", [PAL_ERROR_CONNFAILED_PIPE] = "Broken pipe (PAL_ERROR_CONNFAILED_PIPE)", [PAL_ERROR_CRYPTO_FEATURE_UNAVAILABLE] = "[Crypto] Feature not available (PAL_ERROR_CRYPTO_FEATURE_UNAVAILABLE)", diff --git a/libos/include/libos_fs.h b/libos/include/libos_fs.h index cbee338138..789767db4d 100644 --- a/libos/include/libos_fs.h +++ b/libos/include/libos_fs.h @@ -388,6 +388,19 @@ struct libos_d_ops { */ int (*follow_link)(struct libos_dentry* dent, char** out_target); + /* + * \brief Set up a link/symlink name to a dentry. + * + * \param dent Dentry (must be positive for hard links). + * \param target Link name. + * \param is_soft_link true if it is a soft/ymbolic link + * + * Set up hard/soft link name to a dentry. + * + * The caller should hold `g_dcache_lock`. + */ + int (*set_link)(struct libos_dentry* dent, const char* link, bool is_soft_link); + /* * \brief Change file permissions. * diff --git a/libos/include/libos_table.h b/libos/include/libos_table.h index aa6aaf25c7..90ce8229db 100644 --- a/libos/include/libos_table.h +++ b/libos/include/libos_table.h @@ -103,6 +103,8 @@ long libos_syscall_rename(const char* oldname, const char* newname); long libos_syscall_mkdir(const char* pathname, int mode); long libos_syscall_rmdir(const char* pathname); long libos_syscall_creat(const char* path, mode_t mode); +long libos_syscall_link(const char* target, const char* linkpath); +long libos_syscall_symlink(const char* target, const char* linkpath); long libos_syscall_unlink(const char* file); long libos_syscall_readlink(const char* file, char* buf, int bufsize); long libos_syscall_chmod(const char* filename, mode_t mode); @@ -176,6 +178,9 @@ long libos_syscall_mkdirat(int dfd, const char* pathname, int mode); long libos_syscall_newfstatat(int dirfd, const char* pathname, struct stat* statbuf, int flags); long libos_syscall_unlinkat(int dfd, const char* pathname, int flag); long libos_syscall_readlinkat(int dirfd, const char* file, char* buf, int bufsize); +long libos_syscall_linkat(int olddirfd, const char* target, int newdirfd, const char* linkpath, + int flags); +long libos_syscall_symlinkat(const char* target, int newdirfd, const char* linkpath); long libos_syscall_renameat(int olddfd, const char* pathname, int newdfd, const char* newname); long libos_syscall_fchmodat(int dfd, const char* filename, mode_t mode); long libos_syscall_fchownat(int dfd, const char* filename, uid_t user, gid_t group, int flags); diff --git a/libos/src/arch/x86_64/libos_table.c b/libos/src/arch/x86_64/libos_table.c index 86147ec29e..5041e787b6 100644 --- a/libos/src/arch/x86_64/libos_table.c +++ b/libos/src/arch/x86_64/libos_table.c @@ -100,9 +100,9 @@ libos_syscall_t libos_syscall_table[LIBOS_SYSCALL_BOUND] = { [__NR_mkdir] = (libos_syscall_t)libos_syscall_mkdir, [__NR_rmdir] = (libos_syscall_t)libos_syscall_rmdir, [__NR_creat] = (libos_syscall_t)libos_syscall_creat, - [__NR_link] = (libos_syscall_t)0, // libos_syscall_link + [__NR_link] = (libos_syscall_t)libos_syscall_link, [__NR_unlink] = (libos_syscall_t)libos_syscall_unlink, - [__NR_symlink] = (libos_syscall_t)0, // libos_syscall_symlink + [__NR_symlink] = (libos_syscall_t)libos_syscall_symlink, [__NR_readlink] = (libos_syscall_t)libos_syscall_readlink, [__NR_chmod] = (libos_syscall_t)libos_syscall_chmod, [__NR_fchmod] = (libos_syscall_t)libos_syscall_fchmod, @@ -279,8 +279,8 @@ libos_syscall_t libos_syscall_table[LIBOS_SYSCALL_BOUND] = { [__NR_newfstatat] = (libos_syscall_t)libos_syscall_newfstatat, [__NR_unlinkat] = (libos_syscall_t)libos_syscall_unlinkat, [__NR_renameat] = (libos_syscall_t)libos_syscall_renameat, - [__NR_linkat] = (libos_syscall_t)0, // libos_syscall_linkat - [__NR_symlinkat] = (libos_syscall_t)0, // libos_syscall_symlinkat + [__NR_linkat] = (libos_syscall_t)libos_syscall_linkat, + [__NR_symlinkat] = (libos_syscall_t)libos_syscall_symlinkat, [__NR_readlinkat] = (libos_syscall_t)libos_syscall_readlinkat, [__NR_fchmodat] = (libos_syscall_t)libos_syscall_fchmodat, [__NR_faccessat] = (libos_syscall_t)libos_syscall_faccessat, diff --git a/libos/src/fs/chroot/encrypted.c b/libos/src/fs/chroot/encrypted.c index 1e2852d5f3..1f578607d4 100644 --- a/libos/src/fs/chroot/encrypted.c +++ b/libos/src/fs/chroot/encrypted.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright (C) 2022 Intel Corporation + * Copyright (C) 2024 Fortanix, Inc. * Paweł Marczewski + * Bobby Marinov */ /* @@ -40,6 +42,8 @@ #include "stat.h" #include "toml_utils.h" +#define USEC_IN_SEC 1000000 + /* * Always add read and write permissions to files created on host. PAL requires opening the file * even for operations such as `unlink` or `chmod`, and the underlying `libos_fs_encrypted` module @@ -258,7 +262,7 @@ static int chroot_encrypted_mkdir(struct libos_dentry* dent, mode_t perm) { /* This opens a "dir:..." URI */ PAL_HANDLE palhdl; ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, HOST_PERM(perm), PAL_CREATE_ALWAYS, - PAL_OPTION_PASSTHROUGH, &palhdl); + PAL_OPTION_PASSTHROUGH, false, &palhdl); if (ret < 0) { ret = pal_to_unix_errno(ret); goto out; @@ -290,7 +294,7 @@ static int chroot_encrypted_unlink(struct libos_dentry* dent) { PAL_HANDLE palhdl; ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - PAL_OPTION_PASSTHROUGH, &palhdl); + PAL_OPTION_PASSTHROUGH, false, &palhdl); if (ret < 0) { ret = pal_to_unix_errno(ret); goto out; @@ -308,6 +312,147 @@ static int chroot_encrypted_unlink(struct libos_dentry* dent) { return ret; } +static int chroot_encrypted_create_symlink_file(struct libos_dentry* link_dent, + const char* targetpath) { + assert(locked(&g_dcache_lock)); + assert(link_dent->mount != NULL); + + if (link_dent->inode != NULL) + return -EEXIST; + + uint64_t time_us; + if (PalSystemTimeQuery(&time_us) < 0) + return -EPERM; + + bool do_unlock = false; + char* uri; + int ret = chroot_dentry_uri(link_dent, S_IFREG, &uri); + if (ret < 0) + return ret; + + mode_t perm = 0755; + if ((link_dent->parent != NULL) && (link_dent->parent->inode != NULL)) + perm = link_dent->parent->inode->perm; + + struct libos_encrypted_file* enc = NULL; + struct libos_inode* inode = get_new_inode(link_dent->mount, S_IFLNK, HOST_PERM(perm)); + if (inode == NULL) { + ret = -ENOMEM; + goto out; + } + + lock(&inode->lock); + do_unlock = true; + + struct libos_encrypted_files_key* key = link_dent->mount->data; + ret = encrypted_file_create(uri, HOST_PERM(perm), key, &enc); + if (ret < 0) + goto out; + inode->type = S_IFLNK; + + inode->data = enc; + link_dent->inode = inode; + get_inode(inode); + + file_off_t pos = 0; + size_t out_count = 0; + size_t target_len = strlen(targetpath); + ret = encrypted_file_write(enc, targetpath, target_len, pos, &out_count); + if (ret < 0) + goto out; + assert(out_count <= target_len); + + inode->size = target_len; + inode->mtime = time_us / USEC_IN_SEC; + +out: + if (enc != NULL) + encrypted_file_put(enc); + if (inode != NULL) { + if (do_unlock) + unlock(&inode->lock); + put_inode(inode); + } + free(uri); + return ret; +} + +static int chroot_encrypted_set_link(struct libos_dentry* link_dent, const char* targetpath, + bool is_soft_link) { + assert(locked(&g_dcache_lock)); + + int ret; + if (is_soft_link) + ret = chroot_encrypted_create_symlink_file(link_dent, targetpath); + else + ret = -EPERM; + if (ret < 0) + goto out; + ret = 0; + +out: + return ret; +} + +static int chroot_encrypted_follow_symlink(struct libos_dentry* link_dent, char** out_target) { + assert(locked(&g_dcache_lock)); + + if (link_dent->inode == NULL) + return -ENOENT; + struct libos_inode* inode = link_dent->inode; + bool put_required = false; + char* targetpath = NULL; + int ret = 0; + + lock(&inode->lock); + + /* open file, if not opened yet */ + struct libos_encrypted_file* enc = inode->data; + ret = encrypted_file_get(enc); + if (ret < 0) + goto out; + put_required = true; + + file_off_t file_sz = 0; + ret = encrypted_file_get_size(enc, &file_sz); + if (ret < 0) + goto out; + if (file_sz > PATH_MAX) { + ret = -EPERM; + goto out; + } + + targetpath = malloc(file_sz + 1); + if (targetpath == NULL) { + ret = -ENOMEM; + goto out; + } + + size_t out_count = 0; + ret = encrypted_file_read(enc, targetpath, file_sz, 0, &out_count); + if (ret < 0) + goto out; + static_assert(sizeof(out_count) >= sizeof(file_sz)); + assert(out_count <= (size_t)file_sz); + *(targetpath + out_count) = '\x00'; + + *out_target = targetpath; + targetpath = NULL; /* to skip freeing it below */ + +out: + if (put_required) + encrypted_file_put(enc); + unlock(&inode->lock); + free(targetpath); + return ret; +} + +static int chroot_encrypted_follow_link(struct libos_dentry* link_dent, char** out_target) { + assert(locked(&g_dcache_lock)); + + return chroot_encrypted_follow_symlink(link_dent, out_target); +} + static int chroot_encrypted_rename(struct libos_dentry* old, struct libos_dentry* new) { assert(locked(&g_dcache_lock)); assert(old->inode); @@ -348,7 +493,7 @@ static int chroot_encrypted_chmod(struct libos_dentry* dent, mode_t perm) { PAL_HANDLE palhdl; ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - PAL_OPTION_PASSTHROUGH, &palhdl); + PAL_OPTION_PASSTHROUGH, false, &palhdl); if (ret < 0) { ret = pal_to_unix_errno(ret); goto out; @@ -518,6 +663,8 @@ struct libos_d_ops chroot_encrypted_d_ops = { .stat = &generic_inode_stat, .readdir = &chroot_readdir, /* same as in `chroot` filesystem */ .unlink = &chroot_encrypted_unlink, + .follow_link = &chroot_encrypted_follow_link, + .set_link = &chroot_encrypted_set_link, .rename = &chroot_encrypted_rename, .chmod = &chroot_encrypted_chmod, .idrop = &chroot_encrypted_idrop, diff --git a/libos/src/fs/chroot/fs.c b/libos/src/fs/chroot/fs.c index 0d53726393..33db24ecfe 100644 --- a/libos/src/fs/chroot/fs.c +++ b/libos/src/fs/chroot/fs.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright (C) 2014 Stony Brook University * Copyright (C) 2021 Intel Corporation + * Copyright (C) 2024 Fortanix, Inc. * Paweł Marczewski + * Bobby Marinov */ /* @@ -62,6 +64,7 @@ int chroot_dentry_uri(struct libos_dentry* dent, mode_t type, char** out_uri) { size_t prefix_len; switch (type) { case S_IFREG: + case S_IFLNK: prefix = URI_PREFIX_FILE; prefix_len = static_strlen(URI_PREFIX_FILE); break; @@ -182,14 +185,15 @@ static int chroot_lookup(struct libos_dentry* dent) { } /* Open a temporary read-only PAL handle for a file (used by `unlink` etc.) */ -static int chroot_temp_open(struct libos_dentry* dent, mode_t type, PAL_HANDLE* out_palhdl) { +static int chroot_temp_open(struct libos_dentry* dent, mode_t type, + bool create_delete_handle, PAL_HANDLE* out_palhdl) { char* uri; int ret = chroot_dentry_uri(dent, type, &uri); if (ret < 0) return ret; ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - /*options=*/0, out_palhdl); + /*options=*/0, create_delete_handle, out_palhdl); free(uri); return pal_to_unix_errno(ret); } @@ -201,7 +205,7 @@ static int chroot_do_open(struct libos_handle* hdl, struct libos_dentry* dent, m int ret; - char* uri; + char* uri = NULL; ret = chroot_dentry_uri(dent, type, &uri); if (ret < 0) return ret; @@ -211,7 +215,7 @@ static int chroot_do_open(struct libos_handle* hdl, struct libos_dentry* dent, m enum pal_create_mode create = LINUX_OPEN_FLAGS_TO_PAL_CREATE(flags); pal_stream_options_t options = LINUX_OPEN_FLAGS_TO_PAL_OPTIONS(flags); mode_t host_perm = HOST_PERM(perm); - ret = PalStreamOpen(uri, access, host_perm, create, options, &palhdl); + ret = PalStreamOpen(uri, access, host_perm, create, options, false, &palhdl); if (ret < 0) { ret = pal_to_unix_errno(ret); goto out; @@ -347,7 +351,7 @@ int chroot_readdir(struct libos_dentry* dent, readdir_callback_t callback, void* char* buf = NULL; size_t buf_size = READDIR_BUF_SIZE; - ret = chroot_temp_open(dent, S_IFDIR, &palhdl); + ret = chroot_temp_open(dent, S_IFDIR, false, &palhdl); if (ret < 0) return ret; @@ -408,7 +412,7 @@ int chroot_unlink(struct libos_dentry* dent) { int ret; PAL_HANDLE palhdl; - ret = chroot_temp_open(dent, dent->inode->type, &palhdl); + ret = chroot_temp_open(dent, dent->inode->type, true, &palhdl); if (ret < 0) return ret; @@ -432,7 +436,7 @@ static int chroot_rename(struct libos_dentry* old, struct libos_dentry* new) { goto out; PAL_HANDLE palhdl; - ret = chroot_temp_open(old, old->inode->type, &palhdl); + ret = chroot_temp_open(old, old->inode->type, false, &palhdl); if (ret < 0) goto out; @@ -456,7 +460,7 @@ static int chroot_chmod(struct libos_dentry* dent, mode_t perm) { int ret; PAL_HANDLE palhdl; - ret = chroot_temp_open(dent, dent->inode->type, &palhdl); + ret = chroot_temp_open(dent, dent->inode->type, false, &palhdl); if (ret < 0) return ret; diff --git a/libos/src/fs/dev/fs.c b/libos/src/fs/dev/fs.c index 3785ac2ae9..fe50452f63 100644 --- a/libos/src/fs/dev/fs.c +++ b/libos/src/fs/dev/fs.c @@ -65,7 +65,7 @@ static int dev_tty_open(struct libos_handle* hdl, struct libos_dentry* dent, int PAL_HANDLE palhdl; int ret = PalStreamOpen(uri, LINUX_OPEN_FLAGS_TO_PAL_ACCESS(flags), PSEUDO_PERM_FILE_RW, - PAL_CREATE_NEVER, /*options=*/0, &palhdl); + PAL_CREATE_NEVER, /*options=*/0, false, &palhdl); if (ret < 0) { free(uri); return pal_to_unix_errno(ret); diff --git a/libos/src/fs/libos_fs_encrypted.c b/libos/src/fs/libos_fs_encrypted.c index 3926bb780a..689a67a5a0 100644 --- a/libos/src/fs/libos_fs_encrypted.c +++ b/libos/src/fs/libos_fs_encrypted.c @@ -169,7 +169,7 @@ static int encrypted_file_internal_open(struct libos_encrypted_file* enc, PAL_HA if (!pal_handle) { enum pal_create_mode create_mode = create ? PAL_CREATE_ALWAYS : PAL_CREATE_NEVER; ret = PalStreamOpen(enc->uri, PAL_ACCESS_RDWR, share_flags, create_mode, - PAL_OPTION_PASSTHROUGH, &pal_handle); + PAL_OPTION_PASSTHROUGH, false, &pal_handle); if (ret < 0) { log_warning("PalStreamOpen failed: %s", pal_strerror(ret)); return pal_to_unix_errno(ret); diff --git a/libos/src/fs/libos_namei.c b/libos/src/fs/libos_namei.c index f23f2de8da..65c42e1beb 100644 --- a/libos/src/fs/libos_namei.c +++ b/libos/src/fs/libos_namei.c @@ -466,6 +466,15 @@ int open_namei(struct libos_handle* hdl, struct libos_dentry* start, const char* } if (dent->inode && dent->inode->type == S_IFLNK) { + /* + * If O_EXCL and O_CREAT are set, and path names a symbolic link, + * open() shall fail and set errno to [EEXIST] + */ + if ((flags & O_CREAT) && (flags & O_EXCL)) { + ret = -EEXIST; + goto out; + } + /* * Can happen if user specified O_NOFOLLOW, or O_TRUNC | O_EXCL. Posix requires us to fail * with -ELOOP when trying to open a symlink. diff --git a/libos/src/fs/shm/fs.c b/libos/src/fs/shm/fs.c index 1ea365fece..ba4bc98e1a 100644 --- a/libos/src/fs/shm/fs.c +++ b/libos/src/fs/shm/fs.c @@ -66,7 +66,7 @@ static int shm_do_open(struct libos_handle* hdl, struct libos_dentry* dent, mode enum pal_create_mode create = LINUX_OPEN_FLAGS_TO_PAL_CREATE(flags); pal_stream_options_t options = LINUX_OPEN_FLAGS_TO_PAL_OPTIONS(flags); mode_t host_perm = HOST_PERM(perm); - ret = PalStreamOpen(uri, access, host_perm, create, options, &palhdl); + ret = PalStreamOpen(uri, access, host_perm, create, options, false, &palhdl); if (ret < 0) { ret = pal_to_unix_errno(ret); goto out; diff --git a/libos/src/fs/tmpfs/fs.c b/libos/src/fs/tmpfs/fs.c index a2190a7fb2..e5141008d6 100644 --- a/libos/src/fs/tmpfs/fs.c +++ b/libos/src/fs/tmpfs/fs.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright (C) 2021 Intel Corporation + * Copyright (C) 2024 Fortanix, Inc. * Li Xun * Paweł Marczewski + * Bobby Marinov */ /* @@ -204,6 +206,130 @@ static int tmpfs_rename(struct libos_dentry* old, struct libos_dentry* new) { return 0; } +static int tmpfs_create_hardlink_file(struct libos_dentry* link_dent, const char* targetpath) { + assert(locked(&g_dcache_lock)); + + if (link_dent->inode != NULL) + return -EEXIST; + + struct libos_dentry* target_dent = NULL; + int ret = path_lookupat(NULL, targetpath, LOOKUP_NO_FOLLOW, &target_dent); + if (ret < 0) + return ret; + assert(target_dent != NULL); + assert(target_dent->inode != NULL); + if (S_ISDIR(target_dent->inode->type)) + return -EPERM; /* no hardlinks to dirs */ + if (target_dent->mount != link_dent->mount) + return -EXDEV; /* must be on the same mounted filesystem */ + + struct libos_inode *inode = target_dent->inode; + lock(&inode->lock); + + link_dent->inode = target_dent->inode; + get_inode(link_dent->inode); + + unlock(&inode->lock); + + return 0; +} + +static int tmpfs_create_symlink_file(struct libos_dentry* link_dent, const char* targetpath) { + assert(locked(&g_dcache_lock)); + + if (link_dent->inode != NULL) + return -EEXIST; + + uint64_t time_us; + if (PalSystemTimeQuery(&time_us) < 0) + return -EPERM; + + mode_t perm = 0744; + if ((link_dent->parent != NULL) && (link_dent->parent->inode != NULL)) + perm = link_dent->parent->inode->perm; + int ret = tmpfs_setup_dentry(link_dent, S_IFLNK, perm); + if (ret < 0) + return ret; + assert(link_dent->inode != NULL); + + struct libos_inode* inode = link_dent->inode; + file_off_t pos = 0ULL; + size_t target_sz = strlen(targetpath); + + lock(&inode->lock); + bool do_unlock = true; + + struct libos_mem_file* mem = inode->data; + assert(mem != NULL); + ssize_t out_count = mem_file_write(mem, pos, targetpath, target_sz); + if (out_count < 0) { + ret = out_count; + goto out; + } + assert(target_sz == (size_t)out_count); + + inode->size = mem->size; + inode->mtime = time_us / USEC_IN_SEC; + +out: + if (do_unlock) + unlock(&inode->lock); + + return ret; +} + +static int tmpfs_follow_symlink(struct libos_dentry* link_dent, char** out_target) { + assert(locked(&g_dcache_lock)); + + if (link_dent->inode == NULL) + return -ENOENT; + struct libos_inode* inode = link_dent->inode; + + if (inode->size > PATH_MAX) + return -EPERM; + + char* targetpath = malloc(inode->size + 1); + if (targetpath == NULL) + return -ENOMEM; + + struct libos_mem_file* mem = inode->data; + assert(inode->size == mem->size); + size_t sz = inode->size; + file_off_t pos = 0ULL; + int ret = mem_file_read(mem, pos, targetpath, sz); + if (ret < 0) { + free(targetpath); + return ret; + } + *(targetpath + sz) = '\x00'; + + *out_target = targetpath; + return ret; +} + +static int tmpfs_set_link(struct libos_dentry* link_dent, const char* targetpath, + bool is_soft_link) { + assert(locked(&g_dcache_lock)); + + int ret; + if (is_soft_link) + ret = tmpfs_create_symlink_file(link_dent, targetpath); + else + ret = tmpfs_create_hardlink_file(link_dent, targetpath); + if (ret < 0) + goto out; + ret = 0; + +out: + return ret; +} + +static int tmpfs_follow_link(struct libos_dentry* link_dent, char** out_target) { + assert(locked(&g_dcache_lock)); + + return tmpfs_follow_symlink(link_dent, out_target); +} + static int tmpfs_chmod(struct libos_dentry* dent, mode_t perm) { __UNUSED(dent); __UNUSED(perm); @@ -329,6 +455,8 @@ struct libos_d_ops tmp_d_ops = { .stat = &generic_inode_stat, .readdir = &generic_readdir, .unlink = &tmpfs_unlink, + .set_link = &tmpfs_set_link, + .follow_link = &tmpfs_follow_link, .rename = &tmpfs_rename, .chmod = &tmpfs_chmod, .idrop = &tmpfs_idrop, diff --git a/libos/src/ipc/libos_ipc.c b/libos/src/ipc/libos_ipc.c index 9d8bec7e0b..06c023be1d 100644 --- a/libos/src/ipc/libos_ipc.c +++ b/libos/src/ipc/libos_ipc.c @@ -129,7 +129,7 @@ static int ipc_connect(IDTYPE dest, struct libos_ipc_connection** conn_ptr) { } do { ret = PalStreamOpen(uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_IGNORED, - /*options=*/0, &conn->handle); + /*options=*/0, false, &conn->handle); } while (ret == -PAL_ERROR_INTERRUPTED); if (ret < 0) { ret = pal_to_unix_errno(ret); diff --git a/libos/src/libos_init.c b/libos/src/libos_init.c index 332c9f4694..d7c862199e 100644 --- a/libos/src/libos_init.c +++ b/libos/src/libos_init.c @@ -67,6 +67,8 @@ static unsigned pal_errno_to_unix_errno_table[PAL_ERROR_NATIVE_COUNT + 1] = { [PAL_ERROR_CONNFAILED] = ECONNRESET, [PAL_ERROR_ADDRNOTEXIST] = EADDRNOTAVAIL, [PAL_ERROR_AFNOSUPPORT] = EAFNOSUPPORT, + [PAL_ERROR_LOOP] = ELOOP, + [PAL_ERROR_NO_PERMISSION] = EPERM, [PAL_ERROR_CONNFAILED_PIPE] = EPIPE, }; @@ -564,7 +566,7 @@ int create_pipe(char* name, char* uri, size_t size, PAL_HANDLE* hdl, bool use_vm return -ERANGE; ret = PalStreamOpen(uri, PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, - /*options=*/0, &pipe); + /*options=*/0, false, &pipe); if (ret < 0) { if (!use_vmid_for_name && ret == -PAL_ERROR_STREAMEXIST) { /* tried to create a pipe with random name but it already exists */ diff --git a/libos/src/libos_parser.c b/libos/src/libos_parser.c index e9de40c9cd..931dd2c283 100644 --- a/libos/src/libos_parser.c +++ b/libos/src/libos_parser.c @@ -226,9 +226,11 @@ struct parser_table { [__NR_rmdir] = {.slow = false, .name = "rmdir", .parser = {parse_long_arg, parse_string_arg}}, [__NR_creat] = {.slow = false, .name = "creat", .parser = {parse_long_arg, parse_string_arg, parse_open_mode}}, - [__NR_link] = {.slow = false, .name = "link", .parser = {NULL}}, + [__NR_link] = {.slow = false, .name = "link", .parser = {parse_long_arg, parse_string_arg, + parse_string_arg}}, [__NR_unlink] = {.slow = false, .name = "unlink", .parser = {parse_long_arg, parse_string_arg}}, - [__NR_symlink] = {.slow = false, .name = "symlink", .parser = {NULL}}, + [__NR_symlink] = {.slow = false, .name = "symlink", .parser = {parse_long_arg, parse_string_arg, + parse_string_arg}}, [__NR_readlink] = {.slow = false, .name = "readlink", .parser = {parse_long_arg, parse_string_arg, parse_pointer_arg, parse_integer_arg}}, [__NR_chmod] = {.slow = false, .name = "chmod", .parser = {parse_long_arg, parse_string_arg, @@ -487,8 +489,10 @@ struct parser_table { parse_string_arg, parse_integer_arg}}, [__NR_renameat] = {.slow = false, .name = "renameat", .parser = {parse_long_arg, parse_at_fdcwd, parse_string_arg, parse_integer_arg, parse_string_arg}}, - [__NR_linkat] = {.slow = false, .name = "linkat", .parser = {NULL}}, - [__NR_symlinkat] = {.slow = false, .name = "symlinkat", .parser = {NULL}}, + [__NR_linkat] = {.slow = false, .name = "linkat", .parser = {parse_long_arg, parse_at_fdcwd, + parse_string_arg, parse_at_fdcwd, parse_string_arg, parse_integer_arg}}, + [__NR_symlinkat] = {.slow = false, .name = "symlinkat", .parser = {parse_long_arg, parse_string_arg, + parse_at_fdcwd, parse_string_arg}}, [__NR_readlinkat] = {.slow = false, .name = "readlinkat", .parser = {parse_long_arg, parse_at_fdcwd, parse_string_arg, parse_pointer_arg, parse_integer_arg}}, [__NR_fchmodat] = {.slow = false, .name = "fchmodat", .parser = {parse_long_arg, parse_at_fdcwd, diff --git a/libos/src/libos_pollable_event.c b/libos/src/libos_pollable_event.c index a5ce2723e0..53c17749a2 100644 --- a/libos/src/libos_pollable_event.c +++ b/libos/src/libos_pollable_event.c @@ -23,7 +23,7 @@ int create_pollable_event(struct libos_pollable_event* event) { PAL_HANDLE write_handle; do { ret = PalStreamOpen(uri, PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, - PAL_OPTION_NONBLOCK, &write_handle); + PAL_OPTION_NONBLOCK, false, &write_handle); } while (ret == -PAL_ERROR_INTERRUPTED); if (ret < 0) { log_error("PalStreamOpen failed: %s", pal_strerror(ret)); diff --git a/libos/src/net/unix.c b/libos/src/net/unix.c index a638eb0b5f..c64cc9525d 100644 --- a/libos/src/net/unix.c +++ b/libos/src/net/unix.c @@ -141,7 +141,7 @@ static int bind(struct libos_handle* handle, void* addr, size_t addrlen) { PAL_HANDLE pal_handle = NULL; ret = PalStreamOpen(pipe_name, PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, options, - &pal_handle); + false, &pal_handle); if (ret < 0) { return (ret == -PAL_ERROR_STREAMEXIST) ? -EADDRINUSE : pal_to_unix_errno(ret); } @@ -238,7 +238,7 @@ static int connect(struct libos_handle* handle, void* addr, size_t addrlen, bool PAL_HANDLE pal_handle = NULL; ret = PalStreamOpen(pipe_name, PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, options, - &pal_handle); + false, &pal_handle); if (ret < 0) { return (ret == -PAL_ERROR_CONNFAILED) ? -ENOENT : pal_to_unix_errno(ret); } diff --git a/libos/src/sys/libos_eventfd.c b/libos/src/sys/libos_eventfd.c index 92b112618b..4cd14446ac 100644 --- a/libos/src/sys/libos_eventfd.c +++ b/libos/src/sys/libos_eventfd.c @@ -47,7 +47,7 @@ static int create_eventfd(PAL_HANDLE* efd, uint64_t initial_count, int flags) { pal_flags |= flags & EFD_SEMAPHORE ? PAL_OPTION_EFD_SEMAPHORE : 0; ret = PalStreamOpen(URI_PREFIX_EVENTFD, PAL_ACCESS_RDWR, /*share_flags=*/0, - PAL_CREATE_IGNORED, pal_flags, &hdl); + PAL_CREATE_IGNORED, pal_flags, false, &hdl); if (ret < 0) { log_error("eventfd: creation failure"); return pal_to_unix_errno(ret); diff --git a/libos/src/sys/libos_file.c b/libos/src/sys/libos_file.c index 350b532973..4a88942412 100644 --- a/libos/src/sys/libos_file.c +++ b/libos/src/sys/libos_file.c @@ -1,9 +1,13 @@ /* SPDX-License-Identifier: LGPL-3.0-or-later */ -/* Copyright (C) 2014 Stony Brook University */ +/* Copyright (C) 2014 Stony Brook University + * Copyright (C) 2024 Fortanix, Inc. + * Bobby Marinov + */ /* * Implementation of system calls "unlink", "unlinkat", "mkdir", "mkdirat", "rmdir", "umask", - * "chmod", "fchmod", "fchmodat", "rename", "renameat" and "sendfile". + * "chmod", "fchmod", "fchmodat", "rename", "renameat", "sendfile", "link", "linkat", "symlink" + * and "symlinkat". */ #include "libos_fs.h" @@ -590,3 +594,181 @@ long libos_syscall_chroot(const char* filename) { out: return ret; } + +static const char* strip_prefix(const char* uri) { + const char* s = strchr(uri, ':'); + assert(s); + return s + 1; +} + +static int get_dentry_uri_no_pfix(struct libos_dentry* dent, char** out_uri) { + assert(dent->mount); + assert(dent->mount->uri); + + const char* root = strip_prefix(dent->mount->uri); + + char* rel_path = NULL; + size_t rel_path_size = 0; + int ret = dentry_rel_path(dent, &rel_path, &rel_path_size); + if (ret < 0) + return ret; + + /* Treat empty path as "." */ + if (*root == '\0') + root = "."; + + size_t root_len = strlen(root); + + /* Allocate buffer for "/" (if `rel_path` is empty, we don't need the + * space for `/`, but overallocating 1 byte doesn't hurt us, and keeps the code simple) */ + char* uri = malloc(root_len + 1 + rel_path_size); + if (!uri) { + ret = -ENOMEM; + goto out; + } + memcpy(uri, root, root_len); + if (rel_path_size == 1) { + /* this is the mount root, the stripped URI is ""*/ + uri[root_len] = '\0'; + } else { + /* this is not the mount root, the stripped URI is "/" */ + uri[root_len] = '/'; + memcpy(uri + root_len + 1, rel_path, rel_path_size); + } + *out_uri = uri; + ret = 0; + +out: + free(rel_path); + return ret; +} + +static long do_linkat(int targetfd, const char* target, int newdirfd, const char* linkpath, + int flags, bool is_soft_link) { + assert(!locked(&g_dcache_lock)); + __UNUSED(targetfd); + __UNUSED(flags); + + if (!is_user_string_readable(target)) + return -EFAULT; + if (!is_user_string_readable(linkpath)) + return -EFAULT; + + struct libos_dentry *link_dir = NULL; + struct libos_dentry *link_dent = NULL; + + struct libos_dentry *target_dir = NULL; + struct libos_dentry *target_dent = NULL; + char* target_path = NULL; + bool is_locked = false; + + int ret = 0; + + if (!*target || !*linkpath) { + ret = -ENOENT; + goto out; + } + + if (*target != '/') { + if ((ret = get_dirfd_dentry(targetfd, &target_dir)) < 0) + goto out; + assert(target_dir != NULL); + } + + if (*linkpath != '/') { + if ((ret = get_dirfd_dentry(newdirfd, &link_dir)) < 0) + goto out; + assert(link_dir != NULL); + } + + lock(&g_dcache_lock); + is_locked = true; + + if (!is_soft_link) { + ret = path_lookupat(target_dir, target, LOOKUP_NO_FOLLOW, &target_dent); + if (ret < 0) + goto out; + assert(target_dent != NULL); + } + + ret = path_lookupat(link_dir, linkpath, LOOKUP_CREATE, &link_dent); + if ((ret == -ENOENT) || (ret == 0)) { + if (link_dent == NULL) { + /* Some parent directory did not exist. */ + goto out; + } + + /* We don't care if the symlink target exists or not. And since we + * resolve symlinks inside the libOS, we don't have to translate the + * symlink target to a real path on the host file system. This means + * we can end up with symlinks that are broken on the host but work + * inside the libOS (or the reverse) if the filesystem isn't + * identity mapped. + */ + if (link_dent->mount) { + + struct libos_fs* fs = link_dent->mount->fs; + if (fs == NULL || fs->d_ops == NULL || fs->d_ops->set_link == NULL) { + ret = -EPERM; + goto out; + } + + /* If it is a soft link we directly pass the guest path of the + * target and for hardlink we pass the host uri. The symlink’s value + * is read from the host, but then we do a directory walk on the + * symlink’s path using the guest’s view of the filesystem. + * Relative symlinks start from the parent directory of where the + * symlink is stored. + */ + + if (is_soft_link || (*target == '/')) + ret = fs->d_ops->set_link(link_dent, target, is_soft_link); + else { + assert(target_dent != NULL); + ret = get_dentry_uri_no_pfix(target_dent, &target_path); + if (ret != 0) + goto out; + assert(target_path != NULL); + ret = fs->d_ops->set_link(link_dent, target_path, is_soft_link); + } + } else + ret = -EPERM; + } + + /* If path_lookupat() returned anything other than ENOENT or 0, we'll + * fall through and return its return value here. + */ +out: + if (is_locked) + unlock(&g_dcache_lock); + + if (target_path != NULL) + free(target_path); + if (link_dent != NULL) + put_dentry(link_dent); + if (target_dent != NULL) + put_dentry(target_dent); + if (link_dir != NULL) + put_dentry(link_dir); + if (target_dir != NULL) + put_dentry(target_dir); + + return ret; +} + +long libos_syscall_symlink(const char* target, const char* linkpath) { + return libos_syscall_symlinkat(target, AT_FDCWD, linkpath); +} + +long libos_syscall_symlinkat(const char* target, int newdirfd, const char* linkpath) { + return do_linkat(AT_FDCWD, target, newdirfd, linkpath, 0, true); +} + +long libos_syscall_link(const char* target, const char* linkpath) { + return libos_syscall_linkat(AT_FDCWD, target, AT_FDCWD, linkpath, 0); +} + +long libos_syscall_linkat(int olddirfd, const char* target, int newdirfd, const char* linkpath, + int flags) { + return do_linkat(olddirfd, target, newdirfd, linkpath, flags, false); +} \ No newline at end of file diff --git a/libos/src/sys/libos_pipe.c b/libos/src/sys/libos_pipe.c index 33eaf1944e..31cb30bded 100644 --- a/libos/src/sys/libos_pipe.c +++ b/libos/src/sys/libos_pipe.c @@ -35,7 +35,7 @@ static int create_pipes(struct libos_handle* srv, struct libos_handle* cli, int } ret = PalStreamOpen(uri, PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, - LINUX_OPEN_FLAGS_TO_PAL_OPTIONS(flags), &hdl2); + LINUX_OPEN_FLAGS_TO_PAL_OPTIONS(flags), false, &hdl2); if (ret < 0) { ret = pal_to_unix_errno(ret); log_error("pipe connection failure"); diff --git a/libos/test/regression/link_symlink.c b/libos/test/regression/link_symlink.c new file mode 100644 index 0000000000..c877cf3aed --- /dev/null +++ b/libos/test/regression/link_symlink.c @@ -0,0 +1,556 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2024 Fortanix, Inc. + * Bobby Marinov + */ + +/* + * Tests for symbolic and hard links. + */ + +#define _DEFAULT_SOURCE /* fchmod */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "rw_file.h" + +static const char message1[] = "first message\n"; +static const size_t message1_len = sizeof(message1) - 1; + +static const char message2[] = "second message\n"; +//static const size_t message2_len = sizeof(message2) - 1; + +static const char message3[] = "third file content\n"; +static const size_t message3_len = sizeof(message3) - 1; + +static_assert(sizeof(message1) != sizeof(message2) && + sizeof(message1) != sizeof(message3) && + sizeof(message2) != sizeof(message3), + "the messages should have different lengths"); + +#define FILENAME_PREFIX "link_symlink_test_file" +#define FILENAME_PREFIX_LEN (ARRAY_LEN(FILENAME_PREFIX) - 1) +#define DIRNAME_PREFIX "link_symlink_test_dir" +#define DIRNAME_PREFIX_LEN (ARRAY_LEN(DIRNAME_PREFIX) - 1) + +static void remove_test_files_and_dirs_recursively(char* path) { + char* entry_path = NULL; + DIR *dir = opendir(path); + if (dir == NULL) { + err(1, "Error opening directory: '%s'", path); + } + size_t len = strlen(path); + bool separator_present = (len && (*(path + len -1) == '/')) ? true: false; + + struct dirent* entry; + for(entry = readdir(dir); entry != NULL; entry = readdir(dir)) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + bool is_dir = true; + if (strncmp(entry->d_name, FILENAME_PREFIX, FILENAME_PREFIX_LEN) == 0) + is_dir = false; + else if (strncmp(entry->d_name, DIRNAME_PREFIX, DIRNAME_PREFIX_LEN) != 0) + continue; + + /* Construct the full path of the entry */ + if (entry_path == NULL) { + entry_path = malloc(PATH_MAX + 1); + if (entry_path == NULL) + err(1, "Failed to allocate path buffer for '%s/%s'", path, entry->d_name); + } + if (separator_present) + snprintf(entry_path, PATH_MAX, "%s%s", path, entry->d_name); + else + snprintf(entry_path, PATH_MAX, "%s/%s", path, entry->d_name); + *(entry_path + PATH_MAX) = '\x00'; + + if (is_dir) + remove_test_files_and_dirs_recursively(entry_path); + else + remove(entry_path); + } + + free(entry_path); + closedir(dir); + + if (len) { + char* file = path + len - 1; + if (separator_present) + --file; + while (file > path) { + if (*file == '/') + break; + --file; + } + if (*file == '/') + ++file; + if (strncmp(file, DIRNAME_PREFIX, DIRNAME_PREFIX_LEN) == 0) + rmdir(path); + } +} + +#define DIR_SEPARATOR_STR "/" +#define DIR_SEPARATOR_CHAR (DIR_SEPARATOR_STR[0]) +static char* os_path_join(char* base_path, ...) { + if (base_path == NULL) + return NULL; + + size_t total_sz = 0; + size_t len = strlen(base_path); + bool remove_last = true; + if (len && *(base_path + len - 1) == DIR_SEPARATOR_CHAR) { + --len; + remove_last = false; + } + + /* calculate total size */ + const char* sub; + total_sz += len + 1; /* '+1' - path separator */ + va_list args; + va_start(args, base_path); + while ((sub = va_arg(args, const char *)) != NULL) { + len = strlen(sub); + if (len && (*sub == DIR_SEPARATOR_CHAR)) { + --len; + ++sub; + } + if (len && (*(sub + len - 1) == DIR_SEPARATOR_CHAR)) { + --len; + remove_last = false; + } else + remove_last = true; + total_sz += len + 1; /* '+1' - path separator */ + } + va_end(args); + + ++total_sz; /* '+1' - zero-terminator */ + + char* out = (char*)malloc(total_sz); + if (out == NULL) + return NULL; + + assert(total_sz <= SSIZE_MAX); + ssize_t out_sz = (ssize_t)total_sz; + char* out_ptr = out; + int printed; + len = strlen(base_path); + if (len && *(base_path + len - 1) == DIR_SEPARATOR_CHAR) { + printed = snprintf(out_ptr, out_sz, "%s", base_path); + remove_last = false; + } else { + printed = snprintf(out_ptr, out_sz, "%s" DIR_SEPARATOR_STR, base_path); + remove_last = true; + } + out_sz -= printed; + out_ptr += printed; + + va_start(args, base_path); + while ((sub = va_arg(args, const char *)) != NULL) { + len = strlen(sub); // +1 for the path separator + assert(len <= SSIZE_MAX); + if (len && (*sub == DIR_SEPARATOR_CHAR)) { + --len; + ++sub; + } + if (len && *(sub + len - 1) == DIR_SEPARATOR_CHAR) { + assert((ssize_t)len < out_sz); + printed = snprintf(out_ptr, out_sz, "%s", sub); + remove_last = false; + } else { + assert((ssize_t)len <= out_sz); + printed = snprintf(out_ptr, out_sz, "%s" DIR_SEPARATOR_STR, sub); + remove_last = true; + } + out_sz -= printed; + out_ptr += printed; + } + assert(out_sz == 1); + if (remove_last) + --out_ptr; + *out_ptr = '\x00'; + + return out; +} + +static void should_not_exist(const char* path) { + struct stat statbuf; + + if (stat(path, &statbuf) == 0) + errx(1, "%s unexpectedly exists", path); + if (errno != ENOENT) + err(1, "stat %s", path); +} + +static void check_statbuf(const char* desc, struct stat* statbuf, size_t size) { + assert(!OVERFLOWS(off_t, size)); + + if (!S_ISREG(statbuf->st_mode) && !S_ISLNK(statbuf->st_mode)) + errx(1, "%s: wrong mode (0o%o)", desc, statbuf->st_mode); + if (statbuf->st_size != (off_t)size) + errx(1, "%s: wrong size: %lu, expected: %lu", desc, statbuf->st_size, size); +} + +static void should_exist(const char* path, size_t size) { + struct stat statbuf; + + if (stat(path, &statbuf) != 0) + err(1, "stat %s", path); + + check_statbuf(path, &statbuf, size); +} + +static void should_exist_dir(const char* path) { + struct stat statbuf; + + if (stat(path, &statbuf) != 0) + err(1, "stat %s", path); + if (!S_ISDIR(statbuf.st_mode)) + err(1, "'%s' should be a directory.", path); +} + +static void should_exist_symlink(const char* path, size_t size) { + struct stat statbuf; + + if (lstat(path, &statbuf) != 0) + err(1, "lstat %s", path); + + check_statbuf(path, &statbuf, size); +} + +static void should_exist_infinite_loop(const char* path) { + struct stat statbuf; + + if (stat(path, &statbuf) == 0) + err(1, "'%s' does not contain an infinite loop. expected: ELOOP, received: SUCCESS", path); + + if (errno != ELOOP) + err(1, "'%s' does not contain an infinite loop. expected: ELOOP(%d), received: %d", + path, ELOOP, errno); +} + +static int create_file(const char* path, const char* str, size_t len) { + int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + err(1, "create_file::open '%s' (errno:%d)", path, errno); + + ssize_t n = posix_fd_write(fd, str, len); + if (n < 0) + errx(1, "posix_fd_write %s", path); + if ((size_t)n != len) + errx(1, "written less bytes than expected into %s", path); + + return fd; +} + +static void create_file_and_close(const char* path, const char* str, size_t len) { + int fd = create_file(path, str, len); + if (close(fd) != 0) + err(1, "close %s", path); +} + +static int create_parent_dirs(int parent_fd, const char* file_path) { + size_t len = strlen (file_path); + int ret = 0; + + if (len <= 1) { + /* no parent dir in fle_path */ + return 0; + } + + char* path = strdup(file_path); + if (path == NULL) + err(1, "strdup %s", file_path); + + char* parent_end = path + len - 1; + for (size_t i = len - 1; i>0; --i, --parent_end) { + if (*parent_end == '/') + break; + } + if ((*parent_end != '/') || (parent_end == path)) { + goto out; + } + *parent_end = '\x00'; + + /* try creating the directories backwards */ + char* current_end = parent_end; + while (current_end != path) { + size_t i; + ret = 0; + if (mkdirat(parent_fd, path, 0774) != 0) + ret = errno; + if ((ret == 0) || (ret == EEXIST)) { + ret = 0; + break; + } + + for (i = current_end - path - 1; i>0; --i) { + if (*(path + i) == '/') + break; + } + if ((*(path + i) != '/') || (i == 0)) { + goto out; + } + current_end = path + i; + } + if (current_end == parent_end) { + goto out; + } + + /* found the first existing dir, now create the rest */ + *current_end = '/'; + while (current_end < parent_end) { + for (++current_end; current_end < parent_end; ++current_end) { + if (*current_end == '/') { + *current_end = '\x00'; + break; + } + } + ret = 0; + if (mkdirat(parent_fd, path, 0774) != 0) + ret = errno; + if ((ret != 0) && (ret != EEXIST)) { + goto out; + } + *current_end = '/'; + ret = 0; + } + +out: + if (path != NULL) + free(path); + + return ret; +} + +static void test_link_same_file(const char* path, bool is_sym_link) { + const char* txt = is_sym_link ? "symlink": "hardlink"; + + should_exist(path, message1_len); + errno = 0; + int ret = is_sym_link ? symlink(path, path): link(path, path); + if ((ret == 0) || (errno != EEXIST)) // && errno != ENOENT)) + err(1, "%s('%s') to the same file should fail. (errno: %d)", txt, path, errno); +} + +static void test_simple_link(const char* path1, const char* path2, bool is_sym_link) { + const char* txt = is_sym_link ? "symlink": "hardlink"; + + errno = 0; + int ret = is_sym_link ? symlink(path1, path2): link(path1, path2); + if (ret != 0) + err(1, "%s", txt); + should_exist(path2, message1_len); + if (is_sym_link) + should_exist_symlink(path2, strlen(path1)); + + return; +} + +static void test_replace_link(const char* path3, const char* path2, bool is_sym_link) { + const char* txt = is_sym_link ? "symlink": "hardlink"; + + errno = 0; + int ret = (is_sym_link ? symlink(path3, path2): link(path3, path2)); + if ((ret >= 0) || (errno != EEXIST)) + err(1, "%s(%s <- %s) existing should have failed.(%d)", txt, path3, path2, errno); + should_exist(path2, message1_len); +// if (is_sym_link) +// should_exist_symlink(path2, strlen(path1)); + + return; +} + +static void test_dir_link(const char* path1, const char* path2, bool is_sym_link) { + const char* txt = is_sym_link ? "symlink": "hardlink"; + + errno = 0; + int ret = (is_sym_link ? symlink(path1, path2): link(path1, path2)); + if (!is_sym_link) { + if ((ret >= 0) || ((errno != EPERM) && (errno != ENOENT))) + err(1, "%s(%s <- %s) creating hardlink to a directory should have failed.(%d)", + txt, path1, path2, errno); + should_not_exist(path2); + } else { + if (ret != 0) + err(1, "%s", txt); + should_exist_dir(path2); + should_exist_symlink(path2, strlen(path1)); + } + + return; +} + +static void test_link_removal(const char* target, const char* targetpath, const char* linkpath, + bool is_sym_link) { + const char* txt = is_sym_link ? "symlink": "hardlink"; + + should_not_exist(linkpath); + create_file_and_close(targetpath, message3, message3_len); + should_exist(targetpath, message3_len); + if ((is_sym_link ? symlink(target, linkpath): link(target, linkpath)) != 0) + err(1, " create %s(errno:%d)", txt, errno); + + /* remove link first */ + errno = 0; + int ret = unlink(linkpath); + if ((ret < 0) || (errno != 0)) + err(1, "Deleting %s '%s' should succeed. (errno:%d)", txt, linkpath, errno); + should_not_exist(linkpath); + + /* remove target second */ + errno = 0; + ret = unlink(targetpath); + if ((ret < 0) || (errno != 0)) + err(1, "Deleting the %s's target ('%s') should succeed. (errno:%d)", txt, targetpath, errno); + should_not_exist(targetpath); + + return; +} + +static void test_bad_symlink_removal(const char* target, const char* targetpath, + const char* linkpath) { + const char* txt = "symlink"; + + create_file_and_close(targetpath, message3, message3_len); + if (symlink(target, linkpath) != 0) + err(1, "%s", txt); + + /* remove target first */ + errno = 0; + int ret = unlink(targetpath); + if ((ret < 0) || (errno != 0)) + err(1, "Deleting the %s's target ('%s') should succeed.(errno:%d)", txt, targetpath, errno); + should_not_exist(targetpath); + + /* remove broken (pointing to noithing) symlink second */ + errno = 0; + ret = unlink(linkpath); + if ((ret < 0) || (errno != 0)) + err(1, "Deleting %s '%s' should succeed.(errno:%d)", txt, linkpath, errno); + should_not_exist(linkpath); + + return; +} + +static void test_infinite_symlink_removal(const char* target, const char* linkpath) { + const char* txt = "symlink"; + + should_not_exist(linkpath); + if (symlink(target, linkpath) != 0) + err(1, "%s", txt); + + /* check for infinite loop symlink */ + should_exist_infinite_loop(linkpath); + + /* remove broken (pointing to itself) symlink */ + errno = 0; + int ret = unlink(linkpath); + if ((ret < 0) || (errno != 0)) + err(1, "Deleting %s '%s' should succeed.(errno:%d)", txt, linkpath, errno); + should_not_exist(linkpath); + + return; +} + +int main(int argc, char* argv[]) { +// int ret = 0; + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + if (argc != 3) + errx(1, "Usage: %s -h|s /", argv[0]); + if (argv[1][0] != '-' || (argv[1][1] != 'h' && argv[1][1] != 's') || argv[1][2] != '\x00') + errx(1, "ERROR: bad or missing link type: h - hardlink, s - softlink\n" + "Usage: %s -h|s /", argv[0]); + const char* dir = (argv[2][0] == '\x00') ? "./" : argv[2]; + size_t dir_len = strlen(dir); + if (*(dir + dir_len - 1) != '/') + errx(1, "ERROR: missing end of dir path terminator: '/'\n" + "Usage: %s -h|s /", argv[0]); + + char* path1 = os_path_join(argv[2], FILENAME_PREFIX "1", NULL); + if (path1 == NULL) + err(1, "path1 path join fail"); + char* path2 = os_path_join(argv[2], FILENAME_PREFIX "2", NULL); + if (path2 == NULL) + err(1, "path2 path join fail"); + bool is_sym_link = (argv[1][1] != 'h') ? true : false; + const char* oldpath = is_sym_link ? "./" FILENAME_PREFIX "1" : path1; + char* path3 = os_path_join(argv[2], FILENAME_PREFIX "1b", NULL); + if (path3 == NULL) + err(1, "path1b path join fail"); + const char* oldpath3 = is_sym_link ? "./" FILENAME_PREFIX "1b" : oldpath; + + char* path4 = os_path_join(argv[2], FILENAME_PREFIX "4", NULL); + if (path4 == NULL) + err(1, "path4 path join fail"); + const char* path4_target = FILENAME_PREFIX "4"; + + /* cleanup any potential files */ + remove_test_files_and_dirs_recursively(argv[2]); + + /* create any parent dirs */ + (void)create_parent_dirs(AT_FDCWD, path1); + (void)create_parent_dirs(AT_FDCWD, path2); + + create_file_and_close(path1, message1, message1_len); + should_exist(path1, message1_len); + create_file_and_close(path3, message3, message3_len); + should_exist(path3, message3_len); + +#if 01 + if (!is_sym_link) + test_link_same_file(path1, is_sym_link); + + should_exist(path1, message1_len); // assumes oldpath points to path1 + test_simple_link(oldpath, path2, is_sym_link); + test_replace_link(oldpath3, path2, is_sym_link); + unlink(path2); +#endif + + char* dir1 = os_path_join(argv[2], DIRNAME_PREFIX "1", NULL); + char* dir1_file3 = os_path_join(argv[2], DIRNAME_PREFIX "1", FILENAME_PREFIX "3", NULL); + const char* dir_symlink1 = "./" DIRNAME_PREFIX "1"; + const char* fil_symlink3 = "./" DIRNAME_PREFIX "1" "/" FILENAME_PREFIX "3"; + (void)create_parent_dirs(AT_FDCWD, dir1_file3); + should_exist_dir(dir1); + create_file_and_close(dir1_file3, message3, message3_len); + should_exist(dir1_file3, message3_len); + const char* dir_link = is_sym_link ? dir_symlink1: dir1; + const char* file3_link = is_sym_link ? fil_symlink3: dir1_file3; +#if 1 + test_dir_link(dir_link, path2, is_sym_link); + unlink(path2); +#endif + + test_link_removal(file3_link, dir1_file3, path2, is_sym_link); + if (is_sym_link) { + test_bad_symlink_removal(fil_symlink3, dir1_file3, path2); + test_infinite_symlink_removal(path4_target, path4); + } + + /* cleanup */ + free(dir1_file3); + free(dir1); + free(path4); + free(path3); + free(path2); + free(path1); + + printf("TEST OK\n Cleaning up ...\n"); + remove_test_files_and_dirs_recursively(argv[2]); + return 0; +} diff --git a/libos/test/regression/meson.build b/libos/test/regression/meson.build index ee9e96e20f..0e421dfabf 100644 --- a/libos/test/regression/meson.build +++ b/libos/test/regression/meson.build @@ -93,6 +93,7 @@ tests = { 'pthread_set_get_affinity': {}, 'readdir': {}, 'rename_unlink': {}, + 'link_symlink': {}, 'run_test': { 'include_directories': include_directories( # for `gramine_entry_api.h` diff --git a/libos/test/regression/test_libos.py b/libos/test/regression/test_libos.py index b91107b572..8ed1430932 100644 --- a/libos/test/regression/test_libos.py +++ b/libos/test/regression/test_libos.py @@ -24,6 +24,30 @@ 'syscall', ] +SYMLINK_TEST_FILENAME_PREFIX = "link_symlink_test_file" +SYMLINK_TEST_DIRNAME_PREFIX = "link_symlink_test_dir" + +def del_test_files_and_dirs(path, file_pfix, dir_pfix): + path = os.path.abspath(path) + if os.path.isfile(path): + raise Exception(f"{path} cannot be a file") + if not os.path.exists(path): + raise FileNotFoundError() + if (path == "/") and (file_pfix == "") and (dir_pfix == ""): + raise Exception("cannot delete everything in the 'root' directory") + + try: + for entry in os.listdir(path): + if os.path.isdir(entry): + if entry.startswith(dir_pfix): + del_test_files_and_dirs(entry, file_pfix, dir_pfix) + os.rmdir(entry) + else: + if entry.startswith(file_pfix): + os.unlink(entry) + + except Exception as e: + raise Exception(f"Failed to delete directory {path}: {e}") class TC_00_Unittests(RegressionTestCase): def test_000_spinlock(self): @@ -750,6 +774,99 @@ def test_036_rename_unlink_tmpfs(self): stdout, _ = self.run_binary(['rename_unlink', file1, file2]) self.assertIn('TEST OK', stdout) + @unittest.skip # No hardlink support for pass-through filesystem + def test_037_link_chroot(self): + dir1 = 'tmp/' + os.makedirs(dir1, exist_ok=True) + try: + stdout, _ = self.run_binary(['link_symlink', '-h', dir1]) + finally: + del_test_files_and_dirs(dir1, + SYMLINK_TEST_FILENAME_PREFIX, + SYMLINK_TEST_DIRNAME_PREFIX) + self.assertIn('TEST OK', stdout) + + @unittest.skip # No symlink support for pass-through filesystem + def test_038_symlink_chroot(self): + dir1 = 'tmp/' + os.makedirs(dir1, exist_ok=True) + try: + stdout, _ = self.run_binary(['link_symlink', '-s', dir1]) + finally: + del_test_files_and_dirs(dir1, + SYMLINK_TEST_FILENAME_PREFIX, + SYMLINK_TEST_DIRNAME_PREFIX) + self.assertIn('TEST OK', stdout) + + @unittest.skip # No hardlink support for pass-through filesystem + def test_039_link_pf(self): + dir1 = 'tmp/pf/' + os.makedirs(dir1, exist_ok=True) + try: + stdout, _ = self.run_binary(['link_symlink', '-h', dir1]) + finally: + del_test_files_and_dirs(dir1, + SYMLINK_TEST_FILENAME_PREFIX, + SYMLINK_TEST_DIRNAME_PREFIX) + self.assertIn('TEST OK', stdout) + + @unittest.skip # No symlink support for pass-through filesystem + def test_03A_symlink_pf(self): + dir1 = 'tmp/pf/' + os.makedirs(dir1, exist_ok=True) + try: + stdout, _ = self.run_binary(['link_symlink', '-s', dir1]) + finally: + del_test_files_and_dirs(dir1, + SYMLINK_TEST_FILENAME_PREFIX, + SYMLINK_TEST_DIRNAME_PREFIX) + self.assertIn('TEST OK', stdout) + + @unittest.skip # No hardlink support for encrypted filesystem + def test_03B_link_enc(self): + dir1 = 'tmp_enc/' + os.makedirs(dir1, exist_ok=True) + try: + stdout, _ = self.run_binary(['link_symlink', '-h', dir1]) + finally: + del_test_files_and_dirs(dir1, + SYMLINK_TEST_FILENAME_PREFIX, + SYMLINK_TEST_DIRNAME_PREFIX) + self.assertIn('TEST OK', stdout) + + def test_03C_symlink_enc(self): + dir1 = 'tmp_enc/' + os.makedirs(dir1, exist_ok=True) + try: + stdout, _ = self.run_binary(['link_symlink', '-s', dir1]) + finally: + del_test_files_and_dirs(dir1, + SYMLINK_TEST_FILENAME_PREFIX, + SYMLINK_TEST_DIRNAME_PREFIX) + self.assertIn('TEST OK', stdout) + + def test_03D_link_tmpfs(self): + dir1 = '/mnt/tmpfs/' + try: + os.makedirs(dir1, exist_ok=True) + except OSError as e: + if e == PermissionError: + print(f'{dir1} already exists') + + stdout, _ = self.run_binary(['link_symlink', '-h', dir1]) + self.assertIn('TEST OK', stdout) + + def test_03E_symlink_tmpfs(self): + dir1 = '/mnt/tmpfs/' + try: + os.makedirs(dir1, exist_ok=True) + except OSError as e: + if e == PermissionError: + print(f'{dir1} already exists') + + stdout, _ = self.run_binary(['link_symlink', '-s', dir1]) + self.assertIn('TEST OK', stdout) + def test_040_futex_bitset(self): stdout, _ = self.run_binary(['futex_bitset']) diff --git a/libos/test/regression/tests.toml b/libos/test/regression/tests.toml index 36a3290a60..7ac80ded08 100644 --- a/libos/test/regression/tests.toml +++ b/libos/test/regression/tests.toml @@ -64,6 +64,7 @@ manifests = [ "large_dir_read", "large_file", "large_mmap", + "link_symlink", "madvise", "mkfifo", "mmap_file", diff --git a/libos/test/regression/tests_musl.toml b/libos/test/regression/tests_musl.toml index 7848dd46b3..f2792e301e 100644 --- a/libos/test/regression/tests_musl.toml +++ b/libos/test/regression/tests_musl.toml @@ -66,6 +66,7 @@ manifests = [ "large_dir_read", "large_file", "large_mmap", + "link_symlink", "madvise", "mkfifo", "mmap_file", diff --git a/pal/include/pal/pal.h b/pal/include/pal/pal.h index 259082fb76..12788a86b3 100644 --- a/pal/include/pal/pal.h +++ b/pal/include/pal/pal.h @@ -23,6 +23,8 @@ #include "cpu.h" #endif +struct stat; /* forward declaration */ + /* TODO: we should `#include "toml.h"` here. However, this is currently inconvenient to do in Meson, * because `toml.h` is a generated (patched) file, and all targets built using `pal.h` would need to * declare a dependency on it. */ @@ -336,6 +338,8 @@ typedef uint32_t pal_stream_options_t; /* bitfield */ * \param share_flags A combination of the `PAL_SHARE_*` flags. * \param create See #pal_create_mode. * \param options A combination of the `PAL_OPTION_*` flags. + * \param create_delete_handle creates a limited use handle that can only be use to delete the + * associated stream. It does not open the specified stream. * \param handle[out] If the resource is successfully opened or created, a PAL handle is returned * in `*handle` for further access such as reading or writing. * @@ -352,7 +356,8 @@ typedef uint32_t pal_stream_options_t; /* bitfield */ * as the URI (i.e., without a name), it will open an anonymous bidirectional pipe. */ int PalStreamOpen(const char* typed_uri, enum pal_access access, pal_share_flags_t share_flags, - enum pal_create_mode create, pal_stream_options_t options, PAL_HANDLE* handle); + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle, PAL_HANDLE* handle); /*! * \brief Block until a new connection is accepted and return the PAL handle for the connection. diff --git a/pal/include/pal_internal.h b/pal/include/pal_internal.h index e149a9f001..5a56202a8d 100644 --- a/pal/include/pal_internal.h +++ b/pal/include/pal_internal.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "api.h" #include "log.h" @@ -41,7 +42,8 @@ struct handle_ops { * normalized prefix, 'uri' is the remaining string of uri. access, share, create, and options * follow the same flags defined for PalStreamOpen in pal.h. */ int (*open)(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, - pal_share_flags_t share, enum pal_create_mode create, pal_stream_options_t options); + pal_share_flags_t share, enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle); /* 'read' and 'write' is used by PalStreamRead and PalStreamWrite, so they have exactly same * prototype as them. */ @@ -93,6 +95,12 @@ struct handle_ops { extern const struct handle_ops* g_pal_handle_ops[]; +static inline const struct handle_ops* handle_ops_by_type(PAL_IDX type) { + if (type >= PAL_HANDLE_TYPE_BOUND) + return NULL; + return g_pal_handle_ops[type]; +} + static inline const struct handle_ops* HANDLE_OPS(PAL_HANDLE handle) { int _type = handle->hdr.type; if (_type < 0 || _type >= PAL_HANDLE_TYPE_BOUND) @@ -169,7 +177,7 @@ int _PalGetCPUInfo(struct pal_cpu_info* info); /* PalStream calls */ int _PalStreamOpen(PAL_HANDLE* handle, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options); + pal_stream_options_t options, bool create_delete_handle); int _PalStreamDelete(PAL_HANDLE handle, enum pal_delete_mode delete_mode); int64_t _PalStreamRead(PAL_HANDLE handle, uint64_t offset, uint64_t count, void* buf); int64_t _PalStreamWrite(PAL_HANDLE handle, uint64_t offset, uint64_t count, const void* buf); diff --git a/pal/regression/Directory.c b/pal/regression/Directory.c index ca65e4ddec..8c19ba8180 100644 --- a/pal/regression/Directory.c +++ b/pal/regression/Directory.c @@ -9,7 +9,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir1 = NULL; int ret = PalStreamOpen("dir:dir_exist.tmp", PAL_ACCESS_RDONLY, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &dir1); + PAL_CREATE_NEVER, /*options=*/0, false, &dir1); if (ret >= 0 && dir1) { pal_printf("Directory Open Test 1 OK\n"); @@ -32,7 +32,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir2 = NULL; ret = PalStreamOpen("dir:./dir_exist.tmp", PAL_ACCESS_RDONLY, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &dir2); + PAL_CREATE_NEVER, /*options=*/0, false, &dir2); if (ret >= 0 && dir2) { pal_printf("Directory Open Test 2 OK\n"); PalObjectDestroy(dir2); @@ -40,7 +40,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir3 = NULL; ret = PalStreamOpen("dir:../regression/dir_exist.tmp", PAL_ACCESS_RDONLY, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &dir3); + PAL_CREATE_NEVER, /*options=*/0, false, &dir3); if (ret >= 0 && dir3) { pal_printf("Directory Open Test 3 OK\n"); PalObjectDestroy(dir3); @@ -57,7 +57,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir4 = NULL; ret = PalStreamOpen("dir:dir_nonexist.tmp", PAL_ACCESS_RDONLY, PAL_SHARE_OWNER_R | PAL_SHARE_OWNER_W | PAL_SHARE_OWNER_X, - PAL_CREATE_ALWAYS, /*options=*/0, &dir4); + PAL_CREATE_ALWAYS, /*options=*/0, false, &dir4); if (ret >= 0 && dir4) { pal_printf("Directory Creation Test 1 OK\n"); PalObjectDestroy(dir4); @@ -66,7 +66,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir5 = NULL; ret = PalStreamOpen("dir:dir_nonexist.tmp", PAL_ACCESS_RDONLY, PAL_SHARE_OWNER_R | PAL_SHARE_OWNER_W | PAL_SHARE_OWNER_X, - PAL_CREATE_ALWAYS, /*options=*/0, &dir5); + PAL_CREATE_ALWAYS, /*options=*/0, false, &dir5); if (ret >= 0) { PalObjectDestroy(dir5); } else { @@ -76,7 +76,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir6 = NULL; ret = PalStreamOpen("dir:dir_nonexist.tmp", PAL_ACCESS_RDWR, PAL_SHARE_OWNER_R | PAL_SHARE_OWNER_W, PAL_CREATE_TRY, /*options=*/0, - &dir6); + false, &dir6); if (ret >= 0 && dir6) { pal_printf("Directory Creation Test 3 OK\n"); PalObjectDestroy(dir6); @@ -84,7 +84,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir7 = NULL; ret = PalStreamOpen("dir:dir_delete.tmp", PAL_ACCESS_RDONLY, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &dir7); + PAL_CREATE_NEVER, /*options=*/0, false, &dir7); if (ret >= 0 && dir7) { ret = PalStreamDelete(dir7, PAL_DELETE_ALL); if (ret < 0) { @@ -97,7 +97,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE dir8 = NULL; ret = PalStreamOpen("dir:dir_rename.tmp", PAL_ACCESS_RDWR, PAL_SHARE_OWNER_R | PAL_SHARE_OWNER_W | PAL_SHARE_OWNER_X, - PAL_CREATE_TRY, /*options=*/0, &dir8); + PAL_CREATE_TRY, /*options=*/0, false, &dir8); if (ret >= 0 && dir8) { ret = PalStreamChangeName(dir8, "dir:dir_rename_delete.tmp"); if (ret < 0) { diff --git a/pal/regression/File.c b/pal/regression/File.c index ea3005c2c9..2f4cc25667 100644 --- a/pal/regression/File.c +++ b/pal/regression/File.c @@ -26,7 +26,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE file1 = NULL; ret = PalStreamOpen("file:File.manifest", PAL_ACCESS_RDWR, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &file1); + PAL_CREATE_NEVER, /*options=*/0, false, &file1); if (ret >= 0 && file1) { pal_printf("File Open Test 1 OK\n"); @@ -94,7 +94,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE file2 = NULL; ret = PalStreamOpen("file:File.manifest", PAL_ACCESS_RDWR, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &file2); + PAL_CREATE_NEVER, /*options=*/0, false, &file2); if (ret >= 0 && file2) { pal_printf("File Open Test 2 OK\n"); PalObjectDestroy(file2); @@ -102,7 +102,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE file3 = NULL; ret = PalStreamOpen("file:../regression/File.manifest", PAL_ACCESS_RDWR, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &file3); + PAL_CREATE_NEVER, /*options=*/0, false, &file3); if (ret >= 0 && file3) { pal_printf("File Open Test 3 OK\n"); PalObjectDestroy(file3); @@ -119,14 +119,14 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE file4 = NULL; ret = PalStreamOpen("file:file_nonexist.tmp", PAL_ACCESS_RDWR, PAL_SHARE_OWNER_R | PAL_SHARE_OWNER_W, PAL_CREATE_ALWAYS, /*options=*/0, - &file4); + false, &file4); if (ret >= 0 && file4) pal_printf("File Creation Test 1 OK\n"); PAL_HANDLE file5 = NULL; ret = PalStreamOpen("file:file_nonexist.tmp", PAL_ACCESS_RDWR, PAL_SHARE_OWNER_R | PAL_SHARE_OWNER_W, PAL_CREATE_ALWAYS, /*options=*/0, - &file5); + false, &file5); if (ret >= 0) { PalObjectDestroy(file5); } else { @@ -136,7 +136,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE file6 = NULL; ret = PalStreamOpen("file:file_nonexist.tmp", PAL_ACCESS_RDWR, PAL_SHARE_OWNER_R | PAL_SHARE_OWNER_W, PAL_CREATE_TRY, /*options=*/0, - &file6); + false, &file6); if (ret >= 0 && file6) { pal_printf("File Creation Test 3 OK\n"); PalObjectDestroy(file6); @@ -175,7 +175,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE file7 = NULL; ret = PalStreamOpen("file:file_delete.tmp", PAL_ACCESS_RDONLY, /*share_flags=*/0, - PAL_CREATE_NEVER, /*options=*/0, &file7); + PAL_CREATE_NEVER, /*options=*/0, false, &file7); if (ret >= 0 && file7) { ret = PalStreamDelete(file7, PAL_DELETE_ALL); if (ret < 0) { diff --git a/pal/regression/File2.c b/pal/regression/File2.c index d364b84118..2bd8b7075d 100644 --- a/pal/regression/File2.c +++ b/pal/regression/File2.c @@ -10,7 +10,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE out = NULL; int ret = PalStreamOpen(FILE_URI, PAL_ACCESS_RDWR, PAL_SHARE_OWNER_W | PAL_SHARE_OWNER_R, - PAL_CREATE_TRY, /*options=*/0, &out); + PAL_CREATE_TRY, /*options=*/0, false, &out); if (ret < 0) { pal_printf("first PalStreamOpen failed\n"); @@ -28,7 +28,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE in = NULL; ret = PalStreamOpen(FILE_URI, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - /*options=*/0, &in); + /*options=*/0, false, &in); if (ret < 0) { pal_printf("third PalStreamOpen failed\n"); return 1; @@ -57,7 +57,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE del = NULL; ret = PalStreamOpen(FILE_URI, PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_NEVER, - /*options=*/0, &del); + /*options=*/0, false, &del); if (ret >= 0) { pal_printf("PalStreamDelete did not actually delete\n"); diff --git a/pal/regression/HelloWorld.c b/pal/regression/HelloWorld.c index 465c9e74f6..e07aef41c6 100644 --- a/pal/regression/HelloWorld.c +++ b/pal/regression/HelloWorld.c @@ -8,7 +8,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE out = NULL; int ret = PalStreamOpen("console:", PAL_ACCESS_WRONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - /*options=*/0, &out); + /*options=*/0, false, &out); if (ret < 0) { pal_printf("PalStreamOpen failed\n"); diff --git a/pal/regression/Pie.c b/pal/regression/Pie.c index 39814f2d4d..91fc0e3932 100644 --- a/pal/regression/Pie.c +++ b/pal/regression/Pie.c @@ -8,7 +8,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE out = NULL; int ret = PalStreamOpen("console:", PAL_ACCESS_WRONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - /*options=*/0, &out); + /*options=*/0, false, &out); if (ret < 0) { pal_printf("PalStreamOpen failed\n"); diff --git a/pal/regression/Pipe.c b/pal/regression/Pipe.c index 9e760b52a1..23316e0583 100644 --- a/pal/regression/Pipe.c +++ b/pal/regression/Pipe.c @@ -11,7 +11,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE pipe1 = NULL; ret = PalStreamOpen("pipe.srv:1", PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, - /*options=*/0, &pipe1); + /*options=*/0, false, &pipe1); if (ret >= 0 && pipe1) { pal_printf("Pipe Creation 1 OK\n"); @@ -30,7 +30,7 @@ int main(int argc, char** argv, char** envp) { PAL_HANDLE pipe2 = NULL; ret = PalStreamOpen("pipe:1", PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, - /*options=*/0, &pipe2); + /*options=*/0, false, &pipe2); if (ret >= 0 && pipe2) { PAL_HANDLE pipe3 = NULL; diff --git a/pal/regression/Process4.c b/pal/regression/Process4.c index 7232349d05..67b739b9c1 100644 --- a/pal/regression/Process4.c +++ b/pal/regression/Process4.c @@ -14,7 +14,7 @@ int main(int argc, char** argv) { if (argc == 1) { PAL_HANDLE pipe_srv = NULL; int ret = PalStreamOpen("pipe.srv:Process4", PAL_ACCESS_RDWR, /*share_flags=*/0, - PAL_CREATE_IGNORED, /*options=*/0, &pipe_srv); + PAL_CREATE_IGNORED, /*options=*/0, false, &pipe_srv); if (ret < 0) { pal_printf("PalStreamOpen(\"pipe.srv\", ...) failed: %d\n", ret); return 1; @@ -73,7 +73,7 @@ int main(int argc, char** argv) { PAL_HANDLE pipe = NULL; int ret = PalStreamOpen("pipe:Process4", PAL_ACCESS_RDWR, /*share_flags=*/0, - PAL_CREATE_IGNORED, /*options=*/0, &pipe); + PAL_CREATE_IGNORED, /*options=*/0, false, &pipe); if (ret < 0) { pal_printf("Failed to open pipe: %d\n", ret); return 1; diff --git a/pal/regression/send_handle.c b/pal/regression/send_handle.c index 26807347d0..7d54d6a783 100644 --- a/pal/regression/send_handle.c +++ b/pal/regression/send_handle.c @@ -94,12 +94,12 @@ static void do_parent(void) { /* pipe.srv handle */ CHECK(PalStreamOpen("pipe.srv:1", PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, - /*options=*/0, &handle)); + /*options=*/0, false, &handle)); CHECK(PalSendHandle(child_process, handle)); PalObjectDestroy(handle); CHECK(PalStreamOpen("pipe:1", PAL_ACCESS_RDWR, /*share_flags=*/0, PAL_CREATE_IGNORED, - /*options=*/0, &handle)); + /*options=*/0, false, &handle)); recv_and_check(handle, PAL_TYPE_PIPE); PalObjectDestroy(handle); @@ -145,7 +145,7 @@ static void do_parent(void) { /* file handle */ CHECK(PalStreamOpen("file:to_send.tmp", PAL_ACCESS_RDWR, /*share_flags=*/0600, PAL_CREATE_TRY, - /*options=*/0, &handle)); + /*options=*/0, false, &handle)); write_msg(handle, PAL_TYPE_FILE); CHECK(PalSendHandle(child_process, handle)); PalObjectDestroy(handle); diff --git a/pal/src/host/linux-sgx/pal_console.c b/pal/src/host/linux-sgx/pal_console.c index ed89a9624c..0a097bc596 100644 --- a/pal/src/host/linux-sgx/pal_console.c +++ b/pal/src/host/linux-sgx/pal_console.c @@ -23,11 +23,13 @@ static int console_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, - enum pal_create_mode create, pal_stream_options_t options) { + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle) { __UNUSED(uri); __UNUSED(share); __UNUSED(create); __UNUSED(options); + __UNUSED(create_delete_handle); if (strcmp(type, URI_TYPE_CONSOLE)) return -PAL_ERROR_INVAL; diff --git a/pal/src/host/linux-sgx/pal_devices.c b/pal/src/host/linux-sgx/pal_devices.c index ff5f1fb324..6d5552d6f1 100644 --- a/pal/src/host/linux-sgx/pal_devices.c +++ b/pal/src/host/linux-sgx/pal_devices.c @@ -26,7 +26,8 @@ static int dev_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { + __UNUSED(create_delete_handle); int ret; char* normpath = NULL; diff --git a/pal/src/host/linux-sgx/pal_eventfd.c b/pal/src/host/linux-sgx/pal_eventfd.c index 8abf138b1c..eb4bd081e9 100644 --- a/pal/src/host/linux-sgx/pal_eventfd.c +++ b/pal/src/host/linux-sgx/pal_eventfd.c @@ -30,10 +30,12 @@ static inline int eventfd_type(int options) { * `options` holds eventfd's flags */ static int eventfd_pal_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, - enum pal_create_mode create, pal_stream_options_t options) { + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle) { __UNUSED(access); __UNUSED(share); __UNUSED(create); + __UNUSED(create_delete_handle); assert(create == PAL_CREATE_IGNORED); if (strcmp(type, URI_TYPE_EVENTFD) != 0 || *uri != '\0') { diff --git a/pal/src/host/linux-sgx/pal_files.c b/pal/src/host/linux-sgx/pal_files.c index 0f8c50a868..b7a5da3814 100644 --- a/pal/src/host/linux-sgx/pal_files.c +++ b/pal/src/host/linux-sgx/pal_files.c @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: LGPL-3.0-or-later */ -/* Copyright (C) 2014 Stony Brook University */ +/* Copyright (C) 2014 Stony Brook University + * Copyright (C) 2024 Fortanix, Inc. + * Bobby Marinov + */ /* * This file contains operands to handle streams with URIs that start with "file:" or "dir:". @@ -30,14 +33,15 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access pal_access, pal_share_flags_t pal_share, - enum pal_create_mode pal_create, pal_stream_options_t pal_options) { + enum pal_create_mode pal_create, pal_stream_options_t pal_options, + bool create_delete_handle) { assert(pal_create != PAL_CREATE_IGNORED); int ret; int fd = -1; PAL_HANDLE hdl = NULL; bool do_create = (pal_create == PAL_CREATE_ALWAYS) || (pal_create == PAL_CREATE_TRY); - struct stat st; + struct stat st = {0}; int flags = PAL_ACCESS_TO_LINUX_OPEN(pal_access) | PAL_CREATE_TO_LINUX_OPEN(pal_create) | PAL_OPTION_TO_LINUX_OPEN(pal_options) | O_CLOEXEC; @@ -85,16 +89,18 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, } if (!tf) { - fd = ocall_open(uri, flags, pal_share); - if (fd < 0) { - ret = unix_to_pal_error(fd); - goto fail; - } + if (!create_delete_handle) { + fd = ocall_open(uri, flags, pal_share); + if (fd < 0) { + ret = unix_to_pal_error(fd); + goto fail; + } - ret = ocall_fstat(fd, &st); - if (ret < 0) { - ret = unix_to_pal_error(ret); - goto fail; + ret = ocall_fstat(fd, &st); + if (ret < 0) { + ret = unix_to_pal_error(ret); + goto fail; + } } hdl->file.fd = fd; @@ -115,35 +121,39 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, goto fail; } - fd = ocall_open(uri, flags, pal_share); - if (fd < 0) { - ret = unix_to_pal_error(fd); - goto fail; - } + if (!create_delete_handle) { + fd = ocall_open(uri, flags, pal_share); + if (fd < 0) { + ret = unix_to_pal_error(fd); + goto fail; + } - ret = ocall_fstat(fd, &st); - if (ret < 0) { - ret = unix_to_pal_error(ret); - goto fail; + ret = ocall_fstat(fd, &st); + if (ret < 0) { + ret = unix_to_pal_error(ret); + goto fail; + } } hdl->file.fd = fd; hdl->file.seekable = !S_ISFIFO(st.st_mode); hdl->file.total = st.st_size; - sgx_chunk_hash_t* chunk_hashes; - uint64_t total; - void* umem; + if (!create_delete_handle) { + sgx_chunk_hash_t* chunk_hashes; + uint64_t total; + void* umem; - /* we lazily update the size of the trusted file */ - tf->size = st.st_size; - ret = load_trusted_or_allowed_file(tf, hdl, do_create, &chunk_hashes, &total, &umem); - if (ret < 0) - goto fail; + /* we lazily update the size of the trusted file */ + tf->size = st.st_size; + ret = load_trusted_or_allowed_file(tf, hdl, do_create, &chunk_hashes, &total, &umem); + if (ret < 0) + goto fail; - hdl->file.chunk_hashes = chunk_hashes; - hdl->file.total = total; - hdl->file.umem = umem; + hdl->file.chunk_hashes = chunk_hashes; + hdl->file.total = total; + hdl->file.umem = umem; + } *handle = hdl; return 0; @@ -503,8 +513,9 @@ struct handle_ops g_file_ops = { * ended with slashes. dir_open will be called by file_open. */ static int dir_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { __UNUSED(access); + __UNUSED(create_delete_handle); if (strcmp(type, URI_TYPE_DIR)) return -PAL_ERROR_INVAL; diff --git a/pal/src/host/linux-sgx/pal_linux_error.h b/pal/src/host/linux-sgx/pal_linux_error.h index ccb3396b80..c91e56cf5a 100644 --- a/pal/src/host/linux-sgx/pal_linux_error.h +++ b/pal/src/host/linux-sgx/pal_linux_error.h @@ -51,6 +51,10 @@ static int unix_to_pal_error_positive(int unix_errno) { return PAL_ERROR_CONNFAILED_PIPE; case EAFNOSUPPORT: return PAL_ERROR_AFNOSUPPORT; + case ELOOP: + return PAL_ERROR_LOOP; + case EPERM: + return PAL_ERROR_NO_PERMISSION; default: return PAL_ERROR_DENIED; } diff --git a/pal/src/host/linux-sgx/pal_pipes.c b/pal/src/host/linux-sgx/pal_pipes.c index 4846e77626..b6657103dd 100644 --- a/pal/src/host/linux-sgx/pal_pipes.c +++ b/pal/src/host/linux-sgx/pal_pipes.c @@ -328,9 +328,10 @@ static int pipe_connect(PAL_HANDLE* handle, const char* name, pal_stream_options */ static int pipe_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { __UNUSED(access); __UNUSED(create); + __UNUSED(create_delete_handle); assert(create == PAL_CREATE_IGNORED); if (!WITHIN_MASK(share, PAL_SHARE_MASK) || !WITHIN_MASK(options, PAL_OPTION_MASK)) diff --git a/pal/src/host/linux/pal_console.c b/pal/src/host/linux/pal_console.c index 646e193c00..97ec13dbe3 100644 --- a/pal/src/host/linux/pal_console.c +++ b/pal/src/host/linux/pal_console.c @@ -22,11 +22,13 @@ static int console_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, - enum pal_create_mode create, pal_stream_options_t options) { + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle) { __UNUSED(uri); __UNUSED(share); __UNUSED(create); __UNUSED(options); + __UNUSED(create_delete_handle); if (strcmp(type, URI_TYPE_CONSOLE)) return -PAL_ERROR_INVAL; diff --git a/pal/src/host/linux/pal_devices.c b/pal/src/host/linux/pal_devices.c index c8275f031e..f9882dd6fe 100644 --- a/pal/src/host/linux/pal_devices.c +++ b/pal/src/host/linux/pal_devices.c @@ -22,7 +22,8 @@ static int dev_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { + __UNUSED(create_delete_handle); int ret; char* normpath = NULL; diff --git a/pal/src/host/linux/pal_eventfd.c b/pal/src/host/linux/pal_eventfd.c index 00e2f75afd..aefff9ca76 100644 --- a/pal/src/host/linux/pal_eventfd.c +++ b/pal/src/host/linux/pal_eventfd.c @@ -33,10 +33,12 @@ static inline int eventfd_type(pal_stream_options_t options) { * `options` holds eventfd's flags */ static int eventfd_pal_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, - enum pal_create_mode create, pal_stream_options_t options) { + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle) { __UNUSED(access); __UNUSED(share); __UNUSED(create); + __UNUSED(create_delete_handle); assert(create == PAL_CREATE_IGNORED); if (strcmp(type, URI_TYPE_EVENTFD) != 0 || *uri != '\0') { diff --git a/pal/src/host/linux/pal_files.c b/pal/src/host/linux/pal_files.c index 1954610555..d8575ba3b4 100644 --- a/pal/src/host/linux/pal_files.c +++ b/pal/src/host/linux/pal_files.c @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: LGPL-3.0-or-later */ -/* Copyright (C) 2014 Stony Brook University */ +/* Copyright (C) 2014 Stony Brook University + * Copyright (C) 2024 Fortanix, Inc. + * Bobby Marinov + */ /* * This file contains operands to handle streams with URIs that start with "file:" or "dir:". @@ -19,22 +22,27 @@ /* 'open' operation for file streams */ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { if (strcmp(type, URI_TYPE_FILE)) return -PAL_ERROR_INVAL; - assert(WITHIN_MASK(share, PAL_SHARE_MASK)); - assert(WITHIN_MASK(options, PAL_OPTION_MASK)); + int ret = -1; - /* try to do the real open */ - int ret = DO_SYSCALL(open, uri, PAL_ACCESS_TO_LINUX_OPEN(access) | - PAL_CREATE_TO_LINUX_OPEN(create) | - PAL_OPTION_TO_LINUX_OPEN(options) | - O_CLOEXEC, - share); + if (!create_delete_handle) { + assert(WITHIN_MASK(share, PAL_SHARE_MASK)); + assert(WITHIN_MASK(options, PAL_OPTION_MASK)); - if (ret < 0) - return unix_to_pal_error(ret); + /* try to do the real open */ + int oflags = PAL_ACCESS_TO_LINUX_OPEN(access) | + PAL_CREATE_TO_LINUX_OPEN(create) | + PAL_OPTION_TO_LINUX_OPEN(options)| + O_CLOEXEC ; + ret = DO_SYSCALL(open, uri, oflags, share); + if (ret < 0) { + ret = unix_to_pal_error(ret); + return ret; + } + } /* if try_create_path succeeded, prepare for the file handle */ size_t uri_size = strlen(uri) + 1; @@ -64,16 +72,19 @@ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, enum hdl->file.realpath = path; - struct stat st; - ret = DO_SYSCALL(fstat, hdl->file.fd, &st); - if (ret < 0) { - DO_SYSCALL(close, hdl->file.fd); - free(hdl); - free(path); - return unix_to_pal_error(ret); - } + if (!create_delete_handle) { + struct stat st; + ret = DO_SYSCALL(fstat, hdl->file.fd, &st); + if (ret < 0) { + DO_SYSCALL(close, hdl->file.fd); + free(hdl); + free(path); + return unix_to_pal_error(ret); + } - hdl->file.seekable = !S_ISFIFO(st.st_mode); + hdl->file.seekable = !S_ISFIFO(st.st_mode); + } else + hdl->file.seekable = true; *handle = hdl; return 0; @@ -116,10 +127,12 @@ static int64_t file_write(PAL_HANDLE handle, uint64_t offset, uint64_t count, co static void file_destroy(PAL_HANDLE handle) { assert(handle->hdr.type == PAL_TYPE_FILE); - int ret = DO_SYSCALL(close, handle->file.fd); - if (ret < 0) { - log_error("closing file host fd %d failed: %s", handle->file.fd, unix_strerror(ret)); - /* We cannot do anything about it anyway... */ + if ((int)handle->file.fd != -1) { + int ret = DO_SYSCALL(close, handle->file.fd); + if (ret < 0) { + log_error("closing file host fd %d failed: %s", handle->file.fd, unix_strerror(ret)); + /* We cannot do anything about it anyway... */ + } } free(handle->file.realpath); @@ -254,8 +267,9 @@ struct handle_ops g_file_ops = { ended with slashes. dir_open will be called by file_open. */ static int dir_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { __UNUSED(access); + __UNUSED(create_delete_handle); assert(create != PAL_CREATE_IGNORED); if (strcmp(type, URI_TYPE_DIR)) return -PAL_ERROR_INVAL; diff --git a/pal/src/host/linux/pal_linux_error.h b/pal/src/host/linux/pal_linux_error.h index ccb3396b80..c91e56cf5a 100644 --- a/pal/src/host/linux/pal_linux_error.h +++ b/pal/src/host/linux/pal_linux_error.h @@ -51,6 +51,10 @@ static int unix_to_pal_error_positive(int unix_errno) { return PAL_ERROR_CONNFAILED_PIPE; case EAFNOSUPPORT: return PAL_ERROR_AFNOSUPPORT; + case ELOOP: + return PAL_ERROR_LOOP; + case EPERM: + return PAL_ERROR_NO_PERMISSION; default: return PAL_ERROR_DENIED; } diff --git a/pal/src/host/linux/pal_pipes.c b/pal/src/host/linux/pal_pipes.c index 4c06011df1..d016a2cf14 100644 --- a/pal/src/host/linux/pal_pipes.c +++ b/pal/src/host/linux/pal_pipes.c @@ -190,9 +190,10 @@ static int pipe_connect(PAL_HANDLE* handle, const char* name, pal_stream_options */ static int pipe_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { __UNUSED(access); __UNUSED(create); + __UNUSED(create_delete_handle); assert(create == PAL_CREATE_IGNORED); if (!WITHIN_MASK(share, PAL_SHARE_MASK) || !WITHIN_MASK(options, PAL_OPTION_MASK)) diff --git a/pal/src/host/skeleton/pal_console.c b/pal/src/host/skeleton/pal_console.c index e08f278fd6..da85f64c3a 100644 --- a/pal/src/host/skeleton/pal_console.c +++ b/pal/src/host/skeleton/pal_console.c @@ -15,7 +15,8 @@ static int console_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, - enum pal_create_mode create, pal_stream_options_t options) { + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle) { return -PAL_ERROR_NOTIMPLEMENTED; } diff --git a/pal/src/host/skeleton/pal_devices.c b/pal/src/host/skeleton/pal_devices.c index 5c5d4fd84c..e6c8a7282d 100644 --- a/pal/src/host/skeleton/pal_devices.c +++ b/pal/src/host/skeleton/pal_devices.c @@ -12,7 +12,7 @@ static int dev_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { return -PAL_ERROR_NOTIMPLEMENTED; } diff --git a/pal/src/host/skeleton/pal_eventfd.c b/pal/src/host/skeleton/pal_eventfd.c index af9aeda635..b7f614fde8 100644 --- a/pal/src/host/skeleton/pal_eventfd.c +++ b/pal/src/host/skeleton/pal_eventfd.c @@ -14,7 +14,8 @@ * `options` holds eventfd's flags */ static int eventfd_pal_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, - enum pal_create_mode create, pal_stream_options_t options) { + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle) { return -PAL_ERROR_NOTIMPLEMENTED; } diff --git a/pal/src/host/skeleton/pal_files.c b/pal/src/host/skeleton/pal_files.c index 7f7dd1f35c..f42efbf9d1 100644 --- a/pal/src/host/skeleton/pal_files.c +++ b/pal/src/host/skeleton/pal_files.c @@ -13,7 +13,7 @@ /* 'open' operation for file streams */ static int file_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { return -PAL_ERROR_NOTIMPLEMENTED; } @@ -85,7 +85,7 @@ struct handle_ops g_file_ops = { * file_open. */ static int dir_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { return -PAL_ERROR_NOTIMPLEMENTED; } diff --git a/pal/src/host/skeleton/pal_pipes.c b/pal/src/host/skeleton/pal_pipes.c index 8f77bfee3e..b3591336f0 100644 --- a/pal/src/host/skeleton/pal_pipes.c +++ b/pal/src/host/skeleton/pal_pipes.c @@ -24,7 +24,7 @@ static int pipe_connect(PAL_HANDLE* handle, const char* name, pal_stream_options static int pipe_open(PAL_HANDLE* handle, const char* type, const char* uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { if (!strcmp(type, URI_TYPE_PIPE_SRV)) return pipe_listen(handle, uri, options); diff --git a/pal/src/pal_main.c b/pal/src/pal_main.c index 6ce935f1c3..5d54d1afd1 100644 --- a/pal/src/pal_main.c +++ b/pal/src/pal_main.c @@ -313,7 +313,7 @@ static int load_cstring_array(const char* uri, const char*** res) { int ret; ret = _PalStreamOpen(&hdl, uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - /*options=*/0); + /*options=*/0, false); if (ret < 0) return ret; ret = _PalStreamAttributesQueryByHandle(hdl, &attr); diff --git a/pal/src/pal_rtld.c b/pal/src/pal_rtld.c index f6f9df5f5b..ff3fd1bbf4 100644 --- a/pal/src/pal_rtld.c +++ b/pal/src/pal_rtld.c @@ -644,7 +644,7 @@ int load_entrypoint(const char* uri) { char buf[1024]; /* must be enough to hold ELF header and all its program headers */ ret = _PalStreamOpen(&handle, uri, PAL_ACCESS_RDONLY, /*share_flags=*/0, PAL_CREATE_NEVER, - /*options=*/0); + /*options=*/0, false); if (ret < 0) return ret; diff --git a/pal/src/pal_streams.c b/pal/src/pal_streams.c index 10d342db1f..f01f9bcfb0 100644 --- a/pal/src/pal_streams.c +++ b/pal/src/pal_streams.c @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: LGPL-3.0-or-later */ -/* Copyright (C) 2014 Stony Brook University */ +/* Copyright (C) 2014 Stony Brook University + * Copyright (C) 2024 Fortanix, Inc. + * Bobby Marinov + */ /* * This file contains APIs to open, read, write and get attribute of streams. @@ -76,7 +79,7 @@ static int split_uri_and_find_ops(const char* typed_uri, char* out_type, const c int _PalStreamOpen(PAL_HANDLE* handle, const char* typed_uri, enum pal_access access, pal_share_flags_t share, enum pal_create_mode create, - pal_stream_options_t options) { + pal_stream_options_t options, bool create_delete_handle) { assert(WITHIN_MASK(share, PAL_SHARE_MASK)); assert(WITHIN_MASK(options, PAL_OPTION_MASK)); @@ -89,7 +92,7 @@ int _PalStreamOpen(PAL_HANDLE* handle, const char* typed_uri, enum pal_access ac return ret; assert(ops && ops->open); - return ops->open(handle, type, uri, access, share, create, options); + return ops->open(handle, type, uri, access, share, create, options, create_delete_handle); } /* @@ -100,9 +103,10 @@ int _PalStreamOpen(PAL_HANDLE* handle, const char* typed_uri, enum pal_access ac * portable and will cause problems when implementing other PALs. */ int PalStreamOpen(const char* typed_uri, enum pal_access access, pal_share_flags_t share, - enum pal_create_mode create, pal_stream_options_t options, PAL_HANDLE* handle) { + enum pal_create_mode create, pal_stream_options_t options, + bool create_delete_handle, PAL_HANDLE* handle) { *handle = NULL; - return _PalStreamOpen(handle, typed_uri, access, share, create, options); + return _PalStreamOpen(handle, typed_uri, access, share, create, options, create_delete_handle); } static int _PalStreamWaitForClient(PAL_HANDLE handle, PAL_HANDLE* client,