diff options
Diffstat (limited to 'src/coopgammad.c')
-rw-r--r-- | src/coopgammad.c | 895 |
1 files changed, 0 insertions, 895 deletions
diff --git a/src/coopgammad.c b/src/coopgammad.c deleted file mode 100644 index e81840e..0000000 --- a/src/coopgammad.c +++ /dev/null @@ -1,895 +0,0 @@ -/** - * 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 "arg.h" -#include "util.h" -#include "state.h" -#include "servers/master.h" -#include "servers/kernel.h" -#include "servers/crtc.h" -#include "servers/gamma.h" -#include "servers/coopgamma.h" - -#include <sys/resource.h> -#include <sys/stat.h> -#include <sys/wait.h> -#include <errno.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - - - -/** - * Number put in front of the marshalled data - * so the program an detect incompatible updates - */ -#define MARSHAL_VERSION 0 - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Lists all function recognised adjustment methods, - * will call macro X with the code for the each - * adjustment method as the first argument and - * corresponding name as the second argument - */ -#define LIST_ADJUSTMENT_METHODS \ - X(LIBGAMMA_METHOD_DUMMY, "dummy") \ - X(LIBGAMMA_METHOD_X_RANDR, "randr") \ - X(LIBGAMMA_METHOD_X_VIDMODE, "vidmode") \ - X(LIBGAMMA_METHOD_LINUX_DRM, "drm") \ - X(LIBGAMMA_METHOD_W32_GDI, "gdi") \ - X(LIBGAMMA_METHOD_QUARTZ_CORE_GRAPHICS, "quartz") - - - -/** - * Used by initialisation functions as their return type. If a - * value not listed here is returned by such function, it is the - * exit value the process shall exit with as soon as possible. - */ -enum init_status -{ - /** - * Initialisation was successful - */ - INIT_SUCCESS = -1, - - /** - * Initialisation failed - */ - INIT_FAILURE = -2, - - /** - * Server is already running - */ - INIT_RUNNING = -3, -}; - - - -/** - * The pathname of the PID file - */ -extern char* restrict pidpath; -char* restrict pidpath = NULL; - -/** - * The pathname of the socket - */ -extern char* restrict socketpath; -char* restrict socketpath = NULL; - - - -/** - * Called when the process receives - * a signal telling it to re-execute - * - * @param signo The received signal - */ -static void sig_reexec(int signo) -{ - int saved_errno = errno; - reexec = 1; - signal(signo, sig_reexec); - errno = saved_errno; -} - - -/** - * Called when the process receives - * a signal telling it to terminate - * - * @param signo The received signal - */ -static void sig_terminate(int signo) -{ - terminate = 1; - (void) signo; -} - - -/** - * Called when the process receives - * a signal telling it to disconnect - * from or reconnect to the site - * - * @param signo The received signal - */ -static void sig_connection(int signo) -{ - int saved_errno = errno; - connection = signo - SIGRTMIN + 1; - signal(signo, sig_connection); - errno = saved_errno; -} - - -/** - * Called when the process receives - * a signal telling it to dump its - * state to stderr - * - * @param signo The received signal - */ -static void sig_info(int signo) -{ - int saved_errno = errno; - char* env; - signal(signo, sig_info); - env = getenv("COOPGAMMAD_PIDFILE_TOKEN"); - fprintf(stderr, "PID file token: %s\n", env ? env : "(null)"); - fprintf(stderr, "PID file: %s\n", pidpath ? pidpath : "(null)"); - fprintf(stderr, "Socket path: %s\n", socketpath ? socketpath : "(null)"); - state_dump(); - errno = saved_errno; -} - - -/** - * Parse adjustment method name (or stringised number) - * - * @param arg The adjustment method name (or stringised number) - * @return The adjustment method, -1 (negative) on error - */ -GCC_ONLY(__attribute__((nonnull))) -static int get_method(const char* restrict arg) -{ -#if LIBGAMMA_METHOD_MAX > 5 -# warning libgamma has added more adjustment methods -#endif - - const char* restrict p; - -#define X(C, N) if (!strcmp(arg, N)) return C; - LIST_ADJUSTMENT_METHODS; -#undef X - - if (!*arg || (/* avoid overflow: */ strlen(arg) > 4)) - goto bad; - for (p = arg; *p; p++) - if (('0' > *p) || (*p > '9')) - goto bad; - - return atoi(arg); - - bad: - fprintf(stderr, "%s: unrecognised adjustment method name: %s\n", argv0, arg); - errno = 0; - return -1; -} - - -/** - * Set up signal handlers - * - * @return Zero on success, -1 on error - */ -static int set_up_signals(void) -{ - if ((signal(SIGUSR1, sig_reexec) == SIG_ERR) || - (signal(SIGUSR2, sig_info) == SIG_ERR) || -#if defined(SIGINFO) - (signal(SIGINFO, sig_info) == SIG_ERR) || -#endif - (signal(SIGTERM, sig_terminate) == SIG_ERR) || - (signal(SIGRTMIN + 0, sig_connection) == SIG_ERR) || - (signal(SIGRTMIN + 1, sig_connection) == SIG_ERR)) - return -1; - return 0; -} - - -/** - * Fork the process to the background - * - * @param keep_stderr Keep stderr open? - * @return An `enum init_status` value or an exit value - */ -static enum init_status daemonise(int keep_stderr) -{ - pid_t pid; - int fd = -1, saved_errno; - int notify_rw[2] = { -1, -1 }; - char a_byte = 0; - ssize_t got; - - if (pipe(notify_rw) < 0) - goto fail; - if (notify_rw[0] <= STDERR_FILENO) - if ((notify_rw[0] = dup2atleast(notify_rw[0], STDERR_FILENO + 1)) < 0) - goto fail; - if (notify_rw[1] <= STDERR_FILENO) - if ((notify_rw[1] = dup2atleast(notify_rw[1], STDERR_FILENO + 1)) < 0) - goto fail; - - if ((pid = fork()) < 0) - goto fail; - if (pid > 0) - { - /* Original process (parent): */ - waitpid(pid, NULL, 0); - close(notify_rw[1]), notify_rw[1] = -1; - got = read(notify_rw[0], &a_byte, 1); - if (got < 0) - goto fail; - close(notify_rw[0]); - errno = 0; - return got == 0 ? INIT_FAILURE : (enum init_status)0; - } - - /* Intermediary process (child): */ - close(notify_rw[0]), notify_rw[0] = -1; - if (setsid() < 0) - goto fail; - if ((pid = fork()) < 0) - goto fail; - if (pid > 0) - { - /* Intermediary process (parent): */ - return (enum init_status)0; - } - - /* Daemon process (child): */ - - /* Replace std* with /dev/null */ - fd = open("/dev/null", O_RDWR); - if (fd < 0) - goto fail; -#define xdup2(s, d) do if (s != d) { close(d); if (dup2(s, d) < 0) goto fail; } while (0) - xdup2(fd, STDIN_FILENO); - xdup2(fd, STDOUT_FILENO); - if (keep_stderr) - xdup2(fd, STDERR_FILENO); - if (fd > STDERR_FILENO) - close(fd); - fd = -1; - - /* Update PID file */ - fd = open(pidpath, O_WRONLY); - if (fd < 0) - goto fail; - if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0) - goto fail; - close(fd), fd = -1; - - /* Notify */ - if (write(notify_rw[1], &a_byte, 1) <= 0) - goto fail; - close(notify_rw[1]); - - return INIT_SUCCESS; - fail: - saved_errno = errno; - if (fd >= 0) close(fd); - if (notify_rw[0] >= 0) close(notify_rw[0]); - if (notify_rw[1] >= 0) close(notify_rw[1]); - errno = saved_errno; - return INIT_FAILURE; -} - - -/** - * Initialise the process - * - * @param foreground Keep process in the foreground - * @param keep_stderr Keep stderr open - * @param query Was -q used, see `main` for description - * @return An `enum init_status` value or an exit value - */ -static enum init_status initialise(int foreground, int keep_stderr, int query) -{ - struct rlimit rlimit; - size_t i, n; - sigset_t mask; - int s; - enum init_status r; - - /* Zero out some memory so it can be destoried safely. */ - memset(&site, 0, sizeof(site)); - - if (!query) - { - /* Close all file descriptors above stderr */ - if (getrlimit(RLIMIT_NOFILE, &rlimit) || (rlimit.rlim_cur == RLIM_INFINITY)) - n = 4 << 10; - else - n = (size_t)(rlimit.rlim_cur); - for (i = STDERR_FILENO + 1; i < n; i++) - close((int)i); - - /* Set umask, reset signal handlers, and reset signal mask */ - umask(0); - for (s = 1; s < _NSIG; s++) - signal(s, SIG_DFL); - if (sigfillset(&mask)) - perror(argv0); - else - sigprocmask(SIG_UNBLOCK, &mask, NULL); - } - - /* Get method */ - if ((method < 0) && (libgamma_list_methods(&method, 1, 0) < 1)) - return fprintf(stderr, "%s: no adjustment method available\n", argv0), -1; - - /* Go no further if we are just interested in the adjustment method and site */ - if (query) - return INIT_SUCCESS; - - /* Get site */ - if (initialise_site() < 0) - goto fail; - - /* Get PID file and socket pathname */ - if (!(pidpath = get_pidfile_pathname()) || - !(socketpath = get_socket_pathname())) - goto fail; - - /* Create PID file */ - if ((r = create_pidfile(pidpath)) < 0) - { - free(pidpath), pidpath = NULL; - if (r == -2) - return INIT_RUNNING; - goto fail; - } - - /* Get partitions and CRTC:s */ - if (initialise_crtcs() < 0) - goto fail; - - /* Get CRTC information */ - if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs)))) - goto fail; - if (initialise_gamma_info() < 0) - goto fail; - - /* Sort outputs */ - qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name); - - /* Load current gamma ramps */ - store_gamma(); - - /* Preserve current gamma ramps at priority=0 if -p */ - if (preserve && (preserve_gamma() < 0)) - goto fail; - - /* Create socket and start listening */ - if (create_socket(socketpath) < 0) - goto fail; - - /* Get the real pathname of the process's binary, in case - * it is relative, so we can re-execute without problem. */ - if ((*argv0 != '/') && strchr(argv0, '/') && !(argv0_real = realpath(argv0, NULL))) - goto fail; - - /* Change directory to / to avoid blocking umounting */ - if (chdir("/") < 0) - perror(argv0); - - /* Set up signal handlers */ - if (set_up_signals() < 0) - goto fail; - - /* Place in the background unless -f */ - if (foreground == 0) - return daemonise(keep_stderr); - else - { - /* Signal the spawner that the service is ready */ - close(STDOUT_FILENO); - /* Avoid potential catastrophes that would occur if a library - * that is being used was so mindless as to write to stdout. */ - if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) - perror(argv0); - } - - return INIT_SUCCESS; - fail: - return INIT_FAILURE; -} - - -/** - * Deinitialise the process - * - * @param full Perform a full deinitialisation, shall be - * done iff the process is going to re-execute - */ -static void destroy(int full) -{ - if (full) - { - disconnect_all(); - close_socket(socketpath); - free(argv0_real); - if ((outputs != NULL) && connected) - restore_gamma(); - } - state_destroy(); - free(socketpath); - if (full && (pidpath != NULL)) - unlink(pidpath); - free(pidpath); -} - - - -#if defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - - -/** - * Marshal the state of the process - * - * @param buf Output buffer for the marshalled data, - * `NULL` to only measure how large the - * buffer needs to be - * @return The number of marshalled bytes - */ -static size_t marshal(void* restrict buf) -{ - size_t off = 0, n; - char* restrict bs = buf; - - if (bs != NULL) - *(int*)(bs + off) = MARSHAL_VERSION; - off += sizeof(int); - - n = strlen(pidpath) + 1; - if (bs != NULL) - memcpy(bs + off, pidpath, n); - off += n; - - n = strlen(socketpath) + 1; - if (bs != NULL) - memcpy(bs + off, socketpath, n); - off += n; - - off += state_marshal(bs ? bs + off : NULL); - - return off; -} - - -/** - * Unmarshal the state of the process - * - * @param buf Buffer with the marshalled data - * @return The number of marshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -static size_t unmarshal(const void* restrict buf) -{ - size_t off = 0, n; - const char* restrict bs = buf; - - if (*(const int*)(bs + off) != MARSHAL_VERSION) - { - fprintf(stderr, "%s: re-executing to incompatible version, sorry about that\n", argv0); - errno = 0; - return 0; - } - off += sizeof(int); - - n = strlen(bs + off) + 1; - if (!(pidpath = memdup(bs + off, n))) - return 0; - off += n; - - n = strlen(bs + off) + 1; - if (!(socketpath = memdup(bs + off, n))) - return 0; - off += n; - - off += n = state_unmarshal(bs + off); - if (n == 0) - return 0; - - return off; -} - - -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif - - - -/** - * Do minimal initialisation, unmarshal the state of - * the process and merge with new state - * - * @param statefile The state file - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull))) -static int restore_state(const char* restrict statefile) -{ - void* marshalled = NULL; - int fd = -1, saved_errno; - size_t r, n; - - if (set_up_signals() < 0) - goto fail; - - fd = open(statefile, O_RDONLY); - if (fd < 0) - goto fail; - - if (!(marshalled = nread(fd, &n))) - goto fail; - close(fd), fd = -1; - unlink(statefile), statefile = NULL; - - r = unmarshal(marshalled); - if (r == 0) - goto fail; - if (r != n) - { - fprintf(stderr, "%s: unmarshalled state file was %s than the unmarshalled state: read %zu of %zu bytes\n", - argv0, n > r ? "larger" : "smaller", r, n); - errno = 0; - goto fail; - } - free(marshalled), marshalled = NULL; - - if (connected) - { - connected = 0; - if (reconnect() < 0) - goto fail; - } - - return 0; - fail: - saved_errno = errno; - if (fd >= 0) - close(fd); - free(marshalled); - errno = saved_errno; - return -1; -} - - -/** - * Reexecute the server - * - * Returns only on failure - * - * @return Pathname of file where the state is stored, - * `NULL` if the state is in tact - */ -static char* reexecute(void) -{ - char* statefile = NULL; - char* statebuffer = NULL; - size_t buffer_size; - int fd = -1, saved_errno; - - statefile = get_state_pathname(); - if (statefile == NULL) - goto fail; - - buffer_size = marshal(NULL); - statebuffer = malloc(buffer_size); - if (statebuffer == NULL) - goto fail; - if (marshal(statebuffer) != buffer_size) - { - fprintf(stderr, "%s: internal error\n", argv0); - errno = 0; - goto fail; - } - - fd = open(statefile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); - if (fd < 0) - goto fail; - - if (nwrite(fd, statebuffer, buffer_size) != buffer_size) - goto fail; - free(statebuffer), statebuffer = NULL; - - if ((close(fd) < 0) && (fd = -1, errno != EINTR)) - goto fail; - fd = -1; - - destroy(0); - -#if !defined(USE_VALGRIND) - execlp(argv0_real ? argv0_real : argv0, argv0, "- ", statefile, NULL); -#else - execlp("valgrind", "valgrind", "--leak-check=full", argv0_real ? argv0_real : argv0, "- ", statefile, NULL); -#endif - saved_errno = errno; - free(argv0_real), argv0_real = NULL; - errno = saved_errno; - return statefile; - - fail: - saved_errno = errno; - free(statebuffer); - if (fd >= 0) - close(fd); - if (statefile != NULL) - unlink(statefile), free(statefile); - errno = saved_errno; - return NULL; -} - - - -/** - * Print the response for the -q option - * - * @param query See -q for `main`, must be atleast 1 - * @return Zero on success, -1 on error - */ -static int print_method_and_site(int query) -{ - const char* restrict methodname = NULL; - char* p; - - if (query == 1) - { - switch (method) - { -#define X(C, N) case C: methodname = N; break; - LIST_ADJUSTMENT_METHODS; -#undef X - default: - if (printf("%i\n", method) < 0) - return -1; - break; - } - if (methodname != NULL) - if (printf("%s\n", methodname) < 0) - return -1; - } - - if (sitename == NULL) - if ((sitename = libgamma_method_default_site(method))) - if (!(sitename = memdup(sitename, strlen(sitename) + 1))) - return -1; - - if (sitename != NULL) - switch (method) - { - case LIBGAMMA_METHOD_X_RANDR: - case LIBGAMMA_METHOD_X_VIDMODE: - if ((p = strrchr(sitename, ':'))) - if ((p = strchr(p, '.'))) - *p = '\0'; - break; - default: - break; - } - - if ((sitename != NULL) && (query == 1)) - if (printf("%s\n", sitename) < 0) - return -1; - - if (query == 2) - { - site.method = method; - site.site = sitename, sitename = NULL; - socketpath = get_socket_pathname(); - if (socketpath == NULL) - return -1; - if (printf("%s\n", socketpath) < 0) - return -1; - } - - if (fflush(stdout)) - return -1; - return 0; -} - - - -/** - * Print usage information and exit - */ -#if defined(__GNU__) || defined(__clang__) -__attribute__((noreturn)) -#endif -static void usage(void) -{ - printf("Usage: %s [-m method] [-s site] [-fkpq]\n", argv0); - exit(1); -} - - -#if defined(__clang__) -# pragma GCC diagnostic ignored "-Wdocumentation-unknown-command" -#endif - - -/** - * Must not be started without stdin, stdout, or stderr (may be /dev/null) - * - * argv[0] must refer to the real command name or pathname, - * otherwise, re-execute will not work - * - * The process closes stdout when the socket has been created - * - * @signal SIGUSR1 Re-execute to updated process image - * @signal SIGUSR2 Dump the state of the process to standard error - * @signal SIGINFO Ditto - * @signal SIGTERM Terminate the process gracefully - * @signal SIGRTMIN+0 Disconnect from the site - * @signal SiGRTMIN+1 Reconnect to the site - * - * @param argc The number of elements in `argv` - * @param argv Command line arguments. Recognised options: - * -s SITENAME - * The site to which to connect - * -m METHOD - * Adjustment method name or adjustment method number - * -p - * Preserve current gamma ramps at priority 0 - * -f - * Do not fork the process into the background - * -k - * Keep stderr open - * -q - * Print the select (possiblity default) adjustment - * method on the first line in stdout, and the - * selected (possibility defasult) site on the second - * line in stdout, and exit. If the site name is `NULL`, - * the second line is omitted. This is indented to - * be used by clients to figure out to which instance - * of the service it should connect. Use twice to - * simply ge the socket pathname, an a terminating LF. - * By combining -q and -m you can enumerate the name - * of all recognised adjustment method, start from 0 - * and work up until a numerical adjustment method is - * returned. - * @return 0: Successful - * 1: An error occurred - * 2: Already running - */ -int main(int argc, char** argv) -{ - int rc = 1, foreground = 0, keep_stderr = 0, query = 0, r; - char* statefile = NULL; - char* statefile_ = NULL; - - ARGBEGIN - { - case 's': - sitename = EARGF(usage()); - /* To simplify re-exec: */ - sitename = memdup(sitename, strlen(sitename) + 1); - if (sitename == NULL) - goto fail; - break; - case 'm': - method = get_method(EARGF(usage())); - if (method < 0) - goto fail; - break; - case 'p': preserve = 1; break; - case 'f': foreground = 1; break; - case 'k': keep_stderr = 1; break; - case 'q': query = 1 + !!query; break; - case ' ': /* Internal, do not document */ - statefile = statefile_ = EARGF(usage()); - break; - default: - usage(); - } - ARGEND; - if (argc > 0) - usage(); - - restart: - - if (statefile == NULL) - switch ((r = initialise(foreground, keep_stderr, query))) - { - case INIT_SUCCESS: break; - case INIT_RUNNING: rc = 2; /* fall through */ - case INIT_FAILURE: goto fail; - default: return r; - } - else if (restore_state(statefile) < 0) - goto fail; - else - { - if (statefile != statefile_) - free(statefile); - unlink(statefile), statefile = NULL; - } - - if (query) - { - if (print_method_and_site(query) < 0) - goto fail; - goto done; - } - - reenter_loop: - if (main_loop() < 0) - goto fail; - - if (reexec && !terminate) - { - if ((statefile = reexecute())) - { - perror(argv0); - fprintf(stderr, "%s: restoring state without re-executing\n", argv0); - reexec = 0; - goto restart; - } - perror(argv0); - fprintf(stderr, "%s: continuing without re-executing\n", argv0); - reexec = 0; - goto reenter_loop; - } - - done: - rc = 0; - deinit: - if (statefile) - unlink(statefile); - if (reexec) - free(statefile); - destroy(1); - return rc; - fail: - if (errno != 0) - perror(argv0); - goto deinit; -} - |