From 350b498fb0f85026cb6225266094cd7cd6dfcb7e Mon Sep 17 00:00:00 2001 From: Dmitrii Kuvaiskii Date: Fri, 20 Mar 2020 20:33:48 -0700 Subject: [PATCH] [Pal/{Linux,Linux-SGX}] Refactor db_pipes.c code Cleanup of the pipes' implementation in both PALs, including better comments and small fixups (proper `pipe_delete()`, proper error handling, proper nonblocking option). --- Pal/src/host/Linux-SGX/db_pipes.c | 324 +++++++++++++++------- Pal/src/host/Linux/db_pipes.c | 439 +++++++++++++++++++----------- 2 files changed, 501 insertions(+), 262 deletions(-) diff --git a/Pal/src/host/Linux-SGX/db_pipes.c b/Pal/src/host/Linux-SGX/db_pipes.c index 5299a882..f25362fb 100644 --- a/Pal/src/host/Linux-SGX/db_pipes.c +++ b/Pal/src/host/Linux-SGX/db_pipes.c @@ -21,8 +21,6 @@ * "pipe:" or "pipe.srv:". */ -#include - #include "api.h" #include "pal.h" #include "pal_debug.h" @@ -33,53 +31,87 @@ #include "pal_linux_defs.h" #include "pal_linux_error.h" #include "pal_security.h" + typedef __kernel_pid_t pid_t; #include #include +#include #include -static int pipe_path(int pipeid, char* path, int len) { - /* use abstrace UNIX sockets for pipes */ - memset(path, 0, len); - return snprintf(path + 1, len - 1, "%s%08x", pal_sec.pipe_prefix, pipeid); -} - static int pipe_addr(int pipeid, struct sockaddr_un* addr) { + /* use abstract UNIX sockets for pipes, with name format "@/graphene/12345678" */ addr->sun_family = AF_UNIX; - return pipe_path(pipeid, (char*)addr->sun_path, sizeof(addr->sun_path)); -} + memset(addr->sun_path, 0, sizeof(addr->sun_path)); -static inline int pipe_type(int options) { - int type = SOCK_STREAM; - if (options & PAL_OPTION_NONBLOCK) - type |= SOCK_NONBLOCK; - return type; + /* for abstract sockets, first char is NUL */ + char* str = (char*)addr->sun_path + 1; + size_t size = sizeof(addr->sun_path) - 1; + + /* pipe_prefix already contains a slash at the end, so not needed in the format string */ + int ret = snprintf(str, size, "%s%08x", pal_sec.pipe_prefix, pipeid); + return ret >= 0 && (size_t)ret < size ? 0 : -EINVAL; } +/*! + * \brief Create a listening abstract UNIX socket as preparation for connecting two ends of a pipe. + * + * An abstract UNIX socket with name "@/graphene/" is opened for listening. A corresponding + * PAL handle with type `pipesrv` is created. This PAL handle typically serves only as an + * intermediate step to connect two ends of the pipe (`pipecli` and `pipe`). As soon as the other + * end of the pipe connects to this listening socket, a new accepted socket and the corresponding + * PAL handle are created, and this `pipesrv` handle can be closed. + * + * \param[out] handle PAL handle of type `pipesrv` with abstract UNIX socket opened for listening. + * \param[in] pipeid Integer uniquely identifying the pipe. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_listen(PAL_HANDLE* handle, PAL_NUM pipeid, int options) { - struct sockaddr_un addr; int ret; - if ((ret = pipe_addr(pipeid, &addr)) < 0) - return ret; + struct sockaddr_un addr; + ret = pipe_addr(pipeid, &addr); + if (IS_ERR(ret)) + return -PAL_ERROR_DENIED; - unsigned int addrlen = sizeof(struct sockaddr_un); struct sockopt sock_options; - ret = ocall_listen(AF_UNIX, pipe_type(options), 0, /*ipv6_v6only=*/0, + unsigned int addrlen = sizeof(struct sockaddr_un); + int nonblock = options & PAL_OPTION_NONBLOCK ? SOCK_NONBLOCK : 0; + + ret = ocall_listen(AF_UNIX, SOCK_STREAM | nonblock, 0, /*ipv6_v6only=*/0, (struct sockaddr*)&addr, &addrlen, &sock_options); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipe)); + if (!hdl) { + ocall_close(ret); + return -PAL_ERROR_NOMEM; + } + SET_HANDLE_TYPE(hdl, pipesrv); - HANDLE_HDR(hdl)->flags |= RFD(0); - hdl->pipe.fd = ret; - hdl->pipe.pipeid = pipeid; - hdl->pipe.nonblocking = options & PAL_OPTION_NONBLOCK ? PAL_TRUE : PAL_FALSE; - *handle = hdl; + HANDLE_HDR(hdl)->flags |= RFD(0); /* cannot write to a listening socket */ + hdl->pipe.fd = ret; + hdl->pipe.pipeid = pipeid; + hdl->pipe.nonblocking = options & PAL_OPTION_NONBLOCK ? PAL_TRUE : PAL_FALSE; + + *handle = hdl; return 0; } +/*! + * \brief Accept the other end of the pipe and create PAL handle for our end of the pipe. + * + * Caller creates a `pipesrv` PAL handle with the underlying abstract UNIX socket opened for + * listening, and then calls this function to wait for the other end of the pipe to connect. + * When the connection request arrives, a new `pipecli` PAL handle is created with the + * corresponding underlying socket and is returned in `client`. This `pipecli` PAL handle denotes + * our end of the pipe. Typically, `pipesrv` handle is not needed after this and can be closed. + * + * \param[in] handle PAL handle of type `pipesrv` with abstract UNIX socket opened for listening. + * \param[out] client PAL handle of type `pipecli` connected to the other end of the pipe (`pipe`). + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_waitforclient(PAL_HANDLE handle, PAL_HANDLE* client) { if (!IS_HANDLE_TYPE(handle, pipesrv)) return -PAL_ERROR_NOTSERVER; @@ -93,64 +125,131 @@ static int pipe_waitforclient(PAL_HANDLE handle, PAL_HANDLE* client) { return unix_to_pal_error(ERRNO(ret)); PAL_HANDLE clnt = malloc(HANDLE_SIZE(pipe)); + if (!clnt) { + ocall_close(ret); + return -PAL_ERROR_NOMEM; + } + SET_HANDLE_TYPE(clnt, pipecli); HANDLE_HDR(clnt)->flags |= RFD(0) | WFD(0); - clnt->pipe.fd = ret; - clnt->pipe.nonblocking = PAL_FALSE; - clnt->pipe.pipeid = handle->pipe.pipeid; - *client = clnt; + clnt->pipe.fd = ret; + clnt->pipe.nonblocking = PAL_FALSE; /* FIXME: must set nonblocking based on `handle` value */ + clnt->pipe.pipeid = handle->pipe.pipeid; + *client = clnt; return 0; } +/*! + * \brief Connect to the other end of the pipe and create PAL handle for our end of the pipe. + * + * This function connects to the other end of the pipe, represented as an abstract UNIX socket + * "@/graphene/" opened for listening. When the connection succeeds, a new `pipe` PAL handle + * is created with the corresponding underlying socket and is returned in `handle`. The other end of + * the pipe is typically of type `pipecli`. + * + * \param[out] handle PAL handle of type `pipe` with abstract UNIX socket connected to another end. + * \param[in] pipeid Integer uniquely identifying the pipe. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_connect(PAL_HANDLE* handle, PAL_NUM pipeid, int options) { - struct sockaddr_un addr; int ret; - if ((ret = pipe_addr(pipeid, &addr)) < 0) - return ret; + struct sockaddr_un addr; + ret = pipe_addr(pipeid, &addr); + if (IS_ERR(ret)) + return -PAL_ERROR_DENIED; struct sockopt sock_options; - ret = ocall_connect(AF_UNIX, pipe_type(options), 0, /*ipv6_v6only=*/0, (void*)&addr, - sizeof(struct sockaddr_un), NULL, NULL, &sock_options); + unsigned int addrlen = sizeof(struct sockaddr_un); + int nonblock = options & PAL_OPTION_NONBLOCK ? SOCK_NONBLOCK : 0; + + ret = ocall_connect(AF_UNIX, SOCK_STREAM | nonblock, 0, /*ipv6_v6only=*/0, + (const struct sockaddr*)&addr, + addrlen, NULL, NULL, &sock_options); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipe)); + if (!hdl) { + ocall_close(ret); + return -PAL_ERROR_NOMEM; + } + SET_HANDLE_TYPE(hdl, pipe); HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(0); - hdl->pipe.fd = ret; - hdl->pipe.pipeid = pipeid; - hdl->pipe.nonblocking = (options & PAL_OPTION_NONBLOCK) ? PAL_TRUE : PAL_FALSE; - *handle = hdl; + hdl->pipe.fd = ret; + hdl->pipe.pipeid = pipeid; + hdl->pipe.nonblocking = (options & PAL_OPTION_NONBLOCK) ? PAL_TRUE : PAL_FALSE; + *handle = hdl; return 0; } +/*! + * \brief Create PAL handle with read and write ends of a pipe. + * + * This function creates a PAL handle of type `pipeprv` (anonymous pipe). In contrast to other types + * of pipes, `pipeprv` encapsulates both ends of the pipe, backed by a host-level socketpair. This + * type of pipe is typically reserved for internal PAL uses, not for LibOS emulation. + * + * \param[out] handle PAL handle of type `pipeprv` backed by a host-level socketpair. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_private(PAL_HANDLE* handle, int options) { - int ret, fds[2]; - int type = SOCK_STREAM; - if (options & PAL_OPTION_NONBLOCK) - type |= SOCK_NONBLOCK; + int fds[2]; - ret = ocall_socketpair(AF_UNIX, type, 0, fds); + int nonblock = options & PAL_OPTION_NONBLOCK ? SOCK_NONBLOCK : 0; + + int ret = ocall_socketpair(AF_UNIX, SOCK_STREAM | nonblock, 0, fds); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipeprv)); + if (!hdl) { + ocall_close(fds[0]); + ocall_close(fds[1]); + return -PAL_ERROR_NOMEM; + } + SET_HANDLE_TYPE(hdl, pipeprv); - HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(1); + HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(1); /* first FD for reads, second FD for writes */ hdl->pipeprv.fds[0] = fds[0]; hdl->pipeprv.fds[1] = fds[1]; hdl->pipeprv.nonblocking = (options & PAL_OPTION_NONBLOCK) ? PAL_TRUE : PAL_FALSE; - *handle = hdl; + + *handle = hdl; return 0; } -/* 'open' operation of pipe stream. For each pipe stream, it is - identified by a decimal number in URI. There could be two - types: pipe and pipe.srv. They behave pretty much the same, - except they are two ends of the pipe. */ +/*! + * \brief Create PAL handle of type `pipeprv`, `pipesrv`, or `pipe` depending on `type` and `uri`. + * + * Depending on the combination of `type` and `uri`, the following PAL handles are created: + * + * - `type` is URI_TYPE_PIPE and `url` is empty: create `pipeprv` handle (with two connected + * ends of an anonymous pipe). + * + * - `type` is URI_TYPE_PIPE_SRV: create `pipesrv` handle (intermediate listening socket) with + * name in the form of "@/graphene/" where pipeid is + * derived from `uri` via strtol(). Caller is expected to call + * pipe_waitforclient() afterwards. + * + * - `type` is URI_TYPE_PIPE: create `pipe` handle (connecting socket) with name in the form of + * "@/graphene/" where pipeid is derived from `uri` via + * strtol(). + * + * \param[out] handle Created PAL handle of type `pipeprv`, `pipesrv`, or `pipe`. + * \param[in] type Can be URI_TYPE_PIPE or URI_TYPE_PIPE_SRV. + * \param[in] uri Content is either NUL (for anonymous pipe) or an integer denoting pipeid. + * \param[in] access Not used. + * \param[in] share Not used. + * \param[in] create Not used. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_open(PAL_HANDLE* handle, const char* type, const char* uri, int access, int share, int create, int options) { if (!WITHIN_MASK(access, PAL_ACCESS_MASK) || !WITHIN_MASK(share, PAL_SHARE_MASK) || @@ -175,7 +274,15 @@ static int pipe_open(PAL_HANDLE* handle, const char* type, const char* uri, int return -PAL_ERROR_INVAL; } -/* 'read' operation of pipe stream. offset does not apply here. */ +/*! + * \brief Read from pipe (from read end in case of `pipeprv`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipecli`, or `pipe`. + * \param[in] offset Not used. + * \param[in] len Size of user-supplied buffer. + * \param[out] buffer User-supplied buffer to read data to. + * \return Number of bytes read on success, negative PAL error code otherwise. + */ static int64_t pipe_read(PAL_HANDLE handle, uint64_t offset, uint64_t len, void* buffer) { if (offset) return -PAL_ERROR_INVAL; @@ -184,12 +291,9 @@ static int64_t pipe_read(PAL_HANDLE handle, uint64_t offset, uint64_t len, void* !IS_HANDLE_TYPE(handle, pipe)) return -PAL_ERROR_NOTCONNECTION; - if (len != (uint32_t)len) - return -PAL_ERROR_INVAL; + int fd = IS_HANDLE_TYPE(handle, pipeprv) ? handle->pipeprv.fds[0] : handle->pipe.fd; - int fd = IS_HANDLE_TYPE(handle, pipeprv) ? handle->pipeprv.fds[0] : handle->pipe.fd; ssize_t bytes = ocall_recv(fd, buffer, len, NULL, NULL, NULL, NULL); - if (IS_ERR(bytes)) return unix_to_pal_error(ERRNO(bytes)); @@ -199,7 +303,15 @@ static int64_t pipe_read(PAL_HANDLE handle, uint64_t offset, uint64_t len, void* return bytes; } -/* 'write' operation of pipe stream. offset does not apply here. */ +/*! + * \brief Write to pipe (to write end in case of `pipeprv`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipecli`, or `pipe`. + * \param[in] offset Not used. + * \param[in] len Size of user-supplied buffer. + * \param[in] buffer User-supplied buffer to write data from. + * \return Number of bytes written on success, negative PAL error code otherwise. + */ static int64_t pipe_write(PAL_HANDLE handle, uint64_t offset, uint64_t len, const void* buffer) { if (offset) return -PAL_ERROR_INVAL; @@ -208,9 +320,6 @@ static int64_t pipe_write(PAL_HANDLE handle, uint64_t offset, uint64_t len, cons !IS_HANDLE_TYPE(handle, pipe)) return -PAL_ERROR_NOTCONNECTION; - if (len != (uint32_t)len) - return -PAL_ERROR_INVAL; - int fd = IS_HANDLE_TYPE(handle, pipeprv) ? handle->pipeprv.fds[1] : handle->pipe.fd; ssize_t bytes = ocall_send(fd, buffer, len, NULL, 0, NULL, 0); @@ -220,7 +329,12 @@ static int64_t pipe_write(PAL_HANDLE handle, uint64_t offset, uint64_t len, cons return bytes; } -/* 'close' operation of pipe stream. */ +/*! + * \brief Close pipe (both ends in case of `pipeprv`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_close(PAL_HANDLE handle) { if (IS_HANDLE_TYPE(handle, pipeprv)) { if (handle->pipeprv.fds[0] != PAL_IDX_POISON) { @@ -231,10 +345,7 @@ static int pipe_close(PAL_HANDLE handle) { ocall_close(handle->pipeprv.fds[1]); handle->pipeprv.fds[1] = PAL_IDX_POISON; } - return 0; - } - - if (handle->pipe.fd != PAL_IDX_POISON) { + } else if (handle->pipe.fd != PAL_IDX_POISON) { ocall_close(handle->pipe.fd); handle->pipe.fd = PAL_IDX_POISON; } @@ -242,40 +353,14 @@ static int pipe_close(PAL_HANDLE handle) { return 0; } -/* 'delete' operation of pipe stream. */ +/*! + * \brief Shut down pipe (one or both ends in case of `pipeprv` depending on `access`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[in] access May be 0, PAL_DELETE_RD, PAL_DELETE_WR. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_delete(PAL_HANDLE handle, int access) { - if (IS_HANDLE_TYPE(handle, pipeprv)) { - switch (access) { - case 0: - if (handle->pipeprv.fds[0] != PAL_IDX_POISON) { - ocall_close(handle->pipeprv.fds[0]); - handle->pipeprv.fds[0] = PAL_IDX_POISON; - } - if (handle->pipeprv.fds[1] != PAL_IDX_POISON) { - ocall_close(handle->pipeprv.fds[1]); - handle->pipeprv.fds[1] = PAL_IDX_POISON; - } - break; - case PAL_DELETE_RD: - if (handle->pipeprv.fds[0] != PAL_IDX_POISON) { - ocall_close(handle->pipeprv.fds[0]); - handle->pipeprv.fds[0] = PAL_IDX_POISON; - } - break; - case PAL_DELETE_WR: - if (handle->pipeprv.fds[1] != PAL_IDX_POISON) { - ocall_close(handle->pipeprv.fds[1]); - handle->pipeprv.fds[1] = PAL_IDX_POISON; - } - break; - default: - return -PAL_ERROR_INVAL; - } - } - - if (handle->pipe.fd == PAL_IDX_POISON) - return 0; - int shutdown; switch (access) { case 0: @@ -291,10 +376,34 @@ static int pipe_delete(PAL_HANDLE handle, int access) { return -PAL_ERROR_INVAL; } - ocall_shutdown(handle->pipe.fd, shutdown); + if (IS_HANDLE_TYPE(handle, pipeprv)) { + /* pipeprv has two underlying FDs, shut down the requested one(s) */ + if (handle->pipeprv.fds[0] != PAL_IDX_POISON && + (shutdown == SHUT_RD || shutdown == SHUT_RDWR)) { + ocall_shutdown(handle->pipeprv.fds[0], SHUT_RD); + } + + if (handle->pipeprv.fds[1] != PAL_IDX_POISON && + (shutdown == SHUT_WR || shutdown == SHUT_RDWR)) { + ocall_shutdown(handle->pipeprv.fds[1], SHUT_WR); + } + } else { + /* other types of pipes have a single underlying FD, shut it down */ + if (handle->pipe.fd != PAL_IDX_POISON) { + ocall_shutdown(handle->pipe.fd, shutdown); + } + } + return 0; } +/*! + * \brief Retrieve attributes of PAL handle. + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[out] attr User-supplied buffer to store handle's current attributes. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { int ret; @@ -347,6 +456,15 @@ static int pipe_attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { return 0; } +/*! + * \brief Set attributes of PAL handle. + * + * Currently only `nonblocking` attribute can be set. + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[in] attr User-supplied buffer with new handle's attributes. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_attrsetbyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { if (handle->generic.fds[0] == PAL_IDX_POISON) return -PAL_ERROR_BADHANDLE; @@ -366,8 +484,18 @@ static int pipe_attrsetbyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { return 0; } +/*! + * \brief Retrieve full URI of PAL handle. + * + * Full URI is composed of the type and pipeid: ":". + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[out] buffer User-supplied buffer to write URI to. + * \param[in] count Size of the user-supplied buffer. + * \return Number of bytes written on success, negative PAL error code otherwise. + */ static int pipe_getname(PAL_HANDLE handle, char* buffer, size_t count) { - int old_count = count; + size_t old_count = count; int ret; const char* prefix = NULL; diff --git a/Pal/src/host/Linux/db_pipes.c b/Pal/src/host/Linux/db_pipes.c index 254ab12e..4e5b8aad 100644 --- a/Pal/src/host/Linux/db_pipes.c +++ b/Pal/src/host/Linux/db_pipes.c @@ -21,8 +21,6 @@ * "pipe:" or "pipe.srv:". */ -#include - #include "api.h" #include "pal.h" #include "pal_debug.h" @@ -32,71 +30,105 @@ #include "pal_linux.h" #include "pal_linux_defs.h" #include "pal_security.h" + typedef __kernel_pid_t pid_t; #include #include #include #include +#include #include #include -static int pipe_path(int pipeid, char* path, int len) { - /* use abstract UNIX sockets for pipes */ - memset(path, 0, len); - - if (pal_sec.pipe_prefix_id) - return snprintf(path + 1, len - 1, GRAPHENE_UNIX_PREFIX_FMT "/%08x", pal_sec.pipe_prefix_id, - pipeid); - else - return snprintf(path + 1, len - 1, "/graphene/%08x", pipeid); -} - static int pipe_addr(int pipeid, struct sockaddr_un* addr) { + /* use abstract UNIX sockets for pipes, with name format "@/graphene/12345678" */ addr->sun_family = AF_UNIX; - return pipe_path(pipeid, (char*)addr->sun_path, sizeof(addr->sun_path)); + memset(addr->sun_path, 0, sizeof(addr->sun_path)); + + /* for abstract sockets, first char is NUL */ + char* str = (char*)addr->sun_path + 1; + size_t size = sizeof(addr->sun_path) - 1; + + /* FIXME: Below naming scheme is slightly different from Linux-SGX, not important though */ + int ret; + if (pal_sec.pipe_prefix_id) { + ret = snprintf(str, size, GRAPHENE_UNIX_PREFIX_FMT "/%08x", + pal_sec.pipe_prefix_id, pipeid); + } else { + ret = snprintf(str, size, "/graphene/%08x", pipeid); + } + return ret >= 0 && (size_t)ret < size ? 0 : -EINVAL; } +/*! + * \brief Create a listening abstract UNIX socket as preparation for connecting two ends of a pipe. + * + * An abstract UNIX socket with name "@/graphene/" is opened for listening. A corresponding + * PAL handle with type `pipesrv` is created. This PAL handle typically serves only as an + * intermediate step to connect two ends of the pipe (`pipecli` and `pipe`). As soon as the other + * end of the pipe connects to this listening socket, a new accepted socket and the corresponding + * PAL handle are created, and this `pipesrv` handle can be closed. + * + * \param[out] handle PAL handle of type `pipesrv` with abstract UNIX socket opened for listening. + * \param[in] pipeid Integer uniquely identifying the pipe. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_listen(PAL_HANDLE* handle, PAL_NUM pipeid, int options) { - int ret, fd; - - fd = INLINE_SYSCALL(socket, 3, AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | options, 0); - if (IS_ERR(fd)) - return -PAL_ERROR_DENIED; + int ret; struct sockaddr_un addr; - - if ((ret = pipe_addr(pipeid, &addr)) < 0) - return ret; - - ret = INLINE_SYSCALL(bind, 3, fd, &addr, sizeof(addr.sun_path) - 1); - - if (IS_ERR(ret)) { - INLINE_SYSCALL(close, 1, fd); - - switch (ERRNO(ret)) { - case EINVAL: - return -PAL_ERROR_INVAL; - case EADDRINUSE: - return -PAL_ERROR_STREAMEXIST; - default: - return -PAL_ERROR_DENIED; - } - } - - ret = INLINE_SYSCALL(listen, 2, fd, 1); + ret = pipe_addr(pipeid, &addr); if (IS_ERR(ret)) return -PAL_ERROR_DENIED; + int nonblock = options & PAL_OPTION_NONBLOCK ? SOCK_NONBLOCK : 0; + + int fd = INLINE_SYSCALL(socket, 3, AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | nonblock, 0); + if (IS_ERR(fd)) + return unix_to_pal_error(ERRNO(fd)); + + ret = INLINE_SYSCALL(bind, 3, fd, &addr, sizeof(addr.sun_path) - 1); + if (IS_ERR(ret)) { + INLINE_SYSCALL(close, 1, fd); + return unix_to_pal_error(ERRNO(ret)); + } + + ret = INLINE_SYSCALL(listen, 2, fd, 1); + if (IS_ERR(ret)) { + INLINE_SYSCALL(close, 1, fd); + return unix_to_pal_error(ERRNO(ret)); + } + PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipe)); + if (!hdl) { + INLINE_SYSCALL(close, 1, fd); + return -PAL_ERROR_NOMEM; + } + SET_HANDLE_TYPE(hdl, pipesrv); - HANDLE_HDR(hdl)->flags |= RFD(0); - hdl->pipe.fd = fd; - hdl->pipe.pipeid = pipeid; - hdl->pipe.nonblocking = options & PAL_OPTION_NONBLOCK ? PAL_TRUE : PAL_FALSE; - *handle = hdl; + HANDLE_HDR(hdl)->flags |= RFD(0); /* cannot write to a listening socket */ + hdl->pipe.fd = fd; + hdl->pipe.pipeid = pipeid; + hdl->pipe.nonblocking = options & PAL_OPTION_NONBLOCK ? PAL_TRUE : PAL_FALSE; + + *handle = hdl; return 0; } +/*! + * \brief Accept the other end of the pipe and create PAL handle for our end of the pipe. + * + * Caller creates a `pipesrv` PAL handle with the underlying abstract UNIX socket opened for + * listening, and then calls this function to wait for the other end of the pipe to connect. + * When the connection request arrives, a new `pipecli` PAL handle is created with the + * corresponding underlying socket and is returned in `client`. This `pipecli` PAL handle denotes + * our end of the pipe. Typically, `pipesrv` handle is not needed after this and can be closed. + * + * \param[in] handle PAL handle of type `pipesrv` with abstract UNIX socket opened for listening. + * \param[out] client PAL handle of type `pipecli` connected to the other end of the pipe (`pipe`). + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_waitforclient(PAL_HANDLE handle, PAL_HANDLE* client) { if (!IS_HANDLE_TYPE(handle, pipesrv)) return -PAL_ERROR_NOTSERVER; @@ -105,87 +137,137 @@ static int pipe_waitforclient(PAL_HANDLE handle, PAL_HANDLE* client) { return -PAL_ERROR_DENIED; int newfd = INLINE_SYSCALL(accept4, 4, handle->pipe.fd, NULL, NULL, O_CLOEXEC); - if (IS_ERR(newfd)) - switch (ERRNO(newfd)) { - case EWOULDBLOCK: - return -PAL_ERROR_TRYAGAIN; - case ECONNABORTED: - return -PAL_ERROR_CONNFAILED; - default: - return -PAL_ERROR_DENIED; - } + return unix_to_pal_error(ERRNO(newfd)); PAL_HANDLE clnt = malloc(HANDLE_SIZE(pipe)); - SET_HANDLE_TYPE(clnt, pipecli); - HANDLE_HDR(clnt)->flags |= RFD(0) | WFD(0); - clnt->pipe.fd = newfd; - clnt->pipe.pipeid = handle->pipe.pipeid; - clnt->pipe.nonblocking = PAL_FALSE; - *client = clnt; - - return 0; -} - -static int pipe_connect(PAL_HANDLE* handle, PAL_NUM pipeid, int options) { - int ret, fd; - - fd = INLINE_SYSCALL(socket, 3, AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | options, 0); - - if (IS_ERR(fd)) - return -PAL_ERROR_DENIED; - - struct sockaddr_un addr; - - if ((ret = pipe_addr(pipeid, &addr)) < 0) - return ret; - - ret = INLINE_SYSCALL(connect, 3, fd, &addr, sizeof(addr.sun_path) - 1); - - if (IS_ERR(ret)) { - INLINE_SYSCALL(close, 1, fd); - switch (ERRNO(ret)) { - case ECONNREFUSED: - return -PAL_ERROR_STREAMNOTEXIST; - case EINTR: - return -PAL_ERROR_TRYAGAIN; - default: - return -PAL_ERROR_DENIED; - } + if (!clnt) { + INLINE_SYSCALL(close, 1, newfd); + return -PAL_ERROR_NOMEM; } - PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipe)); - SET_HANDLE_TYPE(hdl, pipe); - HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(0); - hdl->pipe.fd = fd; - hdl->pipe.pipeid = pipeid; - hdl->pipe.nonblocking = (options & PAL_OPTION_NONBLOCK) ? PAL_TRUE : PAL_FALSE; - *handle = hdl; + SET_HANDLE_TYPE(clnt, pipecli); + HANDLE_HDR(clnt)->flags |= RFD(0) | WFD(0); + clnt->pipe.fd = newfd; + clnt->pipe.nonblocking = PAL_FALSE; /* FIXME: must set nonblocking based on `handle` value */ + clnt->pipe.pipeid = handle->pipe.pipeid; + *client = clnt; return 0; } -static int pipe_private(PAL_HANDLE* handle, int options) { - int ret, fds[2]; +/*! + * \brief Connect to the other end of the pipe and create PAL handle for our end of the pipe. + * + * This function connects to the other end of the pipe, represented as an abstract UNIX socket + * "@/graphene/" opened for listening. When the connection succeeds, a new `pipe` PAL handle + * is created with the corresponding underlying socket and is returned in `handle`. The other end of + * the pipe is typically of type `pipecli`. + * + * \param[out] handle PAL handle of type `pipe` with abstract UNIX socket connected to another end. + * \param[in] pipeid Integer uniquely identifying the pipe. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ +static int pipe_connect(PAL_HANDLE* handle, PAL_NUM pipeid, int options) { + int ret; - ret = INLINE_SYSCALL(socketpair, 4, AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | options, 0, fds); + struct sockaddr_un addr; + ret = pipe_addr(pipeid, &addr); if (IS_ERR(ret)) return -PAL_ERROR_DENIED; - PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipeprv)); - SET_HANDLE_TYPE(hdl, pipeprv); - HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(1); - hdl->pipeprv.fds[0] = fds[0]; - hdl->pipeprv.fds[1] = fds[1]; - hdl->pipeprv.nonblocking = (options & PAL_OPTION_NONBLOCK) ? PAL_TRUE : PAL_FALSE; - *handle = hdl; + int nonblock = options & PAL_OPTION_NONBLOCK ? SOCK_NONBLOCK : 0; + + int fd = INLINE_SYSCALL(socket, 3, AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | nonblock, 0); + if (IS_ERR(fd)) + return -PAL_ERROR_DENIED; + + ret = INLINE_SYSCALL(connect, 3, fd, &addr, sizeof(addr.sun_path) - 1); + if (IS_ERR(ret)) { + INLINE_SYSCALL(close, 1, fd); + return unix_to_pal_error(ERRNO(ret)); + } + + PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipe)); + if (!hdl) { + INLINE_SYSCALL(close, 1, fd); + return -PAL_ERROR_NOMEM; + } + + SET_HANDLE_TYPE(hdl, pipe); + HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(0); + hdl->pipe.fd = fd; + hdl->pipe.pipeid = pipeid; + hdl->pipe.nonblocking = (options & PAL_OPTION_NONBLOCK) ? PAL_TRUE : PAL_FALSE; + + *handle = hdl; return 0; } -/* 'open' operation of pipe stream. For each pipe stream, it is - identified by a decimal number in URI. There could be two - types: pipe and pipe.srv. They behave pretty much the same, - except they are two ends of the pipe. */ +/*! + * \brief Create PAL handle with read and write ends of a pipe. + * + * This function creates a PAL handle of type `pipeprv` (anonymous pipe). In contrast to other types + * of pipes, `pipeprv` encapsulates both ends of the pipe, backed by a host-level socketpair. This + * type of pipe is typically reserved for internal PAL uses, not for LibOS emulation. + * + * \param[out] handle PAL handle of type `pipeprv` backed by a host-level socketpair. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ +static int pipe_private(PAL_HANDLE* handle, int options) { + int fds[2]; + + int nonblock = options & PAL_OPTION_NONBLOCK ? SOCK_NONBLOCK : 0; + + int ret = INLINE_SYSCALL(socketpair, 4, AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | nonblock, 0, fds); + if (IS_ERR(ret)) + return unix_to_pal_error(ERRNO(ret)); + + PAL_HANDLE hdl = malloc(HANDLE_SIZE(pipeprv)); + if (!hdl) { + INLINE_SYSCALL(close, 1, fds[0]); + INLINE_SYSCALL(close, 1, fds[1]); + return -PAL_ERROR_NOMEM; + } + + SET_HANDLE_TYPE(hdl, pipeprv); + HANDLE_HDR(hdl)->flags |= RFD(0) | WFD(1); /* first FD for reads, second FD for writes */ + hdl->pipeprv.fds[0] = fds[0]; + hdl->pipeprv.fds[1] = fds[1]; + hdl->pipeprv.nonblocking = (options & PAL_OPTION_NONBLOCK) ? PAL_TRUE : PAL_FALSE; + + *handle = hdl; + return 0; +} + +/*! + * \brief Create PAL handle of type `pipeprv`, `pipesrv`, or `pipe` depending on `type` and `uri`. + * + * Depending on the combination of `type` and `uri`, the following PAL handles are created: + * + * - `type` is URI_TYPE_PIPE and `url` is empty: create `pipeprv` handle (with two connected + * ends of an anonymous pipe). + * + * - `type` is URI_TYPE_PIPE_SRV: create `pipesrv` handle (intermediate listening socket) with + * name in the form of "@/graphene/" where pipeid is + * derived from `uri` via strtol(). Caller is expected to call + * pipe_waitforclient() afterwards. + * + * - `type` is URI_TYPE_PIPE: create `pipe` handle (connecting socket) with name in the form of + * "@/graphene/" where pipeid is derived from `uri` via + * strtol(). + * + * \param[out] handle Created PAL handle of type `pipeprv`, `pipesrv`, or `pipe`. + * \param[in] type Can be URI_TYPE_PIPE or URI_TYPE_PIPE_SRV. + * \param[in] uri Content is either NUL (for anonymous pipe) or an integer denoting pipeid. + * \param[in] access Not used. + * \param[in] share Not used. + * \param[in] create Not used. + * \param[in] options May contain PAL_OPTION_NONBLOCK. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_open(PAL_HANDLE* handle, const char* type, const char* uri, int access, int share, int create, int options) { if (!WITHIN_MASK(access, PAL_ACCESS_MASK) || !WITHIN_MASK(share, PAL_SHARE_MASK) || @@ -210,7 +292,15 @@ static int pipe_open(PAL_HANDLE* handle, const char* type, const char* uri, int return -PAL_ERROR_INVAL; } -/* 'read' operation of pipe stream. offset does not apply here. */ +/*! + * \brief Read from pipe (from read end in case of `pipeprv`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipecli`, or `pipe`. + * \param[in] offset Not used. + * \param[in] len Size of user-supplied buffer. + * \param[out] buffer User-supplied buffer to read data to. + * \return Number of bytes read on success, negative PAL error code otherwise. + */ static int64_t pipe_read(PAL_HANDLE handle, uint64_t offset, uint64_t len, void* buffer) { if (offset) return -PAL_ERROR_INVAL; @@ -219,13 +309,11 @@ static int64_t pipe_read(PAL_HANDLE handle, uint64_t offset, uint64_t len, void* !IS_HANDLE_TYPE(handle, pipe)) return -PAL_ERROR_NOTCONNECTION; - int fd = IS_HANDLE_TYPE(handle, pipeprv) ? handle->pipeprv.fds[0] : handle->pipe.fd; - int64_t bytes = 0; - - bytes = INLINE_SYSCALL(read, 3, fd, buffer, len); + int fd = IS_HANDLE_TYPE(handle, pipeprv) ? handle->pipeprv.fds[0] : handle->pipe.fd; + ssize_t bytes = INLINE_SYSCALL(read, 3, fd, buffer, len); if (IS_ERR(bytes)) - bytes = unix_to_pal_error(ERRNO(bytes)); + return unix_to_pal_error(ERRNO(bytes)); if (!bytes) return -PAL_ERROR_ENDOFSTREAM; @@ -233,7 +321,15 @@ static int64_t pipe_read(PAL_HANDLE handle, uint64_t offset, uint64_t len, void* return bytes; } -/* 'write' operation of pipe stream. offset does not apply here. */ +/*! + * \brief Write to pipe (to write end in case of `pipeprv`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipecli`, or `pipe`. + * \param[in] offset Not used. + * \param[in] len Size of user-supplied buffer. + * \param[in] buffer User-supplied buffer to write data from. + * \return Number of bytes written on success, negative PAL error code otherwise. + */ static int64_t pipe_write(PAL_HANDLE handle, uint64_t offset, size_t len, const void* buffer) { if (offset) return -PAL_ERROR_INVAL; @@ -242,17 +338,21 @@ static int64_t pipe_write(PAL_HANDLE handle, uint64_t offset, size_t len, const !IS_HANDLE_TYPE(handle, pipe)) return -PAL_ERROR_NOTCONNECTION; - int fd = IS_HANDLE_TYPE(handle, pipeprv) ? handle->pipeprv.fds[1] : handle->pipe.fd; - int64_t bytes = 0; + int fd = IS_HANDLE_TYPE(handle, pipeprv) ? handle->pipeprv.fds[1] : handle->pipe.fd; - bytes = INLINE_SYSCALL(write, 3, fd, buffer, len); + ssize_t bytes = INLINE_SYSCALL(write, 3, fd, buffer, len); if (IS_ERR(bytes)) - bytes = unix_to_pal_error(ERRNO(bytes)); + return unix_to_pal_error(ERRNO(bytes)); return bytes; } -/* 'close' operation of pipe stream. */ +/*! + * \brief Close pipe (both ends in case of `pipeprv`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_close(PAL_HANDLE handle) { if (IS_HANDLE_TYPE(handle, pipeprv)) { if (handle->pipeprv.fds[0] != PAL_IDX_POISON) { @@ -263,10 +363,7 @@ static int pipe_close(PAL_HANDLE handle) { INLINE_SYSCALL(close, 1, handle->pipeprv.fds[1]); handle->pipeprv.fds[1] = PAL_IDX_POISON; } - return 0; - } - - if (handle->pipe.fd != PAL_IDX_POISON) { + } else if (handle->pipe.fd != PAL_IDX_POISON) { INLINE_SYSCALL(close, 1, handle->pipe.fd); handle->pipe.fd = PAL_IDX_POISON; } @@ -274,40 +371,14 @@ static int pipe_close(PAL_HANDLE handle) { return 0; } -/* 'delete' operation of pipe stream. */ +/*! + * \brief Shut down pipe (one or both ends in case of `pipeprv` depending on `access`). + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[in] access May be 0, PAL_DELETE_RD, PAL_DELETE_WR. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_delete(PAL_HANDLE handle, int access) { - if (IS_HANDLE_TYPE(handle, pipeprv)) { - switch (access) { - case 0: - if (handle->pipeprv.fds[0] != PAL_IDX_POISON) { - INLINE_SYSCALL(close, 1, handle->pipeprv.fds[0]); - handle->pipeprv.fds[0] = PAL_IDX_POISON; - } - if (handle->pipeprv.fds[1] != PAL_IDX_POISON) { - INLINE_SYSCALL(close, 1, handle->pipeprv.fds[1]); - handle->pipeprv.fds[1] = PAL_IDX_POISON; - } - break; - case PAL_DELETE_RD: - if (handle->pipeprv.fds[0] != PAL_IDX_POISON) { - INLINE_SYSCALL(close, 1, handle->pipeprv.fds[0]); - handle->pipeprv.fds[0] = PAL_IDX_POISON; - } - break; - case PAL_DELETE_WR: - if (handle->pipeprv.fds[1] != PAL_IDX_POISON) { - INLINE_SYSCALL(close, 1, handle->pipeprv.fds[1]); - handle->pipeprv.fds[1] = PAL_IDX_POISON; - } - break; - default: - return -PAL_ERROR_INVAL; - } - } - - if (handle->pipe.fd == PAL_IDX_POISON) - return 0; - int shutdown; switch (access) { case 0: @@ -323,14 +394,36 @@ static int pipe_delete(PAL_HANDLE handle, int access) { return -PAL_ERROR_INVAL; } - INLINE_SYSCALL(shutdown, 2, handle->pipe.fd, shutdown); + if (IS_HANDLE_TYPE(handle, pipeprv)) { + /* pipeprv has two underlying FDs, shut down the requested one(s) */ + if (handle->pipeprv.fds[0] != PAL_IDX_POISON && + (shutdown == SHUT_RD || shutdown == SHUT_RDWR)) { + INLINE_SYSCALL(shutdown, 2, handle->pipeprv.fds[0], SHUT_RD); + } + + if (handle->pipeprv.fds[1] != PAL_IDX_POISON && + (shutdown == SHUT_WR || shutdown == SHUT_RDWR)) { + INLINE_SYSCALL(shutdown, 2, handle->pipeprv.fds[1], SHUT_WR); + } + } else { + /* other types of pipes have a single underlying FD, shut it down */ + if (handle->pipe.fd != PAL_IDX_POISON) { + INLINE_SYSCALL(shutdown, 2, handle->pipe.fd, shutdown); + } + } return 0; } +/*! + * \brief Retrieve attributes of PAL handle. + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[out] attr User-supplied buffer to store handle's current attributes. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { int ret; - int val; if (handle->pipe.fd == PAL_IDX_POISON) return -PAL_ERROR_BADHANDLE; @@ -343,6 +436,7 @@ static int pipe_attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { /* get number of bytes available for reading (doesn't make sense for "listening" pipes) */ attr->pending_size = 0; if (!IS_HANDLE_TYPE(handle, pipesrv)) { + int val; ret = INLINE_SYSCALL(ioctl, 3, handle->pipe.fd, FIONREAD, &val); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); @@ -383,19 +477,26 @@ static int pipe_attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { return 0; } +/*! + * \brief Set attributes of PAL handle. + * + * Currently only `nonblocking` attribute can be set. + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[in] attr User-supplied buffer with new handle's attributes. + * \return 0 on success, negative PAL error code otherwise. + */ static int pipe_attrsetbyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { if (handle->generic.fds[0] == PAL_IDX_POISON) return -PAL_ERROR_BADHANDLE; - int ret; PAL_BOL* nonblocking = (HANDLE_HDR(handle)->type == pal_type_pipeprv) ? &handle->pipeprv.nonblocking : &handle->pipe.nonblocking; if (attr->nonblocking != *nonblocking) { - ret = INLINE_SYSCALL(fcntl, 3, handle->generic.fds[0], F_SETFL, - attr->nonblocking ? O_NONBLOCK : 0); - + int ret = INLINE_SYSCALL(fcntl, 3, handle->generic.fds[0], F_SETFL, + attr->nonblocking ? O_NONBLOCK : 0); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); @@ -405,6 +506,16 @@ static int pipe_attrsetbyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { return 0; } +/*! + * \brief Retrieve full URI of PAL handle. + * + * Full URI is composed of the type and pipeid: ":". + * + * \param[in] handle PAL handle of type `pipeprv`, `pipesrv`, `pipecli`, or `pipe`. + * \param[out] buffer User-supplied buffer to write URI to. + * \param[in] count Size of the user-supplied buffer. + * \return Number of bytes written on success, negative PAL error code otherwise. + */ static int pipe_getname(PAL_HANDLE handle, char* buffer, size_t count) { size_t old_count = count; int ret;