diff options
Diffstat (limited to '')
-rw-r--r-- | src/kernel.c | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/src/kernel.c b/src/kernel.c new file mode 100644 index 0000000..7030a99 --- /dev/null +++ b/src/kernel.c @@ -0,0 +1,351 @@ +/** + * coopgammad -- Cooperative gamma server + * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "kernel.h" +#include "state.h" +#include "util.h" + +#include <libgamma.h> + +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <errno.h> +#include <fcntl.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + + +/** + * Get the pathname of the runtime file + * + * @param suffix The suffix for the file + * @return The pathname of the file, `NULL` on error + */ +GCC_ONLY(__attribute__((malloc, nonnull))) +static char* get_pathname(const char* restrict suffix) +{ + const char* restrict rundir = getenv("XDG_RUNTIME_DIR"); + const char* restrict username = ""; + char* name = NULL; + char* p; + char* restrict rc; + struct passwd* restrict pw; + size_t n; + int saved_errno; + + if (site.site) + { + name = memdup(site.site, strlen(site.site) + 1); + if (name == NULL) + goto fail; + } + else if ((name = libgamma_method_default_site(site.method))) + { + name = memdup(name, strlen(name) + 1); + if (name == NULL) + goto fail; + } + + if (name != NULL) + switch (site.method) + { + case LIBGAMMA_METHOD_X_RANDR: + case LIBGAMMA_METHOD_X_VIDMODE: + if ((p = strrchr(name, ':'))) + if ((p = strchr(p, '.'))) + *p = '\0'; + break; + default: + break; + } + + if (!rundir || !*rundir) + rundir = "/tmp"; + + if ((pw = getpwuid(getuid()))) + username = pw->pw_name ? pw->pw_name : ""; + + n = sizeof("/.coopgammad/~/.") + 3 * sizeof(int); + n += strlen(rundir) + strlen(username) + strlen(name) + strlen(suffix); + if (!(rc = malloc(n))) + goto fail; + sprintf(rc, "%s/.coopgammad/~%s/%i%s%s%s", + rundir, username, site.method, name ? "." : "", name ? name : "", suffix); + return rc; + + fail: + saved_errno = errno; + free(name); + errno = saved_errno; + return NULL; +} + + +/** + * Get the pathname of the socket + * + * @return The pathname of the socket, `NULL` on error + */ +char* get_socket_pathname(void) +{ + return get_pathname(".socket"); +} + + +/** + * Get the pathname of the PID file + * + * @return The pathname of the PID file, `NULL` on error + */ +char* get_pidfile_pathname(void) +{ + return get_pathname(".pid"); +} + + +/** + * Get the pathname of the state file + * + * @return The pathname of the state file, `NULL` on error + */ +char* get_state_pathname(void) +{ + return get_pathname(".state"); +} + + +/** + * Check whether a PID file is outdated + * + * @param pidpath The PID file + * @param token An environment variable (including both key and value) + * that must exist in the process if it is a coopgammad process + * @return -1: An error occurred + * 0: The service is already running + * 1: The PID file is outdated + */ +GCC_ONLY(__attribute__((nonnull))) +static int is_pidfile_reusable(const char* restrict pidpath, const char* restrict token) +{ + /* PORTERS: /proc/$PID/environ is Linux specific */ + + char temp[sizeof("/proc//environ") + 3 * sizeof(pid_t)]; + int fd = -1, saved_errno, tries = 0; + char* content = NULL; + char* p; + pid_t pid = 0; + size_t n; +#if defined(HAVE_LINUX_PROCFS) + char* end; +#else + (void) token; +#endif + + /* Get PID */ + retry: + fd = open(pidpath, O_RDONLY); + if (fd < 0) + return -1; + content = nread(fd, &n); + if (content == NULL) + goto fail; + close(fd), fd = -1; + + if (n == 0) + { + if (++tries > 1) + goto bad; + msleep(100); /* 1 tenth of a second */ + goto retry; + } + + if (('0' > content[0]) || (content[0] > '9')) + goto bad; + if ((content[0] == '0') && ('0' <= content[1]) && (content[1] <= '9')) + goto bad; + for (p = content; *p; p++) + if (('0' <= *p) && (*p <= '9')) + pid = pid * 10 + (*p & 15); + else + break; + if (*p++ != '\n') + goto bad; + if (*p) + goto bad; + if ((size_t)(content - p) != n) + goto bad; + sprintf(temp, "%llu", (unsigned long long)pid); + if (strcmp(content, temp)) + goto bad; + + /* Validate PID */ +#if defined(HAVE_LINUX_PROCFS) + sprintf(temp, "/proc/%llu/environ", (unsigned long long)pid); + fd = open(temp, O_RDONLY); + if (fd < 0) + return ((errno == ENOENT) || (errno == EACCES)) ? 1 : -1; + content = nread(fd, &n); + if (content == NULL) + goto fail; + close(fd), fd = -1; + + for (end = (p = content) + n; p != end; p = strchr(p, '\0') + 1) + if (!strcmp(p, token)) + return 0; +#else + if ((kill(pid, 0) == 0) || (errno == EINVAL)) + return 0; +#endif + + return 1; + bad: + fprintf(stderr, "%s: pid file contain invalid content: %s\n", argv0, pidpath); + errno = 0; + return -1; + fail: + saved_errno = errno; + free(content); + if (fd >= 0) + close(fd); + errno = saved_errno; + return -1; +} + + +/** + * Create PID file + * + * @param pidpath The pathname of the PID file + * @return Zero on success, -1 on error, + * -2 if the service is already running + */ +int create_pidfile(char* pidpath) +{ + int fd, r, saved_errno; + char* p; + char* restrict token; + + /* Create token used to validate the service. */ + token = malloc(sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + strlen(pidpath)); + if (token == NULL) + return -1; + sprintf(token, "COOPGAMMAD_PIDFILE_TOKEN=%s", pidpath); + if (putenv(token)) + return -1; + + /* Create PID file's directory. */ + for (p = pidpath; *p == '/'; p++); + while ((p = strchr(p, '/'))) + { + *p = '\0'; + if (mkdir(pidpath, 0644) < 0) + if (errno != EEXIST) + return -1; + *p++ = '/'; + } + + /* Create PID file. */ + retry: + fd = open(pidpath, O_CREAT | O_EXCL, 0644); + if (fd < 0) + { + if (errno == EINTR) + goto retry; + if (errno != EEXIST) + return -1; + r = is_pidfile_reusable(pidpath, token); + if (r > 0) + { + unlink(pidpath); + goto retry; + } + else if (r < 0) + goto fail; + fprintf(stderr, "%s: service is already running\n", argv0); + errno = 0; + return -2; + } + + /* Write PID to PID file. */ + if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0) + goto fail; + + /* Done */ + if (close(fd) < 0) + if (errno != EINTR) + return -1; + return 0; + fail: + saved_errno = errno; + close(fd); + unlink(pidpath); + errno = saved_errno; + return -1; +} + + +/** + * Create socket and start listening + * + * @param socketpath The pathname of the socket + * @return Zero on success, -1 on error + */ +int create_socket(const char* socketpath) +{ + struct sockaddr_un address; + + address.sun_family = AF_UNIX; + if (strlen(socketpath) >= sizeof(address.sun_path)) + { + errno = ENAMETOOLONG; + return -1; + } + strcpy(address.sun_path, socketpath); + unlink(socketpath); + if ((socketfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + return -1; + if (fchmod(socketfd, S_IRWXU) < 0) + return -1; + if (bind(socketfd, (struct sockaddr*)(&address), (socklen_t)sizeof(address)) < 0) + return -1; + if (listen(socketfd, SOMAXCONN) < 0) + return -1; + + return 0; +} + + +/** + * Close and unlink the socket + * + * @param socketpath The pathname of the socket + */ +void close_socket(const char* socketpath) +{ + if (socketfd >= 0) + { + shutdown(socketfd, SHUT_RDWR); + close(socketfd); + unlink(socketpath); + } +} + |