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,