aboutsummaryrefslogtreecommitdiffstats
path: root/src/servers
diff options
context:
space:
mode:
Diffstat (limited to 'src/servers')
-rw-r--r--src/servers/coopgamma.c601
-rw-r--r--src/servers/coopgamma.h101
-rw-r--r--src/servers/crtc.c129
-rw-r--r--src/servers/crtc.h67
-rw-r--r--src/servers/gamma.c279
-rw-r--r--src/servers/gamma.h94
-rw-r--r--src/servers/kernel.c351
-rw-r--r--src/servers/kernel.h85
8 files changed, 1707 insertions, 0 deletions
diff --git a/src/servers/coopgamma.c b/src/servers/coopgamma.c
new file mode 100644
index 0000000..7f8b815
--- /dev/null
+++ b/src/servers/coopgamma.c
@@ -0,0 +1,601 @@
+/**
+ * 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 "coopgamma.h"
+#include "gamma.h"
+#include "../state.h"
+#include "../communication.h"
+#include "../util.h"
+#include "../types/output.h"
+
+#include <libclut.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+
+/**
+ * Apply a filter on top of another filter
+ *
+ * @param dest The output for the resulting ramp-trio, must be initialised
+ * @param application The red, green and blue ramps, as one single raw array,
+ * of the filter that should be applied
+ * @param depth -1: `float` stops
+ * -2: `double` stops
+ * Other: the number of bits of each (integral) stop
+ * @param base The CLUT on top of which the new filter should be applied,
+ * this can be the same pointer as `dest`
+ */
+static void apply_filter(union gamma_ramps* restrict dest, void* restrict application,
+ int depth, union gamma_ramps* restrict base)
+{
+ union gamma_ramps app;
+ size_t bytedepth;
+ size_t red_width, green_width, blue_width;
+
+ if (depth == -1)
+ bytedepth = sizeof(float);
+ else if (depth == -2)
+ bytedepth = sizeof(double);
+ else
+ bytedepth = (size_t)depth / 8;
+
+ red_width = (app.u8.red_size = base->u8.red_size) * bytedepth;
+ green_width = (app.u8.green_size = base->u8.green_size) * bytedepth;
+ blue_width = (app.u8.blue_size = base->u8.blue_size) * bytedepth;
+
+ app.u8.red = application;
+ app.u8.green = app.u8.red + red_width;
+ app.u8.blue = app.u8.green + green_width;
+
+ if (dest != base)
+ {
+ memcpy(dest->u8.red, base->u8.red, red_width);
+ memcpy(dest->u8.green, base->u8.green, green_width);
+ memcpy(dest->u8.blue, base->u8.blue, blue_width);
+ }
+
+ switch (depth)
+ {
+ case 8:
+ libclut_apply(&(dest->u8), UINT8_MAX, uint8_t, &(app.u8), UINT8_MAX, uint8_t, 1, 1, 1);
+ break;
+ case 16:
+ libclut_apply(&(dest->u16), UINT16_MAX, uint16_t, &(app.u16), UINT16_MAX, uint16_t, 1, 1, 1);
+ break;
+ case 32:
+ libclut_apply(&(dest->u32), UINT32_MAX, uint32_t, &(app.u32), UINT32_MAX, uint32_t, 1, 1, 1);
+ break;
+ case 64:
+ libclut_apply(&(dest->u64), UINT64_MAX, uint64_t, &(app.u64), UINT64_MAX, uint64_t, 1, 1, 1);
+ break;
+ case -1:
+ libclut_apply(&(dest->f), 1.0f, float, &(app.d), 1.0f, float, 1, 1, 1);
+ break;
+ case -2:
+ libclut_apply(&(dest->d), (double)1, double, &(app.f), (double)1, double, 1, 1, 1);
+ break;
+ default:
+ abort();
+ }
+}
+
+
+
+/**
+ * Remove a filter from an output
+ *
+ * @param out The output
+ * @param filter The filter
+ * @return The index of the filter, `out->table_size` if not found
+ */
+static ssize_t remove_filter(struct output* restrict out, struct filter* restrict filter)
+{
+ size_t i, n = out->table_size;
+
+ for (i = 0; i < n; i++)
+ if (!strcmp(filter->class, out->table_filters[i].class))
+ break;
+
+ if (i == out->table_size)
+ {
+ fprintf(stderr, "%s: ignoring attempt to removing non-existing filter on CRTC %s: %s",
+ argv0, out->name, filter->class);
+ return (ssize_t)(out->table_size);
+ }
+
+ filter_destroy(out->table_filters + i);
+ libgamma_gamma_ramps8_destroy(&(out->table_sums[i].u8));
+
+ n = n - i - 1;
+ memmove(out->table_filters + i, out->table_filters + i + 1, n * sizeof(*(out->table_filters)));
+ memmove(out->table_sums + i, out->table_sums + i + 1, n * sizeof(*(out->table_sums)));
+ out->table_size--;
+
+ return (ssize_t)i;
+}
+
+
+/**
+ * Add a filter to an output
+ *
+ * @param out The output
+ * @param filter The filter
+ * @return The index given to the filter, -1 on error
+ */
+static ssize_t add_filter(struct output* restrict out, struct filter* restrict filter)
+{
+ size_t i, n = out->table_size;
+ int r = -1;
+
+ /* Remove? */
+ if (filter->lifespan == LIFESPAN_REMOVE)
+ return remove_filter(out, filter);
+
+ /* Update? */
+ for (i = 0; i < n; i++)
+ if (!strcmp(filter->class, out->table_filters[i].class))
+ break;
+ if (i != n)
+ {
+ filter_destroy(out->table_filters + i);
+ out->table_filters[i] = *filter;
+ return (ssize_t)i;
+ }
+
+ /* Add! */
+ for (i = 0; i < n; i++)
+ if (filter->priority > out->table_filters[i].priority)
+ break;
+
+ if (n == out->table_alloc)
+ {
+ void* new;
+
+ new = realloc(out->table_filters, (n + 10) * sizeof(*(out->table_filters)));
+ if (new == NULL)
+ return -1;
+ out->table_filters = new;
+
+ new = realloc(out->table_sums, (n + 10) * sizeof(*(out->table_sums)));
+ if (new == NULL)
+ return -1;
+ out->table_sums = new;
+
+ out->table_alloc += 10;
+ }
+
+ memmove(out->table_filters + i + 1, out->table_filters + i, (n - i) * sizeof(*(out->table_filters)));
+ memmove(out->table_sums + i + 1, out->table_sums + i, (n - i) * sizeof(*(out->table_sums)));
+ out->table_size++;
+
+ COPY_RAMP_SIZES(&(out->table_sums[i].u8), out);
+ switch (out->depth)
+ {
+ case 8: r = libgamma_gamma_ramps8_initialise(&(out->table_sums[i].u8)); break;
+ case 16: r = libgamma_gamma_ramps16_initialise(&(out->table_sums[i].u16)); break;
+ case 32: r = libgamma_gamma_ramps32_initialise(&(out->table_sums[i].u32)); break;
+ case 64: r = libgamma_gamma_ramps64_initialise(&(out->table_sums[i].u64)); break;
+ case -1: r = libgamma_gamma_rampsf_initialise(&(out->table_sums[i].f)); break;
+ case -2: r = libgamma_gamma_rampsd_initialise(&(out->table_sums[i].d)); break;
+ default:
+ abort();
+ }
+ if (r < 0)
+ return -1;
+
+ out->table_filters[i] = *filter;
+
+ return (ssize_t)i;
+}
+
+
+/**
+ * Make identity mapping ramps
+ *
+ * @param ramps Output parameter for the ramps
+ * @param output The output for which the ramps shall be configured
+ * @return Zero on success, -1 on error
+ */
+static int make_plain_ramps(union gamma_ramps* restrict ramps, struct output* restrict output)
+{
+ COPY_RAMP_SIZES(&(ramps->u8), output);
+ switch (output->depth)
+ {
+ case 8:
+ if (libgamma_gamma_ramps8_initialise(&(ramps->u8)))
+ return -1;
+ libclut_start_over(&(ramps->u8), UINT8_MAX, uint8_t, 1, 1, 1);
+ break;
+ case 16:
+ if (libgamma_gamma_ramps16_initialise(&(ramps->u16)))
+ return -1;
+ libclut_start_over(&(ramps->u16), UINT16_MAX, uint16_t, 1, 1, 1);
+ break;
+ case 32:
+ if (libgamma_gamma_ramps32_initialise(&(ramps->u32)))
+ return -1;
+ libclut_start_over(&(ramps->u32), UINT32_MAX, uint32_t, 1, 1, 1);
+ break;
+ case 64:
+ if (libgamma_gamma_ramps64_initialise(&(ramps->u64)))
+ return -1;
+ libclut_start_over(&(ramps->u64), UINT64_MAX, uint64_t, 1, 1, 1);
+ break;
+ case -1:
+ if (libgamma_gamma_rampsf_initialise(&(ramps->f)))
+ return -1;
+ libclut_start_over(&(ramps->f), 1.0f, float, 1, 1, 1);
+ break;
+ case -2:
+ if (libgamma_gamma_rampsd_initialise(&(ramps->d)))
+ return -1;
+ libclut_start_over(&(ramps->d), (double)1, double, 1, 1, 1);
+ break;
+ default:
+ abort();
+ }
+ return 0;
+}
+
+
+
+/**
+ * Handle a closed connection
+ *
+ * @param client The file descriptor for the client
+ * @return Zero on success, -1 on error
+ */
+int connection_closed(int client)
+{
+ size_t i, j, k;
+ int remove;
+
+ for (i = 0; i < outputs_n; i++)
+ {
+ struct output* output = outputs + i;
+ ssize_t updated = -1;
+ for (j = k = 0; j < output->table_size; j += !remove, k++)
+ {
+ remove = output->table_filters[j].client == client;
+ remove = remove && (output->table_filters[j].lifespan == LIFESPAN_UNTIL_DEATH);
+ if (remove)
+ {
+ filter_destroy(output->table_filters + j);
+ libgamma_gamma_ramps8_destroy(&(output->table_sums[j].u8));
+ output->table_size -= 1;
+ if (updated == -1)
+ updated = (ssize_t)j;
+ }
+ output->table_filters[j] = output->table_filters[k];
+ output->table_sums[j] = output->table_sums[k];
+ }
+ if (updated >= 0)
+ if (flush_filters(output, (size_t)updated) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Handle a ‘Command: get-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @param coalesce The value of the ‘Coalesce’ header
+ * @param high_priority The value of the ‘High priority’ header
+ * @param low_priority The value of the ‘Low priority’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int handle_get_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
+ const char* restrict coalesce, const char* restrict high_priority,
+ const char* restrict low_priority)
+{
+ struct output* restrict output;
+ int64_t high, low;
+ int coal;
+ char* restrict buf;
+ size_t start, end, len, n, i;
+ char depth[3];
+ char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)];
+
+ if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted");
+ if (coalesce == NULL) return send_error("protocol error: 'Coalesce' header omitted");
+ if (high_priority == NULL) return send_error("protocol error: 'High priority' header omitted");
+ if (low_priority == NULL) return send_error("protocol error: 'Low priority' header omitted");
+
+ high = (int64_t)atoll(high_priority);
+ low = (int64_t)atoll(low_priority);
+
+ if (!strcmp(coalesce, "yes"))
+ coal = 1;
+ else if (!strcmp(coalesce, "no"))
+ coal = 0;
+ else
+ return send_error("protocol error: recognised value for 'Coalesce' header");
+
+ output = output_find_by_name(crtc, outputs, outputs_n);
+ if (output == NULL)
+ return send_error("selected CRTC does not exist");
+ else if (output->supported == LIBGAMMA_NO)
+ return send_error("selected CRTC does not support gamma adjustments");
+
+ for (start = 0; start < output->table_size; start++)
+ if (output->table_filters[start].priority <= high)
+ break;
+
+ for (end = output->table_size; end > 0; end--)
+ if (output->table_filters[end - 1].priority >= low)
+ break;
+
+ switch (output->depth)
+ {
+ case -2: strcpy(depth, "d"); break;
+ case -1: strcpy(depth, "f"); break;
+ default:
+ sprintf(depth, "%i", output->depth);
+ break;
+ }
+
+ if (coal)
+ {
+ *tables = '\0';
+ n = output->ramps_size;
+ }
+ else
+ {
+ sprintf(tables, "Tables: %zu\n", end - start);
+ n = (sizeof(int64_t) + output->ramps_size) * (end - start);
+ for (i = start; i < end; i++)
+ n += strlen(output->table_filters[i].class) + 1;
+ }
+
+ MAKE_MESSAGE(&buf, &n, 0,
+ "In response to: %s\n"
+ "Depth: %s\n"
+ "Red size: %zu\n"
+ "Green size: %zu\n"
+ "Blue size: %zu\n"
+ "%s"
+ "Length: %zu\n"
+ "\n",
+ message_id, depth, output->red_size, output->green_size,
+ output->blue_size, tables, n);
+
+ if (coal)
+ {
+ if (start == 0)
+ memcpy(buf + n, output->table_sums[end].u8.red, output->ramps_size);
+ else
+ {
+ union gamma_ramps ramps;
+ if (make_plain_ramps(&ramps, output))
+ {
+ int saved_errno = errno;
+ free(buf);
+ errno = saved_errno;
+ return -1;
+ }
+ for (i = start; i < end; i++)
+ apply_filter(&ramps, output->table_filters[i].ramps, output->depth, &ramps);
+ memcpy(buf + n, ramps.u8.red, output->ramps_size);
+ libgamma_gamma_ramps8_destroy(&(ramps.u8));
+ }
+ n += output->ramps_size;
+ }
+ else
+ for (i = start; i < end; i++)
+ {
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+ *(int64_t*)(buf + n) = output->table_filters[i].priority;
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+ n += sizeof(int64_t);
+ len = strlen(output->table_filters[i].class) + 1;
+ memcpy(buf + n, output->table_filters[i].class, len);
+ n += len;
+ memcpy(buf + n, output->table_filters[i].ramps, output->ramps_size);
+ n += output->ramps_size;
+ }
+
+ return send_message(conn, buf, n);
+}
+
+
+/**
+ * Handle a ‘Command: set-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @param priority The value of the ‘Priority’ header
+ * @param class The value of the ‘Class’ header
+ * @param lifespan The value of the ‘Lifespan’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int handle_set_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
+ const char* restrict priority, const char* restrict class, const char* restrict lifespan)
+{
+ struct message* restrict msg = inbound + conn;
+ struct output* restrict output = NULL;
+ struct filter filter;
+ char* restrict p;
+ char* restrict q;
+ int saved_errno;
+ ssize_t r;
+
+ if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted");
+ if (class == NULL) return send_error("protocol error: 'Class' header omitted");
+ if (lifespan == NULL) return send_error("protocol error: 'Lifespan' header omitted");
+
+ filter.client = connections[conn];
+ filter.priority = priority == NULL ? 0 : (int64_t)atoll(priority);
+ filter.ramps = NULL;
+
+ output = output_find_by_name(crtc, outputs, outputs_n);
+ if (output == NULL)
+ return send_error("CRTC does not exists");
+
+ p = strstr(class, "::");
+ if ((p == NULL) || (p == class))
+ return send_error("protocol error: malformatted value for 'Class' header");
+ q = strstr(p + 2, "::");
+ if ((q == NULL) || (q == p))
+ return send_error("protocol error: malformatted value for 'Class' header");
+
+ if (!strcmp(lifespan, "until-removal"))
+ filter.lifespan = LIFESPAN_UNTIL_REMOVAL;
+ else if (!strcmp(lifespan, "until-death"))
+ filter.lifespan = LIFESPAN_UNTIL_DEATH;
+ else if (!strcmp(lifespan, "remove"))
+ filter.lifespan = LIFESPAN_REMOVE;
+ else
+ return send_error("protocol error: recognised value for 'Lifespan' header");
+
+ if (filter.lifespan == LIFESPAN_REMOVE)
+ {
+ if (msg->payload_size)
+ fprintf(stderr, "%s: ignoring superfluous payload on Command: set-gamma message with "
+ "Lifespan: remove\n", argv0);
+ if (priority != NULL)
+ fprintf(stderr, "%s: ignoring superfluous Priority header on Command: set-gamma message with "
+ "Lifespan: remove\n", argv0);
+ }
+ else if (msg->payload_size != output->ramps_size)
+ return send_error("invalid payload: size of message payload does matched the expectancy");
+ else if (priority == NULL)
+ return send_error("protocol error: 'Priority' header omitted");
+
+ filter.class = memdup(class, strlen(class) + 1);
+ if (filter.class == NULL)
+ goto fail;
+
+ if (filter.lifespan != LIFESPAN_REMOVE)
+ {
+ filter.ramps = memdup(msg->payload, msg->payload_size);
+ if (filter.ramps == NULL)
+ goto fail;
+ }
+
+ if ((r = add_filter(output, &filter)) < 0)
+ goto fail;
+ filter.class = NULL;
+ filter.ramps = NULL;
+ if (flush_filters(output, (size_t)r))
+ goto fail;
+
+ return send_errno(0);
+
+ fail:
+ saved_errno = errno;
+ send_errno(saved_errno);
+ free(filter.class);
+ free(filter.ramps);
+ errno = saved_errno;
+ return -1;
+}
+
+
+
+/**
+ * Recalculate the resulting gamma and
+ * update push the new gamma ramps to the CRTC
+ *
+ * @param output The output
+ * @param first_updated The index of the first added or removed filter
+ * @return Zero on success, -1 on error
+ */
+int flush_filters(struct output* restrict output, size_t first_updated)
+{
+ union gamma_ramps plain;
+ union gamma_ramps* last;
+ size_t i;
+
+ if (first_updated == 0)
+ {
+ if (make_plain_ramps(&plain, output) < 0)
+ return -1;
+ last = &plain;
+ }
+ else
+ last = output->table_sums + (first_updated - 1);
+
+ for (i = first_updated; i < output->table_size; i++)
+ {
+ apply_filter(output->table_sums + i, output->table_filters[i].ramps, output->depth, last);
+ last = output->table_sums + i;
+ }
+
+ set_gamma(output, last);
+
+ if (first_updated == 0)
+ libgamma_gamma_ramps8_destroy(&(plain.u8));
+
+ return 0;
+}
+
+
+
+/**
+ * Preserve current gamma ramps at priority 0 for all outputs
+ *
+ * @return Zero on success, -1 on error
+ */
+int preserve_gamma(void)
+{
+ size_t i;
+
+ for (i = 0; i < outputs_n; i++)
+ {
+ struct filter filter = {
+ .client = -1,
+ .priority = 0,
+ .class = NULL,
+ .lifespan = LIFESPAN_UNTIL_REMOVAL,
+ .ramps = NULL
+ };
+ outputs[i].table_filters = calloc(4, sizeof(*(outputs[i].table_filters)));
+ outputs[i].table_sums = calloc(4, sizeof(*(outputs[i].table_sums)));
+ outputs[i].table_alloc = 4;
+ outputs[i].table_size = 1;
+ filter.class = memdup(PKGNAME "::" COMMAND "::preserved", sizeof(PKGNAME "::" COMMAND "::preserved"));
+ if (filter.class == NULL)
+ return -1;
+ filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size);
+ if (filter.ramps == NULL)
+ return -1;
+ outputs[i].table_filters[0] = filter;
+ COPY_RAMP_SIZES(&(outputs[i].table_sums[0].u8), outputs + i);
+ if (!gamma_ramps_unmarshal(outputs[i].table_sums, outputs[i].saved_ramps.u8.red, outputs[i].ramps_size))
+ return -1;
+ }
+
+ return 0;
+}
+
diff --git a/src/servers/coopgamma.h b/src/servers/coopgamma.h
new file mode 100644
index 0000000..5a9f7d9
--- /dev/null
+++ b/src/servers/coopgamma.h
@@ -0,0 +1,101 @@
+/**
+ * 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/>.
+ */
+#ifndef SERVERS_COOPGAMMA_H
+#define SERVERS_COOPGAMMA_H
+
+
+#include "../types/output.h"
+
+#include <stddef.h>
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Handle a closed connection
+ *
+ * @param client The file descriptor for the client
+ * @return Zero on success, -1 on error
+ */
+int connection_closed(int client);
+
+/**
+ * Handle a ‘Command: get-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @param coalesce The value of the ‘Coalesce’ header
+ * @param high_priority The value of the ‘High priority’ header
+ * @param low_priority The value of the ‘Low priority’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+GCC_ONLY(__attribute__((nonnull(2))))
+int handle_get_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
+ const char* restrict coalesce, const char* restrict high_priority,
+ const char* restrict low_priority);
+
+/**
+ * Handle a ‘Command: set-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @param priority The value of the ‘Priority’ header
+ * @param class The value of the ‘Class’ header
+ * @param lifespan The value of the ‘Lifespan’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+GCC_ONLY(__attribute__((nonnull(2))))
+int handle_set_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
+ const char* restrict priority, const char* restrict class, const char* restrict lifespan);
+
+
+/**
+ * Recalculate the resulting gamma and
+ * update push the new gamma ramps to the CRTC
+ *
+ * @param output The output
+ * @param first_updated The index of the first added or removed filter
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+int flush_filters(struct output* restrict output, size_t first_updated);
+
+
+/**
+ * Preserve current gamma ramps at priority 0 for all outputs
+ *
+ * @return Zero on success, -1 on error
+ */
+int preserve_gamma(void);
+
+
+#endif
+
diff --git a/src/servers/crtc.c b/src/servers/crtc.c
new file mode 100644
index 0000000..2e884a8
--- /dev/null
+++ b/src/servers/crtc.c
@@ -0,0 +1,129 @@
+/**
+ * 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 "crtc.h"
+#include "../state.h"
+#include "../communication.h"
+
+#include <errno.h>
+#include <string.h>
+
+
+
+/**
+ * Handle a ‘Command: enumerate-crtcs’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int handle_enumerate_crtcs(size_t conn, const char* restrict message_id)
+{
+ size_t i, n = 0, len;
+ char* restrict buf;
+
+ for (i = 0; i < outputs_n; i++)
+ n += strlen(outputs[i].name) + 1;
+
+ MAKE_MESSAGE(&buf, &n, 0,
+ "Command: crtc-enumeration\n"
+ "In response to: %s\n"
+ "Length: %zu\n"
+ "\n",
+ message_id, n);
+
+ for (i = 0; i < outputs_n; i++)
+ {
+ len = strlen(outputs[i].name);
+ memcpy(buf + n, outputs[i].name, len);
+ buf[n + len] = '\n';
+ n += len + 1;
+ }
+
+ return send_message(conn, buf, n);
+}
+
+
+/**
+ * Get the name of a CRTC
+ *
+ * @param info Information about the CRTC
+ * @param crtc libgamma's state for the CRTC
+ * @return The name of the CRTC, `NULL` on error
+ */
+char* get_crtc_name(const libgamma_crtc_information_t* restrict info,
+ const libgamma_crtc_state_t* restrict crtc)
+{
+ if ((info->edid_error == 0) && (info->edid != NULL))
+ return libgamma_behex_edid(info->edid, info->edid_length);
+ else if ((info->connector_name_error == 0) && (info->connector_name != NULL))
+ {
+ char* name = malloc(3 * sizeof(size_t) + strlen(info->connector_name) + 2);
+ if (name != NULL)
+ sprintf(name, "%zu.%s", crtc->partition->partition, info->connector_name);
+ return name;
+ }
+ else
+ {
+ char* name = malloc(2 * 3 * sizeof(size_t) + 2);
+ if (name != NULL)
+ sprintf(name, "%zu.%zu", crtc->partition->partition, crtc->crtc);
+ return name;
+ }
+}
+
+
+/**
+ * Get partitions and CRTC:s
+ *
+ * @return Zero on success, -1 on error
+ */
+int initialise_crtcs(void)
+{
+ size_t i, j, n, n0;
+ int gerror;
+
+ /* Get partitions */
+ if (site.partitions_available)
+ if (!(partitions = calloc(site.partitions_available, sizeof(*partitions))))
+ goto fail;
+ for (i = 0; i < site.partitions_available; i++)
+ {
+ if ((gerror = libgamma_partition_initialise(partitions + i, &site, i)))
+ goto fail_libgamma;
+ outputs_n += partitions[i].crtcs_available;
+ }
+
+ /* Get CRTC:s */
+ if (outputs_n)
+ if (!(crtcs = calloc(outputs_n, sizeof(*crtcs))))
+ goto fail;
+ for (i = 0, j = n = 0; i < site.partitions_available; i++)
+ for (n0 = n, n += partitions[i].crtcs_available; j < n; j++)
+ if ((gerror = libgamma_crtc_initialise(crtcs + j, partitions + i, j - n0)))
+ goto fail_libgamma;
+
+ return 0;
+
+ fail_libgamma:
+ libgamma_perror(argv0, gerror);
+ errno = 0;
+ fail:
+ return -1;
+}
+
diff --git a/src/servers/crtc.h b/src/servers/crtc.h
new file mode 100644
index 0000000..4c96045
--- /dev/null
+++ b/src/servers/crtc.h
@@ -0,0 +1,67 @@
+/**
+ * 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/>.
+ */
+#ifndef SERVERS_CRTC_H
+#define SERVERS_CRTC_H
+
+
+#include <libgamma.h>
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Handle a ‘Command: enumerate-crtcs’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+GCC_ONLY(__attribute__((nonnull)))
+int handle_enumerate_crtcs(size_t conn, const char* restrict message_id);
+
+/**
+ * Get the name of a CRTC
+ *
+ * @param info Information about the CRTC
+ * @param crtc libgamma's state for the CRTC
+ * @return The name of the CRTC, `NULL` on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+char* get_crtc_name(const libgamma_crtc_information_t* restrict info,
+ const libgamma_crtc_state_t* restrict crtc);
+
+/**
+ * Get partitions and CRTC:s
+ *
+ * @return Zero on success, -1 on error
+ */
+int initialise_crtcs(void);
+
+
+#endif
+
diff --git a/src/servers/gamma.c b/src/servers/gamma.c
new file mode 100644
index 0000000..95130e6
--- /dev/null
+++ b/src/servers/gamma.c
@@ -0,0 +1,279 @@
+/**
+ * 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 "gamma.h"
+#include "crtc.h"
+#include "../state.h"
+#include "../communication.h"
+
+#include <errno.h>
+#include <string.h>
+
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic ignored "-Wswitch-enum"
+#endif
+
+
+
+/**
+ * Handle a ‘Command: set-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int handle_get_gamma_info(size_t conn, const char* restrict message_id, const char* restrict crtc)
+{
+ struct output* restrict output;
+ char* restrict buf;
+ char depth[3];
+ const char* supported;
+ size_t n;
+
+ if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted");
+
+ output = output_find_by_name(crtc, outputs, outputs_n);
+ if (output == NULL)
+ return send_error("selected CRTC does not exist");
+
+ switch (output->depth)
+ {
+ case -2: strcpy(depth, "d"); break;
+ case -1: strcpy(depth, "f"); break;
+ default:
+ sprintf(depth, "%i", output->depth);
+ break;
+ }
+
+ switch (output->supported)
+ {
+ case LIBGAMMA_YES: supported = "yes"; break;
+ case LIBGAMMA_NO: supported = "no"; break;
+ default: supported = "maybe"; break;
+ }
+
+ MAKE_MESSAGE(&buf, &n, 0,
+ "In response to: %s\n"
+ "Cooperative: yes\n" /* In mds: say ‘no’, mds-coopgamma changes to ‘yes’.” */
+ "Depth: %s\n"
+ "Red size: %zu\n"
+ "Green size: %zu\n"
+ "Blue size: %zu\n"
+ "Gamma support: %s\n"
+ "\n",
+ message_id, depth, output->red_size, output->green_size,
+ output->blue_size, supported);
+
+ return send_message(conn, buf, n);
+}
+
+
+/**
+ * Set the gamma ramps on an output
+ *
+ * @param output The output
+ * @param ramps The gamma ramps
+ */
+void set_gamma(const struct output* restrict output, const union gamma_ramps* restrict ramps)
+{
+ int r = 0;
+
+ if (!connected)
+ return;
+
+ switch (output->depth)
+ {
+ case 8: r = libgamma_crtc_set_gamma_ramps8(output->crtc, ramps->u8); break;
+ case 16: r = libgamma_crtc_set_gamma_ramps16(output->crtc, ramps->u16); break;
+ case 32: r = libgamma_crtc_set_gamma_ramps32(output->crtc, ramps->u32); break;
+ case 64: r = libgamma_crtc_set_gamma_ramps64(output->crtc, ramps->u64); break;
+ case -1: r = libgamma_crtc_set_gamma_rampsf(output->crtc, ramps->f); break;
+ case -2: r = libgamma_crtc_set_gamma_rampsd(output->crtc, ramps->d); break;
+ default:
+ abort();
+ }
+ if (r)
+ libgamma_perror(argv0, r); /* Not fatal */
+}
+
+
+
+/**
+ * Store all current gamma ramps
+ *
+ * @return Zero on success, -1 on error
+ */
+int initialise_gamma_info(void)
+{
+ libgamma_crtc_information_t info;
+ int saved_errno;
+ size_t i;
+
+ for (i = 0; i < outputs_n; i++)
+ {
+ libgamma_get_crtc_information(&info, crtcs + i,
+ LIBGAMMA_CRTC_INFO_EDID |
+ LIBGAMMA_CRTC_INFO_MACRO_RAMP |
+ LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT |
+ LIBGAMMA_CRTC_INFO_CONNECTOR_NAME);
+ outputs[i].depth = info.gamma_depth_error ? 0 : info.gamma_depth;
+ outputs[i].red_size = info.gamma_size_error ? 0 : info.red_gamma_size;
+ outputs[i].green_size = info.gamma_size_error ? 0 : info.green_gamma_size;
+ outputs[i].blue_size = info.gamma_size_error ? 0 : info.blue_gamma_size;
+ outputs[i].supported = info.gamma_support_error ? 0 : info.gamma_support;
+ if (outputs[i].depth == 0 || outputs[i].red_size == 0 ||
+ outputs[i].green_size == 0 || outputs[i].blue_size == 0)
+ outputs[i].supported = 0;
+ outputs[i].name = get_crtc_name(&info, crtcs + i);
+ saved_errno = errno;
+ outputs[i].crtc = crtcs + i;
+ libgamma_crtc_information_destroy(&info);
+ outputs[i].ramps_size = outputs[i].red_size + outputs[i].green_size + outputs[i].blue_size;
+ switch (outputs[i].depth)
+ {
+ default:
+ outputs[i].depth = 64;
+ /* Fall through */
+ case 8:
+ case 16:
+ case 32:
+ case 64: outputs[i].ramps_size *= (size_t)(outputs[i].depth / 8); break;
+ case -2: outputs[i].ramps_size *= sizeof(double); break;
+ case -1: outputs[i].ramps_size *= sizeof(float); break;
+ }
+ errno = saved_errno;
+ if (outputs[i].name == NULL)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Store all current gamma ramps
+ */
+void store_gamma(void)
+{
+ int gerror;
+ size_t i;
+
+#define LOAD_RAMPS(SUFFIX, MEMBER) \
+ do \
+ { \
+ libgamma_gamma_ramps##SUFFIX##_initialise(&(outputs[i].saved_ramps.MEMBER)); \
+ gerror = libgamma_crtc_get_gamma_ramps##SUFFIX(outputs[i].crtc, &(outputs[i].saved_ramps.MEMBER)); \
+ if (gerror) \
+ { \
+ libgamma_perror(argv0, gerror); \
+ outputs[i].supported = LIBGAMMA_NO; \
+ libgamma_gamma_ramps##SUFFIX##_destroy(&(outputs[i].saved_ramps.MEMBER)); \
+ memset(&(outputs[i].saved_ramps.MEMBER), 0, sizeof(outputs[i].saved_ramps.MEMBER)); \
+ } \
+ } \
+ while (0)
+
+ for (i = 0; i < outputs_n; i++)
+ {
+ if (outputs[i].supported == LIBGAMMA_NO)
+ continue;
+
+ switch (outputs[i].depth)
+ {
+ case 64: LOAD_RAMPS(64, u64); break;
+ case 32: LOAD_RAMPS(32, u32); break;
+ case 16: LOAD_RAMPS(16, u16); break;
+ case 8: LOAD_RAMPS( 8, u8); break;
+ case -2: LOAD_RAMPS(d, d); break;
+ case -1: LOAD_RAMPS(f, f); break;
+ default: /* impossible */ break;
+ }
+ }
+}
+
+
+/**
+ * Restore all gamma ramps
+ */
+void restore_gamma(void)
+{
+ size_t i;
+ int gerror;
+
+#define RESTORE_RAMPS(SUFFIX, MEMBER) \
+ do \
+ if (outputs[i].saved_ramps.MEMBER.red != NULL) \
+ { \
+ gerror = libgamma_crtc_set_gamma_ramps##SUFFIX(outputs[i].crtc, outputs[i].saved_ramps.MEMBER); \
+ if (gerror) \
+ libgamma_perror(argv0, gerror); \
+ } \
+ while (0)
+
+ for (i = 0; i < outputs_n; i++)
+ {
+ if (outputs[i].supported == LIBGAMMA_NO)
+ continue;
+
+ switch (outputs[i].depth)
+ {
+ case 64: RESTORE_RAMPS(64, u64); break;
+ case 32: RESTORE_RAMPS(32, u32); break;
+ case 16: RESTORE_RAMPS(16, u16); break;
+ case 8: RESTORE_RAMPS( 8, u8); break;
+ case -2: RESTORE_RAMPS(d, d); break;
+ case -1: RESTORE_RAMPS(f, f); break;
+ default: /* impossible */ break;
+ }
+ }
+}
+
+
+
+/**
+ * Disconnect from the site
+ *
+ * @return Zero on success, -1 on error
+ */
+int disconnect(void)
+{
+ if (!connected)
+ return 0;
+
+ connected = 0;
+ return 0; /* TODO disconnect() */
+}
+
+
+/**
+ * Reconnect to the site
+ *
+ * @return Zero on success, -1 on error
+ */
+int reconnect(void)
+{
+ if (connected)
+ return 0;
+
+ connected = 1;
+ return 0; /* TODO reconnect() */
+}
+
diff --git a/src/servers/gamma.h b/src/servers/gamma.h
new file mode 100644
index 0000000..ee9af84
--- /dev/null
+++ b/src/servers/gamma.h
@@ -0,0 +1,94 @@
+/**
+ * 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/>.
+ */
+#ifndef SERVERS_GAMMA_H
+#define SERVERS_GAMMA_H
+
+
+#include "../types/output.h"
+
+#include <stddef.h>
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Handle a ‘Command: set-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+GCC_ONLY(__attribute__((nonnull(2))))
+int handle_get_gamma_info(size_t conn, const char* restrict message_id, const char* restrict crtc);
+
+/**
+ * Set the gamma ramps on an output
+ *
+ * @param output The output
+ * @param ramps The gamma ramps
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void set_gamma(const struct output* restrict output, const union gamma_ramps* restrict ramps);
+
+
+/**
+ * Store all current gamma ramps
+ *
+ * @return Zero on success, -1 on error
+ */
+int initialise_gamma_info(void);
+
+/**
+ * Store all current gamma ramps
+ */
+void store_gamma(void);
+
+/**
+ * Restore all gamma ramps
+ */
+void restore_gamma(void);
+
+
+/**
+ * Disconnect from the site
+ *
+ * @return Zero on success, -1 on error
+ */
+int disconnect(void);
+
+/**
+ * Reconnect to the site
+ *
+ * @return Zero on success, -1 on error
+ */
+int reconnect(void);
+
+
+#endif
+
diff --git a/src/servers/kernel.c b/src/servers/kernel.c
new file mode 100644
index 0000000..ce3246a
--- /dev/null
+++ b/src/servers/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);
+ }
+}
+
diff --git a/src/servers/kernel.h b/src/servers/kernel.h
new file mode 100644
index 0000000..494e150
--- /dev/null
+++ b/src/servers/kernel.h
@@ -0,0 +1,85 @@
+/**
+ * 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/>.
+ */
+#ifndef SERVERS_KERNEL_H
+#define SERVERS_KERNEL_H
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Get the pathname of the socket
+ *
+ * @return The pathname of the socket, `NULL` on error
+ */
+GCC_ONLY(__attribute__((malloc)))
+char* get_socket_pathname(void);
+
+/**
+ * Get the pathname of the PID file
+ *
+ * @return The pathname of the PID file, `NULL` on error
+ */
+GCC_ONLY(__attribute__((malloc)))
+char* get_pidfile_pathname(void);
+
+/**
+ * Get the pathname of the state file
+ *
+ * @return The pathname of the state file, `NULL` on error
+ */
+GCC_ONLY(__attribute__((malloc)))
+char* get_state_pathname(void);
+
+/**
+ * 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
+ */
+GCC_ONLY(__attribute__((nonnull)))
+int create_pidfile(char* pidpath);
+
+/**
+ * Create socket and start listening
+ *
+ * @param socketpath The pathname of the socket
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+int create_socket(const char* socketpath);
+
+/**
+ * Close and unlink the socket
+ *
+ * @param socketpath The pathname of the socket
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void close_socket(const char* socketpath);
+
+
+#endif
+