| /* |
| * Copyright 2021 Google LLC |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| #include "render_server.h" |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <poll.h> |
| #include <unistd.h> |
| |
| #include "render_client.h" |
| #include "render_worker.h" |
| |
| #define RENDER_SERVER_MAX_WORKER_COUNT 256 |
| |
| enum render_server_poll_type { |
| RENDER_SERVER_POLL_SOCKET = 0, |
| RENDER_SERVER_POLL_SIGCHLD /* optional */, |
| RENDER_SERVER_POLL_COUNT, |
| }; |
| |
| static int |
| render_server_init_poll_fds(struct render_server *srv, |
| struct pollfd poll_fds[static RENDER_SERVER_POLL_COUNT]) |
| { |
| const int socket_fd = srv->client->socket.fd; |
| const int sigchld_fd = render_worker_jail_get_sigchld_fd(srv->worker_jail); |
| |
| poll_fds[RENDER_SERVER_POLL_SOCKET] = (const struct pollfd){ |
| .fd = socket_fd, |
| .events = POLLIN, |
| }; |
| poll_fds[RENDER_SERVER_POLL_SIGCHLD] = (const struct pollfd){ |
| .fd = sigchld_fd, |
| .events = POLLIN, |
| }; |
| |
| return sigchld_fd >= 0 ? 2 : 1; |
| } |
| |
| static bool |
| render_server_poll(UNUSED struct render_server *srv, |
| struct pollfd *poll_fds, |
| int poll_fd_count) |
| { |
| int ret; |
| do { |
| ret = poll(poll_fds, poll_fd_count, -1); |
| } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); |
| |
| if (ret <= 0) { |
| render_log("failed to poll in the main loop"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| render_server_run(struct render_server *srv) |
| { |
| struct render_client *client = srv->client; |
| |
| struct pollfd poll_fds[RENDER_SERVER_POLL_COUNT]; |
| const int poll_fd_count = render_server_init_poll_fds(srv, poll_fds); |
| |
| while (srv->state == RENDER_SERVER_STATE_RUN) { |
| if (!render_server_poll(srv, poll_fds, poll_fd_count)) |
| return false; |
| |
| if (poll_fds[RENDER_SERVER_POLL_SOCKET].revents) { |
| if (!render_client_dispatch(client)) |
| return false; |
| } |
| |
| if (poll_fds[RENDER_SERVER_POLL_SIGCHLD].revents) { |
| if (!render_worker_jail_reap_workers(srv->worker_jail)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static void |
| render_server_fini(struct render_server *srv) |
| { |
| if (srv->client) |
| render_client_destroy(srv->client); |
| |
| if (srv->worker_jail) |
| render_worker_jail_destroy(srv->worker_jail); |
| |
| if (srv->client_fd >= 0) |
| close(srv->client_fd); |
| } |
| |
| static bool |
| render_server_parse_options(struct render_server *srv, int argc, char **argv) |
| { |
| enum { |
| OPT_SOCKET_FD = 'a', |
| OPT_WORKER_SECCOMP_BPF, |
| OPT_WORKER_SECCOMP_MINIJAIL_POLICY, |
| OPT_WORKER_SECCOMP_MINIJAIL_LOG, |
| OPT_COUNT, |
| }; |
| static const struct option options[] = { |
| { "socket-fd", required_argument, NULL, OPT_SOCKET_FD }, |
| { "worker-seccomp-bpf", required_argument, NULL, OPT_WORKER_SECCOMP_BPF }, |
| { "worker-seccomp-minijail-policy", required_argument, NULL, |
| OPT_WORKER_SECCOMP_MINIJAIL_POLICY }, |
| { "worker-seccomp-minijail-log", no_argument, NULL, |
| OPT_WORKER_SECCOMP_MINIJAIL_LOG }, |
| { NULL, 0, NULL, 0 } |
| }; |
| static_assert(OPT_COUNT <= 'z', ""); |
| |
| while (true) { |
| const int ret = getopt_long(argc, argv, "", options, NULL); |
| if (ret == -1) |
| break; |
| |
| switch (ret) { |
| case OPT_SOCKET_FD: |
| srv->client_fd = atoi(optarg); |
| break; |
| case OPT_WORKER_SECCOMP_BPF: |
| srv->worker_seccomp_bpf = optarg; |
| break; |
| case OPT_WORKER_SECCOMP_MINIJAIL_POLICY: |
| srv->worker_seccomp_minijail_policy = optarg; |
| break; |
| case OPT_WORKER_SECCOMP_MINIJAIL_LOG: |
| srv->worker_seccomp_minijail_log = true; |
| break; |
| default: |
| render_log("unknown option specified"); |
| return false; |
| break; |
| } |
| } |
| |
| if (optind < argc) { |
| render_log("non-option arguments specified"); |
| return false; |
| } |
| |
| if (srv->client_fd < 0 || !render_socket_is_seqpacket(srv->client_fd)) { |
| render_log("no valid client fd specified"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| render_server_init(struct render_server *srv, |
| int argc, |
| char **argv, |
| struct render_context_args *ctx_args) |
| { |
| memset(srv, 0, sizeof(*srv)); |
| srv->state = RENDER_SERVER_STATE_RUN; |
| srv->context_args = ctx_args; |
| srv->client_fd = -1; |
| |
| if (!render_server_parse_options(srv, argc, argv)) |
| return false; |
| |
| enum render_worker_jail_seccomp_filter seccomp_filter = |
| RENDER_WORKER_JAIL_SECCOMP_NONE; |
| const char *seccomp_path = NULL; |
| if (srv->worker_seccomp_minijail_log && srv->worker_seccomp_minijail_policy) { |
| seccomp_filter = RENDER_WORKER_JAIL_SECCOMP_MINIJAIL_POLICY_LOG; |
| seccomp_path = srv->worker_seccomp_minijail_policy; |
| } else if (srv->worker_seccomp_bpf) { |
| seccomp_filter = RENDER_WORKER_JAIL_SECCOMP_BPF; |
| seccomp_path = srv->worker_seccomp_bpf; |
| } else if (srv->worker_seccomp_minijail_policy) { |
| seccomp_filter = RENDER_WORKER_JAIL_SECCOMP_MINIJAIL_POLICY; |
| seccomp_path = srv->worker_seccomp_minijail_policy; |
| } |
| |
| srv->worker_jail = render_worker_jail_create(RENDER_SERVER_MAX_WORKER_COUNT, |
| seccomp_filter, seccomp_path); |
| if (!srv->worker_jail) { |
| render_log("failed to create worker jail"); |
| goto fail; |
| } |
| |
| srv->client = render_client_create(srv, srv->client_fd); |
| if (!srv->client) { |
| render_log("failed to create client"); |
| goto fail; |
| } |
| /* ownership transferred */ |
| srv->client_fd = -1; |
| |
| return true; |
| |
| fail: |
| render_server_fini(srv); |
| return false; |
| } |
| |
| bool |
| render_server_main(int argc, char **argv, struct render_context_args *ctx_args) |
| { |
| struct render_server srv; |
| if (!render_server_init(&srv, argc, argv, ctx_args)) |
| return false; |
| |
| const bool ok = render_server_run(&srv); |
| render_server_fini(&srv); |
| |
| return ok; |
| } |