diff options
Diffstat (limited to 'servers-kernel.c')
-rw-r--r-- | servers-kernel.c | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/servers-kernel.c b/servers-kernel.c new file mode 100644 index 0000000..fd60aef --- /dev/null +++ b/servers-kernel.c @@ -0,0 +1,363 @@ +/* See LICENSE file for copyright and license details. */ +#include "servers-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; + + if (sitename) { + name = memdup(sitename, strlen(sitename) + 1); + if (!name) + goto fail; + } else if ((name = libgamma_method_default_site(method))) { + name = memdup(name, strlen(name) + 1); + if (!name) + goto fail; + } + + if (name) { + switch (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) + (name ? strlen(name) : 0) + strlen(suffix); + if (!(rc = malloc(n))) + goto fail; + sprintf(rc, "%s/.coopgammad/~%s/%i%s%s%s", + rundir, username, method, name ? "." : "", name ? name : "", suffix); + free(name); + return rc; + +fail: + free(name); + 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(long long int)]; + int fd = -1, saved_errno, tries = 0; + char *content = NULL; + char *p; + pid_t pid = 0; + size_t n; +#if defined(__linux__) || 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) + goto fail; + close(fd); + fd = -1; + + if (!n) { + 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)(p - content) != n) + goto bad; + sprintf(temp, "%llu\n", (unsigned long long)pid); + if (strcmp(content, temp)) + goto bad; + free(content); + + /* Validate PID */ +#if defined(__linux__) || defined(HAVE_LINUX_PROCFS) + sprintf(temp, "/proc/%llu/environ", (unsigned long long int)pid); + fd = open(temp, O_RDONLY); + if (fd < 0) + return (errno == ENOENT || errno == EACCES) ? 1 : -1; + content = nread(fd, &n); + if (!content) + goto fail; + close(fd), fd = -1; + + for (end = &(p = content)[n]; p != end; p = &strchr(p, '\0')[1]) + if (!strcmp(p, token)) + return free(content), 0; + free(content); +#else + if (!kill(pid, 0) || errno == EINVAL) + return 0; +#endif + + return 1; + +bad: + fprintf(stderr, "%s: pid file contains 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 = -1, r, saved_errno; + char *p; + char *restrict token = NULL; + + /* Create token used to validate the service. */ + token = malloc(sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + strlen(pidpath)); + if (!token) + return -1; + sprintf(token, "COOPGAMMAD_PIDFILE_TOKEN=%s", pidpath); +#if !defined(USE_VALGRIND) + if (putenv(token)) + goto putenv_fail; + /* `token` must not be free! */ +#else + { + static char static_token[sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + PATH_MAX]; + if (strlen(pidpath) > PATH_MAX) + abort(); + strcpy(static_token, token); + if (putenv(static_token)) + goto fail; + } +#endif + + /* Create PID file's directory. */ + for (p = pidpath; *p == '/'; p++); + while ((p = strchr(p, '/'))) { + *p = '\0'; + if (mkdir(pidpath, 0755) < 0) { + if (errno != EEXIST) { + *p = '/'; + goto fail; + } + } + *p++ = '/'; + } + + /* Create PID file. */ +retry: + fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 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 defined(USE_VALGRIND) + free(token); +#endif + if (close(fd) < 0) + if (errno != EINTR) + return -1; + return 0; +#if !defined(USE_VALGRIND) +putenv_fail: + free(token); +#endif +fail: +#if defined(USE_VALGRIND) + free(token); +#endif + if (fd >= 0) { + 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); + socketfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (socketfd < 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); + } +} |