/* See LICENSE file for copyright and license details. */ #include "servers-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 = ""; const char *const_name; 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 ((const_name = libgamma_method_default_site(method))) { name = memdup(const_name, strlen(const_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 int)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); } }