mirror of
https://github.com/clearlinux/graphene.git
synced 2026-09-03 20:31:40 +00:00
[LibOS] Add support for FIFOs (named pipes)
This commit adds support for FIFOs and the corresponding syscalls mknod() and mknodat(). Internally, FIFOs are emulated as pseudo- files in chroot mount points (not visible in host FS). FIFOs' read/write operations are emulated via pipes at PAL level (this means that they are transparently encrypted under SGX PAL). Generally, emulation of FIFOs is similar to emulation of named UNIX domain sockets, i.e., they are "ephemeral" and only allow communication between two related processes. New LibOS test is added: multi-process `mkfifo`.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
#include <shim_thread.h>
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
+190
-33
@@ -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 <asm/fcntl.h>
|
||||
@@ -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(<named-pipe>)
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
/init_fail
|
||||
/large_dir_read
|
||||
/large_mmap
|
||||
/mkfifo
|
||||
/mmap_file
|
||||
/mprotect_file_fork
|
||||
/multi_pthread
|
||||
|
||||
@@ -32,6 +32,7 @@ c_executables = \
|
||||
init_fail \
|
||||
large_mmap \
|
||||
large_dir_read \
|
||||
mkfifo \
|
||||
mmap_file \
|
||||
mprotect_file_fork \
|
||||
multi_pthread \
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user