/** * 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 . */ #include "kernel.h" #include "../state.h" #include "../util.h" #include #include #include #include #include #include #include #include #include #include #include /** * 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); free(name); 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(long long int)]; 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)(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(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 free(content), 0; free(content); #else if ((kill(pid, 0) == 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 == NULL) 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: saved_errno = errno; free(token); errno = saved_errno; #endif fail: saved_errno = errno; #if defined(USE_VALGRIND) free(token); #endif if (fd >= 0) { 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); } }