Skip to content

Commit

Permalink
Add support for symlinks for regular files
Browse files Browse the repository at this point in the history
Updated per first review:
- enabled symlink and blocked by symlink LTP tests
- fixed problems found by LTP tests
- Updated files copyright
- added symlink information to the documentation

Signed-off-by: Bobby Marinov <[email protected]>
  • Loading branch information
BobbyAtFortanix committed Feb 13, 2024
1 parent 91bc249 commit ab06f66
Show file tree
Hide file tree
Showing 53 changed files with 281 additions and 623 deletions.
32 changes: 19 additions & 13 deletions Documentation/devel/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,13 +419,13 @@ The below list is generated from the [syscall table of Linux
-`creat()`
<sup>[9a](#file-system-operations)</sup>

- `link()`
- `link()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

-`unlink()`
<sup>[9a](#file-system-operations)</sup>

- `symlink()`
- `symlink()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

-`readlink()`
Expand Down Expand Up @@ -966,10 +966,10 @@ The below list is generated from the [syscall table of Linux
-`renameat()`
<sup>[9a](#file-system-operations)</sup>

- `linkat()`
- `linkat()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

- `symlinkat()`
- `symlinkat()`
<sup>[9d](#hard-links-and-soft-links-symbolic-links)</sup>

-`readlinkat()`
Expand Down Expand Up @@ -2068,6 +2068,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 supports 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.

Expand Down Expand Up @@ -2294,21 +2297,24 @@ 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 no support for symlinks (i.e., applications can create links).
- 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.

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.

<details><summary>Related system calls</summary>

- `link()`
- `symlink()`
- `link()`: see note above
- `symlink()`
-`readlink()`: see note above
- `linkat()`
- `symlinkat()`
- `linkat()`
- `symlinkat()`
-`readlinkat()`: see note above
-`lchown()`

Expand Down
6 changes: 3 additions & 3 deletions Documentation/devel/new-syscall.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------------------------
Expand Down
8 changes: 5 additions & 3 deletions libos/src/fs/chroot/encrypted.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2022 Intel Corporation
* Copyright (C) 2024 Fortanix, Inc.
* Paweł Marczewski <[email protected]>
* Bobby Marinov <[email protected]>
*/

/*
Expand Down Expand Up @@ -260,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;
Expand Down Expand Up @@ -292,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;
Expand Down Expand Up @@ -491,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;
Expand Down
107 changes: 13 additions & 94 deletions libos/src/fs/chroot/fs.c
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
* Bobby Marinov <[email protected]>
*/

/*
Expand Down Expand Up @@ -183,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);
}
Expand All @@ -202,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;
Expand All @@ -212,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;
Expand Down Expand Up @@ -348,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;

Expand Down Expand Up @@ -409,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;

Expand All @@ -433,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;

Expand All @@ -450,96 +453,14 @@ static int chroot_rename(struct libos_dentry* old, struct libos_dentry* new) {
return ret;
}

static int chroot_follow_link(struct libos_dentry* link_dent, char** out_target) {
assert(locked(&g_dcache_lock));

int ret;
char* new_uri = NULL;
char* target_buf = NULL;

/* get link path (no prefix) */
ret = chroot_dentry_uri(link_dent, S_IFREG, &new_uri);
if (ret < 0) {
goto out;
}
assert(strstartswith(new_uri, URI_PREFIX_FILE));
char* linkpath = new_uri + URI_PREFIX_FILE_LEN;
if ((*(linkpath + 0) == "./"[0]) && (*(linkpath + 1) == "./"[1])) {
linkpath += 2;
}

struct stat sb;
ret = PalGetLinkStats(linkpath, &sb);
if (ret < 0) {
ret = pal_to_unix_errno(ret);
goto out;
}

size_t target_sz = sb.st_size + 1;
target_buf = malloc(target_sz);
if (target_buf == NULL) {
ret = -ENOMEM;
goto out;
}

size_t ret_len = 0;
ret = PalReadLink(linkpath, target_buf, target_sz - 1, &ret_len);
if (ret < 0) {
ret = pal_to_unix_errno(ret);
goto out;
}

assert(ret_len < target_sz);
*(target_buf + ret_len) = '\x0';

*out_target = target_buf;
target_buf = NULL;
ret = 0;

out:
free(target_buf);
free(new_uri);
return ret;
}

static int chroot_set_link(struct libos_dentry* link_dent, const char* targetpath,
bool is_soft_link) {
assert(locked(&g_dcache_lock));

int ret;
char* new_uri = NULL;

/* get link path (no prefix) */
ret = chroot_dentry_uri(link_dent, S_IFREG, &new_uri);
if (ret < 0) {
goto out;
}
assert(strstartswith(new_uri, URI_PREFIX_FILE));
char* linkpath = new_uri + URI_PREFIX_FILE_LEN;
if ((*(linkpath + 0) == '.') && (*(linkpath + 1) == '/')) {
linkpath += 2;
}

ret = PalCreateLink(targetpath, linkpath, is_soft_link);
if (ret < 0) {
ret = pal_to_unix_errno(ret);
goto out;
}
ret = 0;

out:
free(new_uri);
return ret;
}

static int chroot_chmod(struct libos_dentry* dent, mode_t perm) {
assert(locked(&g_dcache_lock));
assert(dent->inode);

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;

Expand Down Expand Up @@ -589,10 +510,8 @@ struct libos_d_ops chroot_d_ops = {
.stat = &generic_inode_stat,
.readdir = &chroot_readdir,
.unlink = &chroot_unlink,
.follow_link = &chroot_follow_link,
.set_link = &chroot_set_link,
.rename = &chroot_rename,
.chmod = &chroot_chmod,
.rename = &chroot_rename,
.chmod = &chroot_chmod,
};

struct libos_fs chroot_builtin_fs = {
Expand Down
2 changes: 1 addition & 1 deletion libos/src/fs/dev/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion libos/src/fs/libos_fs_encrypted.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,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);
Expand Down
9 changes: 9 additions & 0 deletions libos/src/fs/libos_namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion libos/src/fs/shm/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions libos/src/fs/tmpfs/fs.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2021 Intel Corporation
* Copyright (C) 2024 Fortanix, Inc.
* Li Xun <[email protected]>
* Paweł Marczewski <[email protected]>
* Bobby Marinov <[email protected]>
*/

/*
Expand Down
2 changes: 1 addition & 1 deletion libos/src/ipc/libos_ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion libos/src/libos_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -566,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 */
Expand Down
2 changes: 1 addition & 1 deletion libos/src/libos_pollable_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Loading

0 comments on commit ab06f66

Please sign in to comment.