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