From dd56fbe267e97bee8b7248284466dfee4dfdd8ff Mon Sep 17 00:00:00 2001 From: Dmitrii Kuvaiskii Date: Mon, 7 Dec 2020 06:12:45 -0800 Subject: [PATCH] [LibOS] Refactor /proc/[tid|pid] logic Graphene emulates `/proc/[tid|pid]` subdirectory using the pseudo-FS logic under fs/proc/thread.c and fs/proc/ipc-thread.c. This commit refactors these files: name changes, better structure, using string.h functions, small bug fixes. The IPC-thread logic is significantly simplified and partly commented out (it was never used and probably will never be used, but still keeping it just in case). The LibOS `proc_common` test is augmented to test more functionality. --- LibOS/shim/include/shim_fs.h | 4 +- LibOS/shim/src/fs/proc/fs.c | 38 +- LibOS/shim/src/fs/proc/info.c | 6 +- LibOS/shim/src/fs/proc/ipc-thread.c | 488 ++++++------ LibOS/shim/src/fs/proc/thread.c | 941 +++++++++++------------ LibOS/shim/src/fs/shim_fs_pseudo.c | 20 +- LibOS/shim/test/regression/proc_common.c | 69 ++ LibOS/shim/test/regression/test_libos.py | 4 + 8 files changed, 769 insertions(+), 801 deletions(-) diff --git a/LibOS/shim/include/shim_fs.h b/LibOS/shim/include/shim_fs.h index 8d8337d9..10b8fd49 100644 --- a/LibOS/shim/include/shim_fs.h +++ b/LibOS/shim/include/shim_fs.h @@ -483,8 +483,8 @@ extern struct shim_fs_ops proc_fs_ops; extern struct shim_d_ops proc_d_ops; struct pseudo_name_ops { - int (*match_name)(const char* name); - int (*list_name)(const char* name, struct shim_dirent** buf, int count); + int (*match_path)(const char* relpath); + int (*list_dirents)(const char* relpath, struct shim_dirent** buf, size_t size); }; static inline dev_t makedev(unsigned int major, unsigned int minor) { diff --git a/LibOS/shim/src/fs/proc/fs.c b/LibOS/shim/src/fs/proc/fs.c index e8c3b925..5772e2d8 100644 --- a/LibOS/shim/src/fs/proc/fs.c +++ b/LibOS/shim/src/fs/proc/fs.c @@ -2,42 +2,40 @@ /* Copyright (C) 2014 Stony Brook University */ /*! - * \file - * * This file contains the implementation of `/proc` pseudo-filesystem. */ #include "shim_fs.h" -extern const struct pseudo_name_ops nm_thread; -extern const struct pseudo_fs_ops fs_thread; -extern const struct pseudo_dir dir_thread; +extern const struct pseudo_name_ops proc_thread_name_ops; +extern const struct pseudo_fs_ops proc_thread_fs_ops; +extern const struct pseudo_dir proc_thread_dir; -extern const struct pseudo_name_ops nm_ipc_thread; -extern const struct pseudo_fs_ops fs_ipc_thread; -extern const struct pseudo_dir dir_ipc_thread; +extern const struct pseudo_name_ops proc_ipc_thread_name_ops; +extern const struct pseudo_fs_ops proc_ipc_thread_fs_ops; +extern const struct pseudo_dir proc_ipc_thread_dir; -extern const struct pseudo_fs_ops fs_meminfo; +extern const struct pseudo_fs_ops proc_meminfo_fs_ops; -extern const struct pseudo_fs_ops fs_cpuinfo; +extern const struct pseudo_fs_ops proc_cpuinfo_fs_ops; static const struct pseudo_dir proc_root_dir = { .size = 5, .ent = { {.name = "self", - .fs_ops = &fs_thread, - .dir = &dir_thread}, - {.name_ops = &nm_thread, - .fs_ops = &fs_thread, - .dir = &dir_thread}, - {.name_ops = &nm_ipc_thread, - .fs_ops = &fs_ipc_thread, - .dir = &dir_ipc_thread}, + .fs_ops = &proc_thread_fs_ops, + .dir = &proc_thread_dir}, + {.name_ops = &proc_thread_name_ops, + .fs_ops = &proc_thread_fs_ops, + .dir = &proc_thread_dir}, + {.name_ops = &proc_ipc_thread_name_ops, + .fs_ops = &proc_ipc_thread_fs_ops, + .dir = &proc_ipc_thread_dir}, {.name = "meminfo", - .fs_ops = &fs_meminfo, + .fs_ops = &proc_meminfo_fs_ops, .type = LINUX_DT_REG}, {.name = "cpuinfo", - .fs_ops = &fs_cpuinfo, + .fs_ops = &proc_cpuinfo_fs_ops, .type = LINUX_DT_REG}, }}; diff --git a/LibOS/shim/src/fs/proc/info.c b/LibOS/shim/src/fs/proc/info.c index 31590745..c45777bf 100644 --- a/LibOS/shim/src/fs/proc/info.c +++ b/LibOS/shim/src/fs/proc/info.c @@ -2,8 +2,6 @@ /* Copyright (C) 2014 Stony Brook University */ /*! - * \file - * * This file contains the implementation of `/proc/meminfo` and `/proc/cpuinfo`. */ @@ -179,13 +177,13 @@ static int proc_cpuinfo_open(struct shim_handle* hdl, const char* name, int flag return 0; } -struct pseudo_fs_ops fs_meminfo = { +struct pseudo_fs_ops proc_meminfo_fs_ops = { .mode = &proc_info_mode, .stat = &proc_info_stat, .open = &proc_meminfo_open, }; -struct pseudo_fs_ops fs_cpuinfo = { +struct pseudo_fs_ops proc_cpuinfo_fs_ops = { .mode = &proc_info_mode, .stat = &proc_info_stat, .open = &proc_cpuinfo_open, diff --git a/LibOS/shim/src/fs/proc/ipc-thread.c b/LibOS/shim/src/fs/proc/ipc-thread.c index 2eb1221e..fcf72d47 100644 --- a/LibOS/shim/src/fs/proc/ipc-thread.c +++ b/LibOS/shim/src/fs/proc/ipc-thread.c @@ -1,8 +1,14 @@ -#include -#include +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2014 Stony Brook University */ +/* Copyright (C) 2020 Intel Corporation */ + +/*! + * This file contains the implementation of `/proc/[remote-pid]` dir. Currently only adding remote + * PIDs to the list of `/proc/[pids]` and checking PID existence ("match path") are implemented. + */ + #include #include -#include #include "pal.h" #include "pal_error.h" @@ -17,366 +23,310 @@ #include "shim_utils.h" #include "stat.h" -static int parse_ipc_thread_name(const char* name, IDTYPE* pidptr, const char** next, - size_t* next_len, const char** nextnext) { - const char* p = name; - IDTYPE pid = 0; +static struct pid_status_cache { + size_t status_num; + struct pid_status* status; +} g_pid_status_cache; - if (*p == '/') - p++; +static struct shim_lock g_pid_status_lock; - for (; *p && *p != '/'; p++) { - if (*p < '0' || *p > '9') - return -ENOENT; +/* returns PID of the remote process found in relpath and pointer to the rest of relpath string + * (e.g. "42/cwd" returns 42 in `*pid_ptr` and pointer to "cwd" in `rest` if process 42 exists) */ +static int get_pid_from_relpath(const char* relpath, IDTYPE* pid_ptr, char** rest) { + if (*relpath == '\0' || *relpath == '/') + return -ENOENT; - pid = pid * 10 + *p - '0'; + char* pid_end = NULL; + IDTYPE pid = (IDTYPE)strtol(relpath, &pid_end, /*base=*/10); + + if (!pid_end || (*pid_end != '\0' && *pid_end != '/')) + return -ENOENT; + + if (!create_lock_runtime(&g_pid_status_lock)) + return -ENOMEM; + + lock(&g_pid_status_lock); + + if (!g_pid_status_cache.status_num || !g_pid_status_cache.status) { + unlock(&g_pid_status_lock); + return -ENOENT; } - if (next) { - if (*(p++) == '/' && *p) { - *next = p; - - if (next_len || nextnext) - for (; *p && *p != '/'; p++) - ; - - if (next_len) - *next_len = p - *next; - - if (nextnext) - *nextnext = (*(p++) == '/' && *p) ? p : NULL; - } else { - *next = NULL; + bool found_pid = false; + for (size_t i = 0; i < g_pid_status_cache.status_num; i++) { + if (g_pid_status_cache.status[i].pid == pid) { + found_pid = true; + break; } } - if (pidptr) - *pidptr = pid; + unlock(&g_pid_status_lock); + + if (!found_pid) + return -ENOENT; + + *pid_ptr = pid; + if (rest) + *rest = *pid_end == '\0' ? NULL : pid_end + 1; return 0; } -static int find_ipc_thread_link(const char* name, struct shim_qstr* link, - struct shim_dentry** dentptr) { - const char* next; - const char* nextnext; - size_t next_len; - IDTYPE pid; +/* returns dentry corresponding to PID's "root"/"cwd"/"exe" in relpath (e.g. "[pid]/root") + * FIXME: currently only a stub because there is no shared multi-process file system in Graphene */ +static int get_ipc_genericlink_dentry(const char* relpath, struct shim_dentry** dent_ptr) { + int ret; + assert(dent_ptr); - int ret = parse_ipc_thread_name(name, &pid, &next, &next_len, &nextnext); + IDTYPE pid = 0; + char* rest = NULL; + ret = get_pid_from_relpath(relpath, &pid, &rest); if (ret < 0) return ret; + if (!rest) + return -ENOENT; + struct shim_dentry* dent = NULL; + enum pid_meta_code ipc_code; - void* ipc_data = NULL; - - if (!memcmp(next, "root", next_len)) { + if (strstartswith(rest, "root")) { ipc_code = PID_META_ROOT; - goto do_ipc; - } - - if (!memcmp(next, "cwd", next_len)) { + } else if (strstartswith(rest, "cwd")) { ipc_code = PID_META_CWD; - goto do_ipc; - } - - if (!memcmp(next, "exe", next_len)) { + } else if (strstartswith(rest, "exe")) { ipc_code = PID_META_EXEC; - goto do_ipc; + } else { + return -ENOENT; } - ret = -ENOENT; - goto out; -do_ipc: +#if 0 + /* this doesn't work because there is no shared multi-process file system in Graphene, + * thus path_lookupat() on a returned `ipc_data` path doesn't make much sense */ + void* ipc_data = NULL; ret = ipc_pid_getmeta_send(pid, ipc_code, &ipc_data); if (ret < 0) - goto out; + return ret; - if (link) - qstrsetstr(link, (char*)ipc_data, strlen((char*)ipc_data)); - - if (dentptr) { - /* XXX: Not sure how to handle this case yet */ - __abort(); - ret = path_lookupat(NULL, (char*)ipc_data, 0, &dent, NULL); - if (ret < 0) - goto out; - - get_dentry(dent); - *dentptr = dent; + ret = path_lookupat(NULL, (char*)ipc_data, 0, &dent, NULL); + if (ret < 0) { + free(ipc_data); + return ret; } -out: - if (dent) - put_dentry(dent); - return ret; + free(ipc_data); + get_dentry(dent); +#else + __UNUSED(pid); + __UNUSED(ipc_code); + __UNUSED(dent); + return -ENOENT; +#endif + + *dent_ptr = dent; + return 0; } -static int proc_ipc_thread_link_open(struct shim_handle* hdl, const char* name, int flags) { - struct shim_dentry* dent; +static int proc_ipc_thread_genericlink_open(struct shim_handle* hdl, const char* relpath, + int flags) { + int ret; - int ret = find_ipc_thread_link(name, NULL, &dent); + struct shim_dentry* dent = NULL; + ret = get_ipc_genericlink_dentry(relpath, &dent); if (ret < 0) return ret; if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->open) { - ret = -EACCES; - goto out; + put_dentry(dent); + return -EACCES; } ret = dent->fs->d_ops->open(hdl, dent, flags); -out: put_dentry(dent); return 0; } -static int proc_ipc_thread_link_mode(const char* name, mode_t* mode) { - struct shim_dentry* dent; +static int proc_ipc_thread_genericlink_mode(const char* relpath, mode_t* mode) { + int ret; - int ret = find_ipc_thread_link(name, NULL, &dent); + struct shim_dentry* dent = NULL; + ret = get_ipc_genericlink_dentry(relpath, &dent); if (ret < 0) return ret; if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->mode) { - ret = -EACCES; - goto out; + put_dentry(dent); + return -EACCES; } ret = dent->fs->d_ops->mode(dent, mode); -out: put_dentry(dent); return ret; } -static int proc_ipc_thread_link_stat(const char* name, struct stat* buf) { - struct shim_dentry* dent; +static int proc_ipc_thread_genericlink_stat(const char* relpath, struct stat* buf) { + int ret; - int ret = find_ipc_thread_link(name, NULL, &dent); + struct shim_dentry* dent = NULL; + ret = get_ipc_genericlink_dentry(relpath, &dent); if (ret < 0) return ret; if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->stat) { - ret = -EACCES; - goto out; + put_dentry(dent); + return -EACCES; } ret = dent->fs->d_ops->stat(dent, buf); -out: put_dentry(dent); return ret; } -static int proc_ipc_thread_link_follow_link(const char* name, struct shim_qstr* link) { - return find_ipc_thread_link(name, link, NULL); -} +static int proc_ipc_thread_genericlink_follow(const char* relpath, struct shim_qstr* link) { + int ret; -static const struct pseudo_fs_ops fs_ipc_thread_link = { - .open = &proc_ipc_thread_link_open, - .mode = &proc_ipc_thread_link_mode, - .stat = &proc_ipc_thread_link_stat, - .follow_link = &proc_ipc_thread_link_follow_link, -}; + struct shim_dentry* dent = NULL; + ret = get_ipc_genericlink_dentry(relpath, &dent); + if (ret < 0) + return ret; -static struct pid_status_cache { - uint32_t ref_count; - bool dirty; - size_t nstatus; - struct pid_status* status; -} * pid_status_cache; - -static struct shim_lock status_lock; - -static int proc_match_ipc_thread(const char* name) { - IDTYPE pid; - if (parse_ipc_thread_name(name, &pid, NULL, NULL, NULL) < 0) - return 0; - - if (!create_lock_runtime(&status_lock)) { + if (!dentry_get_path_into_qstr(dent, link)) { + put_dentry(dent); return -ENOMEM; } - lock(&status_lock); - if (pid_status_cache) - for (size_t i = 0; i < pid_status_cache->nstatus; i++) - if (pid_status_cache->status[i].pid == pid) { - unlock(&status_lock); - return 1; - } - - unlock(&status_lock); + put_dentry(dent); return 0; } -static int proc_ipc_thread_dir_mode(const char* name, mode_t* mode) { - const char* next; - size_t next_len; - IDTYPE pid; - int ret = parse_ipc_thread_name(name, &pid, &next, &next_len, NULL); +static const struct pseudo_fs_ops proc_ipc_thread_genericlink_fs_ops = { + .open = &proc_ipc_thread_genericlink_open, + .mode = &proc_ipc_thread_genericlink_mode, + .stat = &proc_ipc_thread_genericlink_stat, + .follow_link = &proc_ipc_thread_genericlink_follow, +}; + +/* return 0 if prefix of relpath (in format "[pid]") is a valid PID, or negative error code + * otherwise */ +static int proc_match_ipc_thread(const char* relpath) { + IDTYPE dummy_pid; + return get_pid_from_relpath(relpath, &dummy_pid, /*rest=*/NULL); +} + +/* return an array of dirents with all other-processes PIDs; also save it in g_pid_status_cache */ +static int proc_list_ipc_threads(const char* relpath, struct shim_dirent** buf, size_t size) { + __UNUSED(relpath); + int ret; + + if (!create_lock_runtime(&g_pid_status_lock)) + return -ENOMEM; + + /* free previously cached list of PIDs; we always query all PIDs anew in this function */ + lock(&g_pid_status_lock); + free(g_pid_status_cache.status); + g_pid_status_cache.status = NULL; + g_pid_status_cache.status_num = 0; + unlock(&g_pid_status_lock); + + struct pid_status* status = NULL; + ret = get_all_pid_status(&status); if (ret < 0) return ret; - if (!create_lock_runtime(&status_lock)) { - return -ENOMEM; - } - lock(&status_lock); + size_t status_num = ret; + size_t bytes = 0; + struct shim_dirent* dirent = *buf; - if (pid_status_cache) - for (size_t i = 0; i < pid_status_cache->nstatus; i++) - if (pid_status_cache->status[i].pid == pid) { - unlock(&status_lock); - *mode = PERM_r_x______; - return 0; - } - - unlock(&status_lock); - return -ENOENT; -} - -static int proc_ipc_thread_dir_stat(const char* name, struct stat* buf) { - const char* next; - size_t next_len; - IDTYPE pid; - int ret = parse_ipc_thread_name(name, &pid, &next, &next_len, NULL); - if (ret < 0) - return ret; - - if (!create_lock_runtime(&status_lock)) { - return -ENOMEM; - } - lock(&status_lock); - - if (pid_status_cache) - for (size_t i = 0; i < pid_status_cache->nstatus; i++) - if (pid_status_cache->status[i].pid == pid) { - memset(buf, 0, sizeof(struct stat)); - buf->st_dev = buf->st_ino = 1; - buf->st_mode = PERM_r_x______ | S_IFDIR; - buf->st_uid = 0; /* XXX */ - buf->st_gid = 0; /* XXX */ - buf->st_size = 4096; - unlock(&status_lock); - return 0; - } - - unlock(&status_lock); - return -ENOENT; -} - -static int proc_list_ipc_thread(const char* name, struct shim_dirent** buf, int len) { - // Only one valid name - __UNUSED(name); - struct pid_status_cache* status = NULL; - int ret = 0; - - if (!create_lock_runtime(&status_lock)) { - return -ENOMEM; - } - - lock(&status_lock); - if (pid_status_cache && !pid_status_cache->dirty) { - status = pid_status_cache; - status->ref_count++; - } - unlock(&status_lock); - - if (!status) { - status = malloc(sizeof(struct pid_status_cache)); - if (!status) - return -ENOMEM; - - ret = get_all_pid_status(&status->status); - if (ret < 0) { - free(status); - return ret; - } - - status->nstatus = ret; - status->ref_count = 1; - status->dirty = false; - - lock(&status_lock); - if (pid_status_cache) { - if (pid_status_cache->dirty) { - if (!pid_status_cache->ref_count) - free(pid_status_cache); - pid_status_cache = status; - } else { - if (status->nstatus) - free(status->status); - free(status); - status = pid_status_cache; - status->ref_count++; - } - } else { - pid_status_cache = status; - } - unlock(&status_lock); - } - - if (!status->nstatus) - goto success; - - struct shim_dirent* ptr = (*buf); - void* buf_end = (void*)ptr + len; - - for (size_t i = 0; i < status->nstatus; i++) { - if (status->status[i].pid != status->status[i].tgid) + for (size_t i = 0; i < status_num; i++) { + if (status[i].pid != status[i].tgid) continue; - IDTYPE pid = status->status[i].pid; - int p = pid, l = 0; - for (; p; p /= 10, l++) - ; + char pid_str[16]; + ssize_t pid_str_size = snprintf(pid_str, sizeof(pid_str), "%u", status[i].pid) + 1; - if ((void*)(ptr + 1) + l + 1 > buf_end) { - ret = -ENOMEM; - goto err; + size_t total_dirent_size = sizeof(struct shim_dirent) + pid_str_size; + bytes += total_dirent_size; + if (bytes > size) { + free(status); + return -ENOMEM; } - ptr->next = (void*)(ptr + 1) + l + 1; - ptr->ino = 1; - ptr->type = LINUX_DT_DIR; - ptr->name[l--] = 0; - for (p = pid; p; p /= 10) { - ptr->name[l--] = p % 10 + '0'; - } - - ptr = ptr->next; + dirent->next = (struct shim_dirent*)((char*)(dirent) + total_dirent_size); + dirent->ino = 1; + dirent->type = LINUX_DT_DIR; + memcpy(dirent->name, pid_str, pid_str_size); + dirent = dirent->next; } - *buf = ptr; -success: - lock(&status_lock); - status->dirty = true; - status->ref_count--; - if (!status->ref_count && status != pid_status_cache) - free(status); - unlock(&status_lock); + lock(&g_pid_status_lock); + g_pid_status_cache.status = status; + g_pid_status_cache.status_num = status_num; + unlock(&g_pid_status_lock); + + *buf = dirent; /* upon return, buf must point past all added entries */ return 0; -err: - lock(&status_lock); - status->ref_count--; - if (!status->ref_count && status != pid_status_cache) - free(status); - unlock(&status_lock); - return ret; } -const struct pseudo_name_ops nm_ipc_thread = { - .match_name = &proc_match_ipc_thread, - .list_name = &proc_list_ipc_thread, +const struct pseudo_name_ops proc_ipc_thread_name_ops = { + .match_path = &proc_match_ipc_thread, + .list_dirents = &proc_list_ipc_threads, }; -const struct pseudo_fs_ops fs_ipc_thread = { +static int proc_ipc_thread_dir_open(struct shim_handle* hdl, const char* relpath, int flags) { + __UNUSED(hdl); + int ret; + + IDTYPE pid = 0; + ret = get_pid_from_relpath(relpath, &pid, /*rest=*/NULL); + if (ret < 0) + return ret; + + if (flags & (O_WRONLY | O_RDWR)) + return -EISDIR; + + return 0; +} + +static int proc_ipc_thread_dir_mode(const char* relpath, mode_t* mode) { + int ret; + + IDTYPE pid = 0; + ret = get_pid_from_relpath(relpath, &pid, /*rest=*/NULL); + if (ret < 0) + return ret; + + *mode = PERM_r_x______; + return 0; +} + +static int proc_ipc_thread_dir_stat(const char* relpath, struct stat* buf) { + int ret; + + IDTYPE pid = 0; + ret = get_pid_from_relpath(relpath, &pid, /*rest=*/NULL); + if (ret < 0) + return ret; + + memset(buf, 0, sizeof(struct stat)); + buf->st_dev = 1; + buf->st_ino = 1; + buf->st_mode = PERM_r_x______ | S_IFDIR; + buf->st_uid = 0; + buf->st_gid = 0; + buf->st_size = 4096; + return 0; +} + +const struct pseudo_fs_ops proc_ipc_thread_fs_ops = { + .open = &proc_ipc_thread_dir_open, .mode = &proc_ipc_thread_dir_mode, .stat = &proc_ipc_thread_dir_stat, }; -const struct pseudo_dir dir_ipc_thread = { +const struct pseudo_dir proc_ipc_thread_dir = { .size = 3, .ent = { - {.name = "cwd", .fs_ops = &fs_ipc_thread_link, .type = LINUX_DT_LNK}, - {.name = "exe", .fs_ops = &fs_ipc_thread_link, .type = LINUX_DT_LNK}, - {.name = "root", .fs_ops = &fs_ipc_thread_link, .type = LINUX_DT_LNK}, + {.name = "cwd", .fs_ops = &proc_ipc_thread_genericlink_fs_ops, .type = LINUX_DT_LNK}, + {.name = "exe", .fs_ops = &proc_ipc_thread_genericlink_fs_ops, .type = LINUX_DT_LNK}, + {.name = "root", .fs_ops = &proc_ipc_thread_genericlink_fs_ops, .type = LINUX_DT_LNK}, } }; diff --git a/LibOS/shim/src/fs/proc/thread.c b/LibOS/shim/src/fs/proc/thread.c index 4351103f..63d7062a 100644 --- a/LibOS/shim/src/fs/proc/thread.c +++ b/LibOS/shim/src/fs/proc/thread.c @@ -1,8 +1,14 @@ -#include +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2014 Stony Brook University */ +/* Copyright (C) 2020 Intel Corporation */ + +/*! + * This file contains the implementation of `/proc/self` and `/proc/[tid]` sub-directories. + */ + #include #include #include -#include #include "pal.h" #include "pal_error.h" @@ -18,471 +24,411 @@ #include "stat.h" #include "shim_vma.h" -static int parse_thread_name(const char* name, IDTYPE* pidptr, const char** next, size_t* next_len, - const char** nextnext) { - const char* p = name; - IDTYPE pid = 0; +/* returns TID of the thread found in relpath and pointer to the rest of relpath string + * (e.g. "42/cwd" returns 42 in `*tid_ptr` and pointer to "cwd" in `rest`, + * "self" returns current-thread's TID in `*tid_ptr` and NULL in `rest`) */ +static int get_tid_from_relpath(const char* relpath, IDTYPE* tid_ptr, char** rest) { + if (*relpath == '\0' || *relpath == '/') + return -ENOENT; - if (*p == '/') - p++; + char* tid_end = NULL; + IDTYPE tid = 0; - if (strstartswith(p, "self")) { - p += static_strlen("self"); - if (*p && *p != '/') - return -ENOENT; - pid = get_cur_tid(); + if (strstartswith(relpath, "self")) { + tid_end = (char*)relpath + 4; + tid = get_cur_tid(); } else { - for (; *p && *p != '/'; p++) { - if (*p < '0' || *p > '9') - return -ENOENT; - - pid = pid * 10 + *p - '0'; - } + tid = (IDTYPE)strtol(relpath, &tid_end, /*base=*/10); } - if (next) { - if (*(p++) == '/' && *p) { - *next = p; + if (!tid_end || (*tid_end != '\0' && *tid_end != '/')) + return -ENOENT; - if (next_len || nextnext) - for (; *p && *p != '/'; p++) - ; + struct shim_thread* thread = lookup_thread(tid); + if (!thread) + return -ENOENT; + put_thread(thread); - if (next_len) - *next_len = p - *next; - - if (nextnext) - *nextnext = (*(p++) == '/' && *p) ? p : NULL; - } else { - *next = NULL; - } - } - - if (pidptr) - *pidptr = pid; + *tid_ptr = tid; + if (rest) + *rest = *tid_end == '\0' ? NULL : tid_end + 1; return 0; } -static int find_thread_link(const char* name, struct shim_qstr* link, - struct shim_dentry** dentptr) { - const char* next; - const char* nextnext; - size_t next_len; - IDTYPE pid; - int ret = parse_thread_name(name, &pid, &next, &next_len, &nextnext); +/* returns handle corresponding to TID and FD in relpath (in format "[tid]/fd/[fd]"); handle's + * refcount is incremented on success; `hdl_ptr` may be NULL if caller only wants to know whether + * the TID + FD pair in relpath actually exists */ +static int get_fd_handle_from_relpath(const char* relpath, struct shim_handle** hdl_ptr) { + IDTYPE tid = 0; + char* rest = NULL; + int ret = get_tid_from_relpath(relpath, &tid, &rest); if (ret < 0) return ret; - struct shim_thread* thread = lookup_thread(pid); - struct shim_dentry* dent = NULL; - - if (!thread) + if (!rest || !strstartswith(rest, "fd/")) return -ENOENT; - /* We just checked that a thread with this id exists and we do not need this handle anymore. */ - put_thread(thread); + + rest += strlen("fd/"); + if (*rest == '\0') + return -ENOENT; + + char* fd_end = NULL; + FDTYPE fd = (FDTYPE)strtol(rest, &fd_end, /*base=*/10); + + if (!fd_end || (*fd_end != '\0' && *fd_end != '/')) + return -ENOENT; + + struct shim_handle_map* handle_map = get_thread_handle_map(NULL); + if (!handle_map) + return -ENOENT; + + lock(&handle_map->lock); + + if (fd >= handle_map->fd_top || !handle_map->map[fd] || !handle_map->map[fd]->handle) { + unlock(&handle_map->lock); + return -ENOENT; + } + + if (hdl_ptr) { + *hdl_ptr = handle_map->map[fd]->handle; + get_handle(*hdl_ptr); + } + + unlock(&handle_map->lock); + return 0; +} + +/* returns dentry corresponding to TID and FD in relpath (in format "[tid]/fd/[fd]"); dentry's + * refcount is incremented on success */ +static int get_fd_dent_from_relpath(const char* relpath, struct shim_dentry** dent_ptr) { + int ret; + assert(dent_ptr); + + struct shim_handle* handle = NULL; + ret = get_fd_handle_from_relpath(relpath, &handle); + if (ret < 0) + return ret; + + struct shim_dentry* dent = NULL; + lock(&handle->lock); + dent = handle->dentry; + if (!dent) { + unlock(&handle->lock); + put_handle(handle); + return -ENOENT; + } + get_dentry(dent); + unlock(&handle->lock); + + *dent_ptr = dent; + put_handle(handle); + return 0; +} + +/* returns qstr link corresponding to TID and FD in relpath (in format "[tid]/fd/[fd]") */ +static int get_fd_link_from_relpath(const char* relpath, struct shim_qstr* link) { + int ret; + assert(link); + + struct shim_dentry* dent = NULL; + ret = get_fd_dent_from_relpath(relpath, &dent); + if (ret < 0) + return ret; + + if (!dentry_get_path_into_qstr(dent, link)) { + put_dentry(dent); + return -ENOMEM; + } + + put_dentry(dent); + return 0; +} + +/* returns dentry corresponding to TID's "root"/"cwd"/"exe" in relpath (e.g. "[tid]/root"); + * dentry's refcount is incremented on success */ +static int get_genericlink_dentry(const char* relpath, struct shim_dentry** dent_ptr) { + int ret; + assert(dent_ptr); + + IDTYPE tid = 0; + char* rest = NULL; + ret = get_tid_from_relpath(relpath, &tid, &rest); + if (ret < 0) + return ret; + + if (!rest) + return -ENOENT; + + struct shim_dentry* dent = NULL; lock(&g_process.fs_lock); - if (next_len == static_strlen("root") && !memcmp(next, "root", next_len)) { + if (strstartswith(rest, "root")) { dent = g_process.root; - get_dentry(dent); - } - - if (next_len == static_strlen("cwd") && !memcmp(next, "cwd", next_len)) { + } else if (strstartswith(rest, "cwd")) { dent = g_process.cwd; - get_dentry(dent); + } else if (strstartswith(rest, "exe")) { + dent = g_process.exec->dentry; } - if (next_len == static_strlen("exe") && !memcmp(next, "exe", next_len)) { - struct shim_handle* exec = g_process.exec; - if (!exec->dentry) { - unlock(&g_process.fs_lock); - ret = -EINVAL; - goto out; - } - dent = exec->dentry; - get_dentry(dent); - } - - unlock(&g_process.fs_lock); - - if (nextnext) { - struct shim_dentry* next_dent = NULL; - - ret = path_lookupat(dent, nextnext, 0, &next_dent, dent->fs); - if (ret < 0) - goto out; - - put_dentry(dent); - dent = next_dent; - } - - if (link && !dentry_get_path_into_qstr(dent, link)) { - ret = -ENOMEM; - goto out; - } - - if (dentptr) { - get_dentry(dent); - *dentptr = dent; - } - - ret = 0; -out: - if (dent) - put_dentry(dent); - return ret; -} - -static int proc_thread_link_open(struct shim_handle* hdl, const char* name, int flags) { - struct shim_dentry* dent; - - int ret = find_thread_link(name, NULL, &dent); - if (ret < 0) - return ret; - - if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->open) { - ret = -EACCES; - goto out; - } - - ret = dent->fs->d_ops->open(hdl, dent, flags); -out: - put_dentry(dent); - return 0; -} - -static int proc_thread_link_mode(const char* name, mode_t* mode) { - struct shim_dentry* dent; - - int ret = find_thread_link(name, NULL, &dent); - if (ret < 0) - return ret; - - if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->mode) { - ret = -EACCES; - goto out; - } - - ret = dent->fs->d_ops->mode(dent, mode); -out: - put_dentry(dent); - return ret; -} - -static int proc_thread_link_stat(const char* name, struct stat* buf) { - struct shim_dentry* dent; - - int ret = find_thread_link(name, NULL, &dent); - if (ret < 0) - return ret; - - if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->stat) { - ret = -EACCES; - goto out; - } - - ret = dent->fs->d_ops->stat(dent, buf); -out: - put_dentry(dent); - return ret; -} - -static int proc_thread_link_follow_link(const char* name, struct shim_qstr* link) { - return find_thread_link(name, link, NULL); -} - -static const struct pseudo_fs_ops fs_thread_link = { - .open = &proc_thread_link_open, - .mode = &proc_thread_link_mode, - .stat = &proc_thread_link_stat, - .follow_link = &proc_thread_link_follow_link, -}; - -/* If *phdl is returned on success, the ref count is incremented */ -static int parse_thread_fd(const char* name, const char** rest, struct shim_handle** phdl) { - const char* next; - const char* nextnext; - size_t next_len; - IDTYPE pid; - int ret = parse_thread_name(name, &pid, &next, &next_len, &nextnext); - if (ret < 0) - return ret; - - if (!next || !nextnext || memcmp(next, "fd", next_len)) - return -EINVAL; - - const char* p = nextnext; - FDTYPE fd = 0; - - for (; *p && *p != '/'; p++) { - if (*p < '0' || *p > '9') - return -ENOENT; - fd = fd * 10 + *p - '0'; - if ((uint64_t)fd >= get_rlimit_cur(RLIMIT_NOFILE)) - return -ENOENT; - } - - struct shim_thread* thread = lookup_thread(pid); - - if (!thread) - return -ENOENT; - - struct shim_handle_map* handle_map = get_thread_handle_map(thread); - - lock(&handle_map->lock); - - if (fd >= handle_map->fd_top || handle_map->map[fd] == NULL || - handle_map->map[fd]->handle == NULL) { - ret = -ENOENT; - goto out; - } - - if (phdl) { - *phdl = handle_map->map[fd]->handle; - get_handle(*phdl); - } - - if (rest) - *rest = *p ? p + 1 : NULL; - - ret = 0; - -out: - unlock(&handle_map->lock); - put_thread(thread); - return ret; -} - -static int proc_match_thread_each_fd(const char* name) { - return parse_thread_fd(name, NULL, NULL) == 0 ? 1 : 0; -} - -static int proc_list_thread_each_fd(const char* name, struct shim_dirent** buf, int count) { - const char* next; - size_t next_len; - IDTYPE pid; - int ret = parse_thread_name(name, &pid, &next, &next_len, NULL); - if (ret < 0) - return ret; - - if (!next || memcmp(next, "fd", next_len)) - return -EINVAL; - - struct shim_thread* thread = lookup_thread(pid); - if (!thread) - return -ENOENT; - - struct shim_handle_map* handle_map = get_thread_handle_map(thread); - int err = 0, bytes = 0; - struct shim_dirent* dirent = *buf; - struct shim_dirent** last = NULL; - - lock(&handle_map->lock); - - for (int i = 0; i < handle_map->fd_size; i++) - if (handle_map->map[i] && handle_map->map[i]->handle) { - int d = i, l = 0; - for (; d; d /= 10, l++) - ; - l = l ?: 1; - - bytes += sizeof(struct shim_dirent) + l + 1; - if (bytes > count) { - err = -ENOMEM; - break; - } - - dirent->next = (void*)(dirent + 1) + l + 1; - dirent->ino = 1; - dirent->type = LINUX_DT_LNK; - dirent->name[0] = '0'; - dirent->name[l--] = 0; - for (d = i; d; d /= 10) { - dirent->name[l--] = '0' + d % 10; - } - last = &dirent->next; - dirent = dirent->next; - } - - unlock(&handle_map->lock); - put_thread(thread); - - if (last) - *last = NULL; - - *buf = dirent; - return err; -} - -static const struct pseudo_name_ops nm_thread_each_fd = { - .match_name = &proc_match_thread_each_fd, - .list_name = &proc_list_thread_each_fd, -}; - -static int find_thread_each_fd(const char* name, struct shim_qstr* link, - struct shim_dentry** dentptr) { - const char* rest; - struct shim_handle* handle; - struct shim_dentry* dent = NULL; - int ret; - - if ((ret = parse_thread_fd(name, &rest, &handle)) < 0) - return ret; - - lock(&handle->lock); - - if (handle->dentry) { - dent = handle->dentry; - get_dentry(dent); - } - - unlock(&handle->lock); - if (!dent) { - ret = -ENOENT; - goto out; + unlock(&g_process.fs_lock); + return -ENOENT; } - if (rest) { - struct shim_dentry* next_dent = NULL; + get_dentry(dent); + unlock(&g_process.fs_lock); - ret = path_lookupat(dent, rest, 0, &next_dent, dent->fs); - if (ret < 0) - goto out; - - put_dentry(dent); - dent = next_dent; - } - - if (link && !dentry_get_path_into_qstr(dent, link)) { - ret = -ENOMEM; - goto out; - } - - if (dentptr) { - get_dentry(dent); - *dentptr = dent; - } - -out: - if (dent) - put_dentry(dent); - - put_handle(handle); - return ret; + *dent_ptr = dent; + return 0; } -static int proc_thread_each_fd_open(struct shim_handle* hdl, const char* name, int flags) { - struct shim_dentry* dent; +static int proc_thread_genericlink_open(struct shim_handle* hdl, const char* relpath, int flags) { + int ret; - int ret = find_thread_each_fd(name, NULL, &dent); + struct shim_dentry* dent = NULL; + ret = get_genericlink_dentry(relpath, &dent); if (ret < 0) return ret; if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->open) { - ret = -EACCES; - goto out; + put_dentry(dent); + return -EACCES; } ret = dent->fs->d_ops->open(hdl, dent, flags); -out: put_dentry(dent); return 0; } -static int proc_thread_each_fd_mode(const char* name, mode_t* mode) { - struct shim_dentry* dent; +static int proc_thread_genericlink_mode(const char* relpath, mode_t* mode) { + int ret; - int ret = find_thread_each_fd(name, NULL, &dent); + struct shim_dentry* dent = NULL; + ret = get_genericlink_dentry(relpath, &dent); if (ret < 0) return ret; if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->mode) { - ret = -EACCES; - goto out; + put_dentry(dent); + return -EACCES; } ret = dent->fs->d_ops->mode(dent, mode); -out: put_dentry(dent); - return 0; + return ret; } -static int proc_thread_each_fd_stat(const char* name, struct stat* buf) { - struct shim_dentry* dent; +static int proc_thread_genericlink_stat(const char* relpath, struct stat* buf) { + int ret; - int ret = find_thread_each_fd(name, NULL, &dent); + struct shim_dentry* dent = NULL; + ret = get_genericlink_dentry(relpath, &dent); if (ret < 0) return ret; if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->stat) { - ret = -EACCES; - goto out; + put_dentry(dent); + return -EACCES; } ret = dent->fs->d_ops->stat(dent, buf); -out: + put_dentry(dent); + return ret; +} + +static int proc_thread_genericlink_follow(const char* relpath, struct shim_qstr* link) { + int ret; + + struct shim_dentry* dent = NULL; + ret = get_genericlink_dentry(relpath, &dent); + if (ret < 0) + return ret; + + if (!dentry_get_path_into_qstr(dent, link)) { + put_dentry(dent); + return -ENOMEM; + } + put_dentry(dent); return 0; } -static int proc_thread_each_fd_follow_link(const char* name, struct shim_qstr* link) { - return find_thread_each_fd(name, link, NULL); +static const struct pseudo_fs_ops proc_thread_genericlink_fs_ops = { + .open = &proc_thread_genericlink_open, + .mode = &proc_thread_genericlink_mode, + .stat = &proc_thread_genericlink_stat, + .follow_link = &proc_thread_genericlink_follow, +}; + +/* return 0 if prefix of relpath (in format "[tid]/fd/[fd]") is a valid TID + FD combination, + * negative error code otherwise */ +static int proc_match_thread_fd(const char* relpath) { + return get_fd_handle_from_relpath(relpath, /*hdl_ptr=*/NULL); } -static const struct pseudo_fs_ops fs_thread_each_fd = { - .open = &proc_thread_each_fd_open, - .mode = &proc_thread_each_fd_mode, - .stat = &proc_thread_each_fd_stat, - .follow_link = &proc_thread_each_fd_follow_link, +/* return an array of dirents for the given relpath (in format "[tid]/fd/[fd]"), or negative error + * code otherwise */ +static int proc_list_thread_fds(const char* relpath, struct shim_dirent** buf, size_t size) { + int ret; + + IDTYPE tid = 0; + char* rest = NULL; + ret = get_tid_from_relpath(relpath, &tid, &rest); + if (ret < 0) + return ret; + + if (!rest || !strstartswith(rest, "fd")) + return -ENOENT; + + rest += strlen("fd"); + if (*rest != '\0' && *rest != '/') + return -ENOENT; + + /* all threads share the same handles, so ignore TID and use current thread's handle map */ + struct shim_handle_map* handle_map = get_thread_handle_map(NULL); + if (!handle_map) + return -ENOENT; + + size_t bytes = 0; + struct shim_dirent* dirent = *buf; + + lock(&handle_map->lock); + + for (size_t i = 0; i < handle_map->fd_size; i++) { + if (!handle_map->map[i] || !handle_map->map[i]->handle) + continue; + + char fd_str[16]; + ssize_t fd_str_size = snprintf(fd_str, sizeof(fd_str), "%lu", i) + 1; + + size_t total_dirent_size = sizeof(struct shim_dirent) + fd_str_size; + bytes += total_dirent_size; + if (bytes > size) { + ret = -ENOMEM; + goto out; + } + + dirent->next = (struct shim_dirent*)((char*)(dirent) + total_dirent_size); + dirent->ino = 1; + dirent->type = LINUX_DT_LNK; + memcpy(dirent->name, fd_str, fd_str_size); + dirent = dirent->next; + } + + *buf = dirent; /* upon return, buf must point past all added entries */ + ret = 0; +out: + unlock(&handle_map->lock); + return ret; +} + +static const struct pseudo_name_ops proc_thread_fds_fd_name_ops = { + .match_path = &proc_match_thread_fd, + .list_dirents = &proc_list_thread_fds, }; -static const struct pseudo_dir dir_fd = { +static int proc_thread_fds_fd_open(struct shim_handle* hdl, const char* relpath, int flags) { + int ret; + + struct shim_dentry* dent = NULL; + ret = get_fd_dent_from_relpath(relpath, &dent); + if (ret < 0) + return ret; + + if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->open) { + put_dentry(dent); + return -EACCES; + } + + ret = dent->fs->d_ops->open(hdl, dent, flags); + put_dentry(dent); + return ret; +} + +static int proc_thread_fds_fd_mode(const char* relpath, mode_t* mode) { + int ret; + + struct shim_dentry* dent = NULL; + ret = get_fd_dent_from_relpath(relpath, &dent); + if (ret < 0) + return ret; + + if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->mode) { + put_dentry(dent); + return -EACCES; + } + + ret = dent->fs->d_ops->mode(dent, mode); + put_dentry(dent); + return ret; +} + +static int proc_thread_fds_fd_stat(const char* relpath, struct stat* buf) { + int ret; + + struct shim_dentry* dent = NULL; + ret = get_fd_dent_from_relpath(relpath, &dent); + if (ret < 0) + return ret; + + if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->stat) { + put_dentry(dent); + return -EACCES; + } + + ret = dent->fs->d_ops->stat(dent, buf); + put_dentry(dent); + return ret; +} + +static int proc_thread_fds_fd_follow(const char* relpath, struct shim_qstr* link) { + return get_fd_link_from_relpath(relpath, link); +} + +/* operations on files with paths of format "/proc/[tid]/fd/[fd]" */ +static const struct pseudo_fs_ops proc_thread_fds_fd_fs_ops = { + .open = &proc_thread_fds_fd_open, + .mode = &proc_thread_fds_fd_mode, + .stat = &proc_thread_fds_fd_stat, + .follow_link = &proc_thread_fds_fd_follow, +}; + +/* sub-directory of format "/proc/[tid]/fd/", contains opened FDs */ +static const struct pseudo_dir proc_thread_fds_dir = { .size = 1, - .ent = - { - { - .name_ops = &nm_thread_each_fd, - .fs_ops = &fs_thread_each_fd, - .type = LINUX_DT_LNK, - }, - }, + .ent = { { .name_ops = &proc_thread_fds_fd_name_ops, + .fs_ops = &proc_thread_fds_fd_fs_ops, + .type = LINUX_DT_LNK } } }; -static int proc_thread_maps_open(struct shim_handle* hdl, const char* name, int flags) { +static int proc_thread_maps_open(struct shim_handle* hdl, const char* relpath, int flags) { + int ret; + + IDTYPE tid = 0; + ret = get_tid_from_relpath(relpath, &tid, /*rest=*/NULL); + if (ret < 0) + return ret; + if (flags & (O_WRONLY | O_RDWR)) return -EACCES; - const char* next; - size_t next_len; - IDTYPE pid; char* buffer = NULL; - int ret = parse_thread_name(name, &pid, &next, &next_len, NULL); - if (ret < 0) - return ret; - - struct shim_thread* thread = lookup_thread(pid); - - if (!thread) - return -ENOENT; - - size_t count; + size_t count = 0; struct shim_vma_info* vmas = NULL; ret = dump_all_vmas(&vmas, &count, /*include_unmapped=*/false); if (ret < 0) { goto err; } -#define DEFAULT_VMA_BUFFER_SIZE 256 - - size_t buffer_size = DEFAULT_VMA_BUFFER_SIZE, offset = 0; + size_t buffer_size = 1024; /* initial size of the VMA-info buffer, expanded if needed */ buffer = malloc(buffer_size); if (!buffer) { ret = -ENOMEM; goto err; } + size_t offset = 0; for (struct shim_vma_info* vma = vmas; vma < vmas + count; vma++) { size_t old_offset = offset; uintptr_t start = (uintptr_t)vma->addr; @@ -561,55 +507,66 @@ err: if (vmas) { free_vma_info_array(vmas, count); } - put_thread(thread); return ret; } -static int proc_thread_maps_mode(const char* name, mode_t* mode) { - // Only used by one file - __UNUSED(name); +static int proc_thread_maps_mode(const char* relpath, mode_t* mode) { + int ret; + + IDTYPE tid = 0; + ret = get_tid_from_relpath(relpath, &tid, /*rest=*/NULL); + if (ret < 0) + return ret; + *mode = PERM_r________; return 0; } -static int proc_thread_maps_stat(const char* name, struct stat* buf) { - // Only used by one file - __UNUSED(name); - memset(buf, 0, sizeof(struct stat)); +static int proc_thread_maps_stat(const char* relpath, struct stat* buf) { + int ret; - buf->st_dev = buf->st_ino = 1; - buf->st_mode = PERM_r________ | S_IFREG; - buf->st_uid = 0; - buf->st_gid = 0; - buf->st_size = 0; + IDTYPE tid = 0; + ret = get_tid_from_relpath(relpath, &tid, /*rest=*/NULL); + if (ret < 0) + return ret; + memset(buf, 0, sizeof(*buf)); + buf->st_dev = 1; + buf->st_ino = 1; + buf->st_mode = PERM_r________ | S_IFREG; + buf->st_uid = 0; + buf->st_gid = 0; + buf->st_size = 0; return 0; } -static const struct pseudo_fs_ops fs_thread_maps = { +/* operations on file "/proc/[tid]/maps" */ +static const struct pseudo_fs_ops proc_thread_maps_fs_ops = { .open = &proc_thread_maps_open, .mode = &proc_thread_maps_mode, .stat = &proc_thread_maps_stat, }; -static int proc_thread_dir_open(struct shim_handle* hdl, const char* name, int flags) { +static int proc_thread_dir_open(struct shim_handle* hdl, const char* relpath, int flags) { __UNUSED(hdl); - __UNUSED(name); + int ret; + + IDTYPE tid = 0; + ret = get_tid_from_relpath(relpath, &tid, /*rest=*/NULL); + if (ret < 0) + return ret; if (flags & (O_WRONLY | O_RDWR)) return -EISDIR; - // Don't really need to do any work here, but keeping as a placeholder, - // just in case. - return 0; } -static int proc_thread_dir_mode(const char* name, mode_t* mode) { - const char* next; - size_t next_len; - IDTYPE pid; - int ret = parse_thread_name(name, &pid, &next, &next_len, NULL); +static int proc_thread_dir_mode(const char* relpath, mode_t* mode) { + int ret; + + IDTYPE tid = 0; + ret = get_tid_from_relpath(relpath, &tid, /*rest=*/NULL); if (ret < 0) return ret; @@ -617,112 +574,98 @@ static int proc_thread_dir_mode(const char* name, mode_t* mode) { return 0; } -static int proc_thread_dir_stat(const char* name, struct stat* buf) { - const char* next; - size_t next_len; - IDTYPE pid; - int ret = parse_thread_name(name, &pid, &next, &next_len, NULL); +static int proc_thread_dir_stat(const char* relpath, struct stat* buf) { + int ret; + + IDTYPE tid = 0; + ret = get_tid_from_relpath(relpath, &tid, /*rest=*/NULL); if (ret < 0) return ret; - struct shim_thread* thread = lookup_thread(pid); - - if (!thread) - return -ENOENT; - memset(buf, 0, sizeof(struct stat)); - buf->st_dev = buf->st_ino = 1; - buf->st_mode = PERM_r_x______ | S_IFDIR; - lock(&thread->lock); - buf->st_uid = thread->uid; - buf->st_gid = thread->gid; - unlock(&thread->lock); + buf->st_dev = 1; + buf->st_ino = 1; + buf->st_mode = PERM_r_x______ | S_IFDIR; + buf->st_uid = 0; + buf->st_gid = 0; buf->st_size = 4096; - - put_thread(thread); return 0; } -static const struct pseudo_fs_ops fs_thread_fd = { - .mode = &proc_thread_dir_mode, - .stat = &proc_thread_dir_stat, -}; - -static int proc_match_thread(const char* name) { - IDTYPE pid; - if (parse_thread_name(name, &pid, NULL, NULL, NULL) < 0) - return 0; - - struct shim_thread* thread = lookup_thread(pid); - - if (thread) { - put_thread(thread); - return 1; - } - - return 0; -} - -struct walk_thread_arg { - struct shim_dirent *buf, *buf_end; -}; - -static int walk_cb(struct shim_thread* thread, void* arg) { - struct walk_thread_arg* args = (struct walk_thread_arg*)arg; - IDTYPE pid = thread->tid; - int p = pid, l = 0; - for (; p; p /= 10, l++) - ; - - if ((void*)(args->buf + 1) + l + 1 > (void*)args->buf_end) - return -ENOMEM; - - struct shim_dirent* buf = args->buf; - - buf->next = (void*)(buf + 1) + l + 1; - buf->ino = 1; - buf->type = LINUX_DT_DIR; - buf->name[l--] = 0; - for (p = pid; p; p /= 10) { - buf->name[l--] = p % 10 + '0'; - } - - args->buf = buf->next; - return 1; -} - -static int proc_list_thread(const char* name, struct shim_dirent** buf, int len) { - __UNUSED(name); // We know this is for "/proc/self" - struct walk_thread_arg args = { - .buf = *buf, - .buf_end = (void*)*buf + len, - }; - - int ret = walk_thread_list(&walk_cb, &args, /*one_shot=*/false); - if (ret < 0) - return ret; - - *buf = args.buf; - return 0; -} - -const struct pseudo_name_ops nm_thread = { - .match_name = &proc_match_thread, - .list_name = &proc_list_thread, -}; - -const struct pseudo_fs_ops fs_thread = { +static const struct pseudo_fs_ops proc_thread_fds_fs_ops = { .open = &proc_thread_dir_open, .mode = &proc_thread_dir_mode, .stat = &proc_thread_dir_stat, }; -const struct pseudo_dir dir_thread = { +/* return 0 if prefix of relpath (in format "[tid]") is a valid TID, or negative error code + * otherwise */ +static int proc_match_thread(const char* relpath) { + IDTYPE dummy_tid; + return get_tid_from_relpath(relpath, &dummy_tid, /*rest=*/NULL); +} + +struct walk_thread_arg { + char* buf; + char* buf_end; +}; + +static int walk_thread_list_cb(struct shim_thread* thread, void* arg) { + struct walk_thread_arg* args = (struct walk_thread_arg*)arg; + + char tid_str[32]; + ssize_t tid_str_size = snprintf(tid_str, sizeof(tid_str), "%u", thread->tid) + 1; + + size_t total_dirent_size = sizeof(struct shim_dirent) + tid_str_size; + if (args->buf + total_dirent_size > args->buf_end) + return -ENOMEM; + + struct shim_dirent* dirent = (struct shim_dirent*)args->buf; + + dirent->next = (struct shim_dirent*)(args->buf + total_dirent_size); + dirent->ino = 1; + dirent->type = LINUX_DT_DIR; + memcpy(dirent->name, tid_str, tid_str_size); + + args->buf = (char*)dirent->next; + return 1; +} + +/* return an array of dirents with all process-local TIDs */ +static int proc_list_threads(const char* relpath, struct shim_dirent** buf, size_t size) { + __UNUSED(relpath); + int ret; + + struct walk_thread_arg args = { + .buf = (char*)*buf, + .buf_end = (char*)*buf + size, + }; + + ret = walk_thread_list(&walk_thread_list_cb, &args, /*one_shot=*/false); + if (ret < 0) + return ret; + + *buf = (struct shim_dirent*)args.buf; /* upon return, buf must point past all added entries */ + return 0; +} + +const struct pseudo_name_ops proc_thread_name_ops = { + .match_path = &proc_match_thread, + .list_dirents = &proc_list_threads, +}; + +const struct pseudo_fs_ops proc_thread_fs_ops = { + .open = &proc_thread_dir_open, + .mode = &proc_thread_dir_mode, + .stat = &proc_thread_dir_stat, +}; + +const struct pseudo_dir proc_thread_dir = { .size = 5, .ent = { - {.name = "cwd", .fs_ops = &fs_thread_link, .type = LINUX_DT_LNK}, - {.name = "exe", .fs_ops = &fs_thread_link, .type = LINUX_DT_LNK}, - {.name = "root", .fs_ops = &fs_thread_link, .type = LINUX_DT_LNK}, - {.name = "fd", .fs_ops = &fs_thread_fd, .dir = &dir_fd}, - {.name = "maps", .fs_ops = &fs_thread_maps, .type = LINUX_DT_REG}, + {.name = "cwd", .fs_ops = &proc_thread_genericlink_fs_ops, .type = LINUX_DT_LNK}, + {.name = "exe", .fs_ops = &proc_thread_genericlink_fs_ops, .type = LINUX_DT_LNK}, + {.name = "root", .fs_ops = &proc_thread_genericlink_fs_ops, .type = LINUX_DT_LNK}, + {.name = "fd", .fs_ops = &proc_thread_fds_fs_ops, .dir = &proc_thread_fds_dir}, + {.name = "maps", .fs_ops = &proc_thread_maps_fs_ops, .type = LINUX_DT_REG}, }}; diff --git a/LibOS/shim/src/fs/shim_fs_pseudo.c b/LibOS/shim/src/fs/shim_fs_pseudo.c index 7ddba4e3..e71b6e4d 100644 --- a/LibOS/shim/src/fs/shim_fs_pseudo.c +++ b/LibOS/shim/src/fs/shim_fs_pseudo.c @@ -53,10 +53,16 @@ static int pseudo_findent(const char* path, const struct pseudo_ent* root_ent, break; } - if (ent->name_ops && ent->name_ops->match_name && ent->name_ops->match_name(token)) { - /* directory entry has a calculated at runtime name (via match_name) that matches - * current token: found ent */ - break; + if (ent->name_ops && ent->name_ops->match_path) { + int ret = ent->name_ops->match_path(path); + if (ret == 0) { + /* directory entry has a calculated at runtime name (via match_path) that + * matches current path prefix (not just token!): found ent */ + break; + } else if (ret != -ENOENT) { + /* actual failure in match_path() */ + return ret; + } } } @@ -104,10 +110,10 @@ static int populate_dirent(const char* path, const struct pseudo_dir* dir, struc dirent_in_buf->type = ent->dir ? LINUX_DT_DIR : ent->type; dirent_in_buf = dirent_in_buf->next; - } else if (ent->name_ops && ent->name_ops->list_name) { - /* directory entry has a list of entries calculated at runtime (via list_name) */ + } else if (ent->name_ops && ent->name_ops->list_dirents) { + /* directory entry has a list of entries calculated at runtime (via list_dirents) */ struct shim_dirent* old_dirent_in_buf = dirent_in_buf; - int ret = ent->name_ops->list_name(path, &dirent_in_buf, buf_size - total_size); + int ret = ent->name_ops->list_dirents(path, &dirent_in_buf, buf_size - total_size); if (ret < 0) return ret; diff --git a/LibOS/shim/test/regression/proc_common.c b/LibOS/shim/test/regression/proc_common.c index 09771b47..5cc2c36a 100644 --- a/LibOS/shim/test/regression/proc_common.c +++ b/LibOS/shim/test/regression/proc_common.c @@ -9,6 +9,9 @@ #include #include +#define MSG "Hello from /proc/1/fd/1\n" +#define MSG2 "Hello from /dev/stdout\n" + static void* fn(void* arg) { /* not to consume CPU, each thread simply sleeps */ sleep(10000); @@ -89,6 +92,72 @@ int main(int argc, char** argv) { return 1; } + printf("===== Contents of /proc/1/fd\n"); + dir = opendir("/proc/1/fd"); + if (!dir) { + perror("opendir /proc/1/fd"); + return 1; + } + + errno = 0; + while ((dirent = readdir(dir))) { + printf("/proc/1/fd/%s\n", dirent->d_name); + } + if (errno) { + perror("readdir /proc/1/fd"); + return 1; + } + + ret = closedir(dir); + if (ret < 0) { + perror("closedir /proc/1/fd"); + return 1; + } + + printf("===== Writing to /proc/1/fd/1 (stdout)\n"); + f = fopen("/proc/1/fd/1", "w"); + if (!f) { + perror("fopen /proc/1/fd/1"); + return 1; + } + + ret = fwrite(MSG, sizeof(MSG), 1, f); + if (ferror(f)) { + perror("fwrite /proc/1/fd/1"); + return 1; + } + + /* above fwrite will print "Hello ..." to stdout *without* bufferization */ + memset(buf, 0, sizeof(buf)); + + ret = fclose(f); + if (ret) { + perror("fclose /proc/1/fd/1"); + return 1; + } + + printf("===== Writing to /dev/stdout (stdout)\n"); + f = fopen("/dev/stdout", "w"); + if (!f) { + perror("fopen /dev/stdout"); + return 1; + } + + ret = fwrite(MSG2, sizeof(MSG2), 1, f); + if (ferror(f)) { + perror("fwrite /dev/stdout"); + return 1; + } + + /* above fwrite will print "Hello ..." to stdout *without* bufferization */ + memset(buf, 0, sizeof(buf)); + + ret = fclose(f); + if (ret) { + perror("fclose /dev/stdout"); + return 1; + } + printf("===== Contents of /proc/self\n"); dir = opendir("/proc/self"); if (!dir) { diff --git a/LibOS/shim/test/regression/test_libos.py b/LibOS/shim/test/regression/test_libos.py index be543686..d1a82512 100644 --- a/LibOS/shim/test/regression/test_libos.py +++ b/LibOS/shim/test/regression/test_libos.py @@ -549,6 +549,10 @@ class TC_40_FileSystem(RegressionTestCase): self.assertIn('/proc/1/root', stdout) self.assertIn('/proc/1/fd', stdout) self.assertIn('/proc/1/maps', stdout) + self.assertIn('/proc/1/fd/0', stdout) + self.assertIn('/proc/1/fd/1', stdout) + self.assertIn('/proc/1/fd/2', stdout) + self.assertIn('Hello from /proc/1/fd/1', stdout) self.assertIn('/proc/self/..', stdout) self.assertIn('/proc/self/cwd', stdout) self.assertIn('/proc/self/exe', stdout)