aboutsummaryrefslogtreecommitdiffstats
path: root/servers-kernel.c
diff options
context:
space:
mode:
Diffstat (limited to 'servers-kernel.c')
-rw-r--r--servers-kernel.c363
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);
+ }
+}