diff --git a/LibOS/shim/include/shim_fs.h b/LibOS/shim/include/shim_fs.h index 9af04f29..cef076a0 100644 --- a/LibOS/shim/include/shim_fs.h +++ b/LibOS/shim/include/shim_fs.h @@ -525,6 +525,7 @@ extern struct shim_d_ops str_d_ops; extern struct shim_mount chroot_builtin_fs; extern struct shim_mount pipe_builtin_fs; +extern struct shim_mount fifo_builtin_fs; extern struct shim_mount socket_builtin_fs; extern struct shim_mount epoll_builtin_fs; extern struct shim_mount eventfd_builtin_fs; diff --git a/LibOS/shim/include/shim_handle.h b/LibOS/shim/include/shim_handle.h index f932896b..46d6bd71 100644 --- a/LibOS/shim/include/shim_handle.h +++ b/LibOS/shim/include/shim_handle.h @@ -127,6 +127,7 @@ struct shim_dev_handle { }; struct shim_pipe_handle { + bool ready_for_ops; /* true for pipes, false for FIFOs that were mknod'ed but not open'ed */ char name[PIPE_URI_SIZE]; }; diff --git a/LibOS/shim/include/shim_table.h b/LibOS/shim/include/shim_table.h index c1ebe1a1..21af46f6 100644 --- a/LibOS/shim/include/shim_table.h +++ b/LibOS/shim/include/shim_table.h @@ -488,6 +488,8 @@ int shim_do_accept4(int sockfd, struct sockaddr* addr, socklen_t* addrlen, int f int shim_do_dup3(unsigned int oldfd, unsigned int newfd, int flags); int shim_do_epoll_create1(int flags); int shim_do_pipe2(int* fildes, int flags); +int shim_do_mknod(const char *pathname, mode_t mode, dev_t dev); +int shim_do_mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev); ssize_t shim_do_recvmmsg(int sockfd, struct mmsghdr* msg, size_t vlen, int flags, struct __kernel_timespec* timeout); int shim_do_prlimit64(pid_t pid, int resource, const struct __kernel_rlimit64* new_rlim, diff --git a/LibOS/shim/src/bookkeep/shim_handle.c b/LibOS/shim/src/bookkeep/shim_handle.c index e4dff195..55ad2447 100644 --- a/LibOS/shim/src/bookkeep/shim_handle.c +++ b/LibOS/shim/src/bookkeep/shim_handle.c @@ -755,7 +755,7 @@ BEGIN_CP_FUNC(handle) { DO_CP_IN_MEMBER(qstr, new_hdl, path); DO_CP_IN_MEMBER(qstr, new_hdl, uri); - if (fs && hdl->dentry) { + if (fs && fs != &fifo_builtin_fs && hdl->dentry) { DO_CP_MEMBER(mount, hdl, new_hdl, fs); } else { new_hdl->fs = NULL; diff --git a/LibOS/shim/src/fs/pipe/fs.c b/LibOS/shim/src/fs/pipe/fs.c index e90034c5..80153fb3 100644 --- a/LibOS/shim/src/fs/pipe/fs.c +++ b/LibOS/shim/src/fs/pipe/fs.c @@ -39,6 +39,9 @@ #include static ssize_t pipe_read(struct shim_handle* hdl, void* buf, size_t count) { + if (!hdl->info.pipe.ready_for_ops) + return -EACCES; + PAL_NUM bytes = DkStreamRead(hdl->pal_handle, 0, count, buf, NULL, 0); if (bytes == PAL_STREAM_ERROR) @@ -48,6 +51,9 @@ static ssize_t pipe_read(struct shim_handle* hdl, void* buf, size_t count) { } static ssize_t pipe_write(struct shim_handle* hdl, const void* buf, size_t count) { + if (!hdl->info.pipe.ready_for_ops) + return -EACCES; + PAL_NUM bytes = DkStreamWrite(hdl->pal_handle, 0, count, (void*)buf, NULL); if (bytes == PAL_STREAM_ERROR) @@ -92,6 +98,9 @@ static int pipe_checkout(struct shim_handle* hdl) { static off_t pipe_poll(struct shim_handle* hdl, int poll_type) { off_t ret = 0; + if (!hdl->info.pipe.ready_for_ops) + return -EACCES; + lock(&hdl->lock); if (!hdl->pal_handle) { @@ -150,7 +159,79 @@ static int pipe_setflags(struct shim_handle* hdl, int flags) { return 0; } -struct shim_fs_ops pipe_fs_ops = { +static int fifo_open(struct shim_handle* hdl, struct shim_dentry* dent, int flags) { + assert(hdl); + assert(dent && dent->data && dent->fs); + static_assert(sizeof(dent->data) >= sizeof(uint64_t), + "dentry's data must be at least 8B in size"); + + /* FIXME: man 7 fifo says "[with non-blocking flag], opening for write-only fails with ENXIO + * unless the other end has already been opened". We cannot enforce this failure since + * Graphene doesn't know whether the other process already opened this FIFO. */ + + if (flags & O_RDWR) { + /* POSIX disallows FIFOs opened for read-write, but Linux allows it. We must choose only + * one end (read or write) in our emulation, so we treat such FIFOs as read-only. This + * covers most apps seen in the wild (in particular, LTP apps). */ + debug("FIFO (named pipe) '%s' cannot be opened in read-write mode in Graphene. " + "Treating it as read-only.", qstrgetstr(&dent->fs->path)); + flags = O_RDONLY; + } + + int fd = -1; + if (flags & O_WRONLY) { + /* write end of FIFO is stashed in upper bits of dentry's data; invalidate afterwards */ + fd = (uint32_t)((uint64_t)dent->data >> 32); + dent->data = (void*)((uint64_t)dent->data | 0xFFFFFFFF00000000ULL); + } else { + /* read end of FIFO is stashed in lower bits of dentry's data; invalidate afterwards */ + fd = (uint32_t)((uint64_t)dent->data); + dent->data = (void*)((uint64_t)dent->data | 0x00000000FFFFFFFFULL); + } + + if (fd == -1) { + /* fd is invalid, happens if app tries to open the same FIFO end twice; this is ok in + * normal Linux but Graphene uses TLS-encrypted pipes which are inherently point-to-point; + * if this changes, should remove this error case (see GitHub issue #1417) */ + return -EOPNOTSUPP; + } + + struct shim_handle* fifo_hdl = get_fd_handle(fd, /*fd_flags=*/NULL, /*map=*/NULL); + if (!fifo_hdl) { + return -ENOENT; + } + + if (flags & O_NONBLOCK) { + /* FIFOs were created in blocking mode (see shim_do_mknodat), change their attributes */ + int ret = pipe_setflags(fifo_hdl, flags); + if (ret < 0) { + put_handle(fifo_hdl); + return ret; + } + } + + /* rewire new hdl to contents of intermediate FIFO hdl */ + hdl->type = fifo_hdl->type; + hdl->acc_mode = fifo_hdl->acc_mode; + hdl->owner = fifo_hdl->owner; + hdl->info = fifo_hdl->info; + hdl->pal_handle = fifo_hdl->pal_handle; + qstrcopy(&hdl->uri, &fifo_hdl->uri); + + hdl->info.pipe.ready_for_ops = true; + + fifo_hdl->pal_handle = NULL; /* ownership of PAL handle is transferred to hdl */ + + /* can remove intermediate FIFO hdl and its fd now */ + struct shim_handle* tmp = detach_fd_handle(fd, NULL, NULL); + assert(tmp == fifo_hdl); + put_handle(tmp); /* matches detach_fd_handle() */ + put_handle(fifo_hdl); /* matches get_fd_handle() */ + + return 0; +} + +static struct shim_fs_ops pipe_fs_ops = { .read = &pipe_read, .write = &pipe_write, .hstat = &pipe_hstat, @@ -159,7 +240,24 @@ struct shim_fs_ops pipe_fs_ops = { .setflags = &pipe_setflags, }; +static struct shim_fs_ops fifo_fs_ops = { + .read = &pipe_read, + .write = &pipe_write, + .poll = &pipe_poll, + .setflags = &pipe_setflags, +}; + +static struct shim_d_ops fifo_d_ops = { + .open = &fifo_open, +}; + struct shim_mount pipe_builtin_fs = { .type = URI_TYPE_PIPE, .fs_ops = &pipe_fs_ops, }; + +struct shim_mount fifo_builtin_fs = { + .type = "fifo", + .fs_ops = &fifo_fs_ops, + .d_ops = &fifo_d_ops, +}; diff --git a/LibOS/shim/src/fs/shim_dcache.c b/LibOS/shim/src/fs/shim_dcache.c index 4c6f344e..438115f0 100644 --- a/LibOS/shim/src/fs/shim_dcache.c +++ b/LibOS/shim/src/fs/shim_dcache.c @@ -353,14 +353,21 @@ BEGIN_CP_FUNC(dentry) { INIT_LIST_HEAD(new_dent, list); INIT_LISTP(&new_dent->children); INIT_LIST_HEAD(new_dent, siblings); - new_dent->data = NULL; clear_lock(&new_dent->lock); REF_SET(new_dent->ref_count, 0); + if (new_dent->fs == &fifo_builtin_fs) { + /* FIFO pipe, do not try to checkpoint its fs */ + new_dent->fs = NULL; + } else { + /* not FIFO, no need to keep data (FIFOs stash internal FDs into data field) */ + new_dent->data = NULL; + } + DO_CP_IN_MEMBER(qstr, new_dent, rel_path); DO_CP_IN_MEMBER(qstr, new_dent, name); - if (dent->fs) + if (new_dent->fs) DO_CP_MEMBER(mount, dent, new_dent, fs); if (dent->parent) @@ -396,6 +403,11 @@ BEGIN_RS_FUNC(dentry) { return -ENOMEM; } + if (!dent->fs) { + /* special case of FIFO pipe: use built-in FIFO FS */ + dent->fs = &fifo_builtin_fs; + } + /* DEP 6/16/17: I believe the point of this line is to * fix up the children linked list. Presumably the ref count and * child count is already correct in the checkpoint. */ diff --git a/LibOS/shim/src/fs/shim_fs.c b/LibOS/shim/src/fs/shim_fs.c index 73e21e2f..b2e00d59 100644 --- a/LibOS/shim/src/fs/shim_fs.c +++ b/LibOS/shim/src/fs/shim_fs.c @@ -37,9 +37,7 @@ struct shim_fs { struct shim_d_ops* d_ops; }; -#define NUM_MOUNTABLE_FS 3 - -struct shim_fs mountable_fs[NUM_MOUNTABLE_FS] = { +struct shim_fs mountable_fs[] = { { .name = "chroot", .fs_ops = &chroot_fs_ops, @@ -57,11 +55,10 @@ struct shim_fs mountable_fs[NUM_MOUNTABLE_FS] = { }, }; -#define NUM_BUILTIN_FS 5 - -struct shim_mount* builtin_fs[NUM_BUILTIN_FS] = { +struct shim_mount* builtin_fs[] = { &chroot_builtin_fs, &pipe_builtin_fs, + &fifo_builtin_fs, &socket_builtin_fs, &epoll_builtin_fs, &eventfd_builtin_fs, @@ -259,7 +256,7 @@ static inline struct shim_fs* find_fs(const char* type) { struct shim_fs* fs = NULL; size_t len = strlen(type); - for (int i = 0; i < NUM_MOUNTABLE_FS; i++) + for (size_t i = 0; i < ARRAY_SIZE(mountable_fs); i++) if (!memcmp(type, mountable_fs[i].name, len + 1)) { fs = &mountable_fs[i]; break; @@ -271,7 +268,7 @@ static inline struct shim_fs* find_fs(const char* type) { int search_builtin_fs(const char* type, struct shim_mount** fs) { size_t len = strlen(type); - for (int i = 0; i < NUM_BUILTIN_FS; i++) + for (size_t i = 0; i < ARRAY_SIZE(builtin_fs); i++) if (!memcmp(type, builtin_fs[i]->type, len + 1)) { *fs = builtin_fs[i]; return 0; diff --git a/LibOS/shim/src/fs/shim_namei.c b/LibOS/shim/src/fs/shim_namei.c index e1e94e97..8796b799 100644 --- a/LibOS/shim/src/fs/shim_namei.c +++ b/LibOS/shim/src/fs/shim_namei.c @@ -92,12 +92,11 @@ int __permission(struct shim_dentry* dent, mode_t mask) { * just NO_MODE. */ if (dent->mode == NO_MODE) { - - /* DEP 6/16/17: I don't think we should be defaulting to 0 if - * there isn't a mode function. */ assert(dent->fs); - assert(dent->fs->d_ops); - assert(dent->fs->d_ops->mode); + if (!dent->fs->d_ops || !dent->fs->d_ops->mode) { + /* dentry is emulated in LibOS (AF_UNIX socket or FIFO pipe): no permission check */ + return 0; + } /* Fall back to the low-level file system */ int err = dent->fs->d_ops->mode(dent, &mode); @@ -315,7 +314,11 @@ int __path_lookupat (struct shim_dentry * start, const char * path, int flags, } assert(fs); - assert(start->state & DENTRY_ISDIRECTORY); + + if (!(start->state & DENTRY_ISDIRECTORY)) { + err = -ENOTDIR; + goto out; + } // Peel off any preceeding slashes path = eat_slashes(path); diff --git a/LibOS/shim/src/shim_parser.c b/LibOS/shim/src/shim_parser.c index 44220901..9b3378a0 100644 --- a/LibOS/shim/src/shim_parser.c +++ b/LibOS/shim/src/shim_parser.c @@ -215,7 +215,7 @@ struct parser_table { {.slow = 1, .parser = {NULL}}, /* rt_sigsuspend */ {.slow = 0, .parser = {NULL}}, /* sigaltstack */ {.slow = 0, .parser = {NULL}}, /* utime */ - {.slow = 0, .parser = {NULL}}, /* mknod */ + {.slow = 1, .parser = {NULL, &parse_open_mode}}, /* mknod */ {.slow = 0, .parser = {NULL}}, /* uselib */ {.slow = 0, .parser = {NULL}}, /* personality */ {.slow = 0, .parser = {NULL}}, /* ustat */ @@ -342,7 +342,7 @@ struct parser_table { {.slow = 0, .parser = {&parse_at_fdcwd, NULL, &parse_open_flags, &parse_open_mode}}, /* openat */ {.slow = 0, .parser = {&parse_at_fdcwd}}, /* mkdirat */ - {.slow = 0, .parser = {&parse_at_fdcwd}}, /* mknodat */ + {.slow = 0, .parser = {&parse_at_fdcwd, NULL, &parse_open_mode}}, /* mknodat */ {.slow = 0, .parser = {&parse_at_fdcwd}}, /* fchownat */ {.slow = 0, .parser = {&parse_at_fdcwd}}, /* futimesat */ {.slow = 0, .parser = {&parse_at_fdcwd}}, /* newfstatat */ diff --git a/LibOS/shim/src/shim_syscalls.c b/LibOS/shim/src/shim_syscalls.c index 12ae3e1f..e3bbf23b 100644 --- a/LibOS/shim/src/shim_syscalls.c +++ b/LibOS/shim/src/shim_syscalls.c @@ -520,7 +520,7 @@ DEFINE_SHIM_SYSCALL(sigaltstack, 2, shim_do_sigaltstack, int, const stack_t*, ss SHIM_SYSCALL_RETURN_ENOSYS(utime, 2, int, char*, filename, struct utimbuf*, times) -SHIM_SYSCALL_RETURN_ENOSYS(mknod, 3, int, const char*, filename, int, mode, unsigned, dev) +DEFINE_SHIM_SYSCALL(mknod, 3, shim_do_mknod, int, const char*, filename, int, mode, unsigned, dev) SHIM_SYSCALL_RETURN_ENOSYS(uselib, 1, int, const char*, library) @@ -887,8 +887,8 @@ DEFINE_SHIM_SYSCALL(openat, 4, shim_do_openat, int, int, dfd, const char*, filen /* mkdirat: sys/shim_fs.c */ DEFINE_SHIM_SYSCALL(mkdirat, 3, shim_do_mkdirat, int, int, dfd, const char*, pathname, int, mode) -SHIM_SYSCALL_RETURN_ENOSYS(mknodat, 4, int, int, dfd, const char*, filename, int, mode, unsigned, - dev) +DEFINE_SHIM_SYSCALL(mknodat, 4, shim_do_mknodat, int, int, dfd, const char*, filename, int, mode, + unsigned, dev) DEFINE_SHIM_SYSCALL(fchownat, 5, shim_do_fchownat, int, int, dfd, const char*, filename, uid_t, user, gid_t, group, int, flag) diff --git a/LibOS/shim/src/sys/shim_pipe.c b/LibOS/shim/src/sys/shim_pipe.c index 7b0f17b7..4f1a53de 100644 --- a/LibOS/shim/src/sys/shim_pipe.c +++ b/LibOS/shim/src/sys/shim_pipe.c @@ -17,7 +17,7 @@ /* * shim_pipe.c * - * Implementation of system call "pipe", "pipe2" and "socketpair". + * Implementation of system calls "pipe", "pipe2", "socketpair", "mknod", and "mknodat". */ #include @@ -30,6 +30,7 @@ #include "shim_handle.h" #include "shim_internal.h" #include "shim_table.h" +#include "shim_types.h" #include "shim_utils.h" static int create_pipes(PAL_HANDLE* srv, PAL_HANDLE* cli, int flags, char* name, @@ -80,11 +81,22 @@ out: return ret; } +static void undo_set_fd_handle(int fd) { + if (fd >= 0) { + struct shim_handle* hdl = detach_fd_handle(fd, NULL, NULL); + if (hdl) + put_handle(hdl); + } +} + int shim_do_pipe2(int* filedes, int flags) { + int ret = 0; + if (!filedes || test_user_memory(filedes, 2 * sizeof(int), true)) return -EFAULT; - int ret = 0; + int vfd1 = -1; + int vfd2 = -1; struct shim_handle* hdl1 = get_new_handle(); struct shim_handle* hdl2 = get_new_handle(); @@ -104,6 +116,9 @@ int shim_do_pipe2(int* filedes, int flags) { hdl2->flags = O_WRONLY; hdl2->acc_mode = MAY_WRITE; + hdl1->info.pipe.ready_for_ops = true; + hdl2->info.pipe.ready_for_ops = true; + ret = create_pipes(&hdl1->pal_handle, &hdl2->pal_handle, flags, hdl1->info.pipe.name, &hdl1->uri); if (ret < 0) @@ -112,28 +127,27 @@ int shim_do_pipe2(int* filedes, int flags) { memcpy(hdl2->info.pipe.name, hdl1->info.pipe.name, sizeof(hdl2->info.pipe.name)); qstrcopy(&hdl2->uri, &hdl1->uri); - flags = flags & O_CLOEXEC ? FD_CLOEXEC : 0; - int vfd1 = set_new_fd_handle(hdl1, flags, NULL); - int vfd2 = set_new_fd_handle(hdl2, flags, NULL); + vfd1 = set_new_fd_handle(hdl1, flags & O_CLOEXEC ? FD_CLOEXEC : 0, NULL); + if (vfd1 < 0) { + ret = vfd1; + goto out; + } - if (vfd1 < 0 || vfd2 < 0) { - if (vfd1 >= 0) { - struct shim_handle* tmp = detach_fd_handle(vfd1, NULL, NULL); - if (tmp) - put_handle(tmp); - } - if (vfd2 >= 0) { - struct shim_handle* tmp = detach_fd_handle(vfd2, NULL, NULL); - if (tmp) - put_handle(tmp); - } - ret = (vfd1 < 0) ? vfd1 : vfd2; + vfd2 = set_new_fd_handle(hdl2, flags & O_CLOEXEC ? FD_CLOEXEC : 0, NULL); + if (vfd2 < 0) { + ret = vfd2; goto out; } filedes[0] = vfd1; filedes[1] = vfd2; + + ret = 0; out: + if (ret < 0) { + undo_set_fd_handle(vfd1); + undo_set_fd_handle(vfd2); + } if (hdl1) put_handle(hdl1); if (hdl2) @@ -146,6 +160,8 @@ int shim_do_pipe(int* filedes) { } int shim_do_socketpair(int domain, int type, int protocol, int* sv) { + int ret = 0; + if (domain != AF_UNIX) return -EAFNOSUPPORT; @@ -155,7 +171,9 @@ int shim_do_socketpair(int domain, int type, int protocol, int* sv) { if (!sv || test_user_memory(sv, 2 * sizeof(int), true)) return -EFAULT; - int ret = 0; + int vfd1 = -1; + int vfd2 = -1; + struct shim_handle* hdl1 = get_new_handle(); struct shim_handle* hdl2 = get_new_handle(); @@ -193,31 +211,170 @@ int shim_do_socketpair(int domain, int type, int protocol, int* sv) { memcpy(sock2->addr.un.name, sock1->addr.un.name, sizeof(sock2->addr.un.name)); qstrcopy(&hdl2->uri, &hdl1->uri); - int flags = type & SOCK_CLOEXEC ? FD_CLOEXEC : 0; - int vfd1 = set_new_fd_handle(hdl1, flags, NULL); - int vfd2 = set_new_fd_handle(hdl2, flags, NULL); + vfd1 = set_new_fd_handle(hdl1, type & SOCK_CLOEXEC ? FD_CLOEXEC : 0, NULL); + if (vfd1 < 0) { + ret = vfd1; + goto out; + } - if (vfd1 < 0 || vfd2 < 0) { - if (vfd1 >= 0) { - struct shim_handle* tmp = detach_fd_handle(vfd1, NULL, NULL); - if (tmp) - put_handle(tmp); - } - if (vfd2 >= 0) { - struct shim_handle* tmp = detach_fd_handle(vfd2, NULL, NULL); - if (tmp) - put_handle(tmp); - } - ret = (vfd1 < 0) ? vfd1 : vfd2; + vfd2 = set_new_fd_handle(hdl2, type & SOCK_CLOEXEC ? FD_CLOEXEC : 0, NULL); + if (vfd2 < 0) { + ret = vfd2; goto out; } sv[0] = vfd1; sv[1] = vfd2; + + ret = 0; out: + if (ret < 0) { + undo_set_fd_handle(vfd1); + undo_set_fd_handle(vfd2); + } if (hdl1) put_handle(hdl1); if (hdl2) put_handle(hdl2); return ret; } + +int shim_do_mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev) { + int ret = 0; + __UNUSED(dev); + + /* corner case of regular file: emulate via open() + close() */ + if (!(mode & S_IFMT) || S_ISREG(mode)) { + mode &= ~S_IFREG; + /* FIXME: Graphene assumes that file is at least readable by owner, in particular, see + * unlink() emulation that uses DkStreamOpen(). We change empty mode to readable + * by user here to allow a consequent unlink. Was detected on LTP mknod tests. */ + int fd = shim_do_openat(dirfd, pathname, O_CREAT | O_EXCL, mode ? : S_IRUSR); + if (fd < 0) + return fd; + return shim_do_close(fd); + } + + int vfd1 = -1; + int vfd2 = -1; + + struct shim_handle* hdl1 = NULL; + struct shim_handle* hdl2 = NULL; + + if (!S_ISFIFO(mode)) + return -EINVAL; + + if (!pathname || test_user_string(pathname)) + return -EFAULT; + + if (pathname[0] == '\0') + return -ENOENT; + + /* add named pipe as a pseudo entry to file system (relative to dfd) */ + struct shim_dentry* dir = NULL; + struct shim_dentry* dent = NULL; + + ret = get_dirfd_dentry(dirfd, &dir); + if (ret < 0) { + goto out; + } + + ret = path_lookupat(dir, pathname, LOOKUP_CREATE, &dent, NULL); + if (ret < 0 && ret != -ENOENT) { + goto out; + } + + if (!dent) { + ret = -ENOENT; /* impossible path, file cannot be created, mknod must return ENOENT */ + goto out; + } + + if (dent->state & DENTRY_VALID && !(dent->state & DENTRY_NEGATIVE)) { + ret = -EEXIST; + goto out; + } + + dent->fs = &fifo_builtin_fs; + + /* create two pipe ends */ + hdl1 = get_new_handle(); + hdl2 = get_new_handle(); + + if (!hdl1 || !hdl2) { + ret = -ENOMEM; + goto out; + } + + hdl1->type = TYPE_PIPE; + set_handle_fs(hdl1, &fifo_builtin_fs); + hdl1->flags = O_RDONLY; + hdl1->acc_mode = MAY_READ; + get_dentry(dent); + hdl1->dentry = dent; + + hdl2->type = TYPE_PIPE; + set_handle_fs(hdl2, &fifo_builtin_fs); + hdl2->flags = O_WRONLY; + hdl2->acc_mode = MAY_WRITE; + get_dentry(dent); + hdl2->dentry = dent; + + /* FIFO must be open'ed to start read/write operations, mark as not ready */ + hdl1->info.pipe.ready_for_ops = false; + hdl2->info.pipe.ready_for_ops = false; + + /* FIFO pipes are created in blocking mode; they will be changed to non-blocking if open()'ed + * in non-blocking mode later (see fifo_open) */ + ret = create_pipes(&hdl1->pal_handle, &hdl2->pal_handle, /*flags=*/0, hdl1->info.pipe.name, + &hdl1->uri); + if (ret < 0) + goto out; + + memcpy(hdl2->info.pipe.name, hdl1->info.pipe.name, sizeof(hdl2->info.pipe.name)); + qstrcopy(&hdl2->uri, &hdl1->uri); + + /* assign virtual FDs to both handles; ideally FDs must be assigned during open() + * but then checkpointing after mknod() would not migrate the prepared hdl1 and hdl2; also FDs + * are easier to bind to dentry created here */ + vfd1 = set_new_fd_handle(hdl1, /*fd_flags=*/0, NULL); + if (vfd1 < 0) { + ret = vfd1; + goto out; + } + + vfd2 = set_new_fd_handle(hdl2, /*fd_flags=*/0, NULL); + if (vfd2 < 0) { + ret = vfd2; + goto out; + } + + /* mark pseudo entry in file system as valid and stash FDs in data */ + dent->state &= ~DENTRY_NEGATIVE; + dent->state |= DENTRY_VALID | DENTRY_RECENTLY; + + static_assert(sizeof(vfd1) == sizeof(uint32_t) && sizeof(vfd2) == sizeof(uint32_t), + "FDs must be 4B in size"); + static_assert(sizeof(dent->data) >= sizeof(uint64_t), + "dentry's data must be at least 8B in size"); + dent->data = (void*)((uint64_t)vfd2 << 32 | (uint64_t)vfd1); + + ret = 0; +out: + if (ret < 0) { + undo_set_fd_handle(vfd1); + undo_set_fd_handle(vfd2); + } + if (dir) + put_dentry(dir); + if (dent) + put_dentry(dent); + if (hdl1) + put_handle(hdl1); + if (hdl2) + put_handle(hdl2); + return ret; +} + +int shim_do_mknod(const char *pathname, mode_t mode, dev_t dev) { + return shim_do_mknodat(AT_FDCWD, pathname, mode, dev); +} diff --git a/LibOS/shim/test/regression/.gitignore b/LibOS/shim/test/regression/.gitignore index f6da6f33..96826e4d 100644 --- a/LibOS/shim/test/regression/.gitignore +++ b/LibOS/shim/test/regression/.gitignore @@ -40,6 +40,7 @@ /init_fail /large_dir_read /large_mmap +/mkfifo /mmap_file /mprotect_file_fork /multi_pthread diff --git a/LibOS/shim/test/regression/Makefile b/LibOS/shim/test/regression/Makefile index 6fda288b..0199d599 100644 --- a/LibOS/shim/test/regression/Makefile +++ b/LibOS/shim/test/regression/Makefile @@ -32,6 +32,7 @@ c_executables = \ init_fail \ large_mmap \ large_dir_read \ + mkfifo \ mmap_file \ mprotect_file_fork \ multi_pthread \ diff --git a/LibOS/shim/test/regression/mkfifo.c b/LibOS/shim/test/regression/mkfifo.c new file mode 100644 index 00000000..a98e7aaa --- /dev/null +++ b/LibOS/shim/test/regression/mkfifo.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIFO_PATH "fifo123" + +int main(int argc, char** argv) { + int fd; + char buffer[1024]; + + if (mkfifo(FIFO_PATH, S_IRWXU) < 0) { + perror("mkfifo error"); + return 1; + } + + pid_t pid = fork(); + + if (pid < 0) { + perror("fork error"); + return 1; + } else if (pid == 0) { + /* client */ + fd = open(FIFO_PATH, O_NONBLOCK | O_RDONLY); + if (fd < 0) { + perror("[child] open error"); + return 1; + } + + /* note that Linux guarantees either no read message or the complete message on FIFO since + * message size is less than PIPE_BUF; see man pipe(7) */ + ssize_t bytes = 0; + while (bytes <= 0) { + errno = 0; + bytes = read(fd, &buffer, sizeof(buffer)); + if (bytes < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + perror("[child] read error"); + return 1; + } + sched_yield(); + } + + buffer[sizeof(buffer) - 1] = '\0'; + if (bytes < sizeof(buffer)) + buffer[bytes] = '\0'; + + if (close(fd) < 0) { + perror("[child] close error"); + return 1; + } + + printf("read on FIFO: %s\n", buffer); + } else { + /* server */ + fd = -1; + while (fd < 0) { + /* wait until client is ready for read */ + errno = 0; + fd = open(FIFO_PATH, O_NONBLOCK | O_WRONLY); + if (fd < 0 && errno != ENXIO) { + perror("[parent] open error"); + return 1; + } + sched_yield(); + } + + /* note that Linux guarantees sending the complete message on FIFO since message size is + * less than PIPE_BUF and there are no signals possible in this test; see man pipe(7) */ + snprintf(buffer, sizeof(buffer), "Hello from write end of FIFO!"); + if (write(fd, &buffer, strlen(buffer) + 1) < 0) { + perror("[parent] write error"); + return 1; + } + + if (close(fd) < 0) { + perror("[parent] close error"); + return 1; + } + + pid = wait(NULL); /* wait for child termination, just for sanity */ + if (pid < 0) { + perror("[parent] wait error"); + return 1; + } + + if (unlink(FIFO_PATH) < 0) { + perror("[parent] unlink error"); + return 1; + } + } + + return 0; +} diff --git a/LibOS/shim/test/regression/test_libos.py b/LibOS/shim/test/regression/test_libos.py index 918ee3b2..4fbe4433 100644 --- a/LibOS/shim/test/regression/test_libos.py +++ b/LibOS/shim/test/regression/test_libos.py @@ -500,6 +500,10 @@ class TC_80_Socket(RegressionTestCase): stdout, _ = self.run_binary(['pipe'], timeout=60) self.assertIn('read on pipe: Hello from write end of pipe!', stdout) + def test_095_mkfifo(self): + stdout, _ = self.run_binary(['mkfifo'], timeout=60) + self.assertIn('read on FIFO: Hello from write end of FIFO!', stdout) + def test_100_socket_unix(self): stdout, _ = self.run_binary(['unix']) self.assertIn('Data: This is packet 0', stdout)