diff options
Diffstat (limited to 'src/common.c')
-rw-r--r-- | src/common.c | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..a6f4c7c --- /dev/null +++ b/src/common.c @@ -0,0 +1,501 @@ +/** + * Copyright © 2015, 2016 Mattias Andrée <maandree@member.fsf.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "common.h" +#include <ctype.h> +#include <stdarg.h> +#include <pwd.h> +#include <sys/wait.h> + + + +/** + * The environment. + */ +extern char **environ; + + + +/** + * Common code for `preadn` and `pwriten`. + * + * @param FUN `pread` or `pwrite`. + */ +#define PIO(FUN) \ + ssize_t r, n = 0; \ + int saved_errno = 0; \ + sigset_t mask, oldmask; \ + sigfillset(&mask); \ + sigprocmask(SIG_BLOCK, &mask, &oldmask); \ + while (nbyte) { \ + t (r = FUN(fildes, buffer, nbyte, (off_t)offset), r < 0); \ + if (r == 0) \ + break; \ + n += r; \ + buffer += r; \ + offset += (size_t)r; \ + nbyte -= (size_t)r; \ + } \ +done: \ + sigprocmask(SIG_SETMASK, &oldmask, NULL); \ + errno = saved_errno; \ + return n; \ +fail: \ + saved_errno = errno; \ + n = -1; \ + goto done + + +/** + * Wrapper for `pread` that reads the required amount of data. + * + * @param fildes See pread(3). + * @param buf See pread(3). + * @param nbyte See pread(3). + * @param offset See pread(3). + * @return See pread(3), only short if the file is shorter. + */ +ssize_t +preadn(int fildes, void *buf, size_t nbyte, size_t offset) +{ + char *buffer = buf; + PIO(pread); +} + + +/** + * Wrapper for `pwrite` that writes all specified data. + * + * @param fildes See pwrite(3). + * @param buf See pwrite(3). + * @param nbyte See pwrite(3). + * @param offset See pwrite(3). + * @return See pwrite(3). + */ +ssize_t +pwriten(int fildes, const void *buf, size_t nbyte, size_t offset) +{ + const char *buffer = buf; + PIO(pwrite); +} + + +/** + * Unmarshal a `NULL`-terminated string array. + * + * The elements are not actually copied, subpointers + * to `buf` are stored in the returned list. + * + * @param buf The marshalled array. Must end with a NUL byte. + * @param len The length of `buf`. + * @param n Output parameter for the number of elements. May be `NULL` + * @return The list, `NULL` on error. + * + * @throws Any exception specified for realloc(3). + */ +char ** +restore_array(char *buf, size_t len, size_t *n) +{ + char **rc = malloc((len + 1) * sizeof(char*)); + char **new = NULL; + size_t i = 0, e = 0; + + t (!rc); + while (i < len) i += strlen(rc[e++] = buf + i) + 1; + if (n) *n = e; + rc[e++] = NULL; + new = realloc(rc, e * sizeof(char*)); +fail: + return new ? new : rc; +} + + +/** + * Create `NULL`-terminate subcopy of an list, + * + * @param list The list. + * @param n The number of elements in the new sublist. + * @return The sublist, `NULL` on error. + * + * @throws Any exception specified for malloc(3). + */ +char ** +sublist(char *const *list, size_t n) +{ + char **rc = malloc((n + 1) * sizeof(char*)); + t (!rc); + for (rc[n] = NULL; n--; rc[n] = list[n]); +fail: + return rc; +} + + +/** + * Create a new open file descriptor for an already + * existing file descriptor. + * + * @param fd The file descriptor that shall be promoted + * to a new open file descriptor. + * @param oflag See open(3), `O_CREAT` is not allowed. + * @return 0 on success, -1 on error. + */ +int +reopen(int fd, int oflag) +{ + char path[sizeof("/dev/fd/") + 3 * sizeof(int)]; + int r, saved_errno; + + sprintf(path, "/dev/fd/%i", fd); + if (r = open(path, oflag), r < 0) + return -1; + if (DUP2_AND_CLOSE(r, fd) == -1) + return S(close(r)), -1; + return 0; +} + + +/** + * Run a job or a hook. + * + * @param job The job. + * @param hook The hook, `NULL` to run the job. + * @return 0 on success, -1 on error, 1 if the child failed. + */ +int +run_job_or_hook(struct job *job, const char *hook) +{ + pid_t pid; + char **args = NULL; + char **argv = NULL; + char **envp = NULL; + size_t argsn; + void *new; + int status = 0, saved_errno; + + t (!(args = restore_array(job->payload, job->n, &argsn))); + t (!(argv = sublist(args, (size_t)(job->argc)))); + t (!(envp = sublist(args + job->argc, argsn - (size_t)(job->argc)))); /* Includes wdir. */ + free(args), args = NULL; + + if (hook) { + t (!(new = realloc(argv, ((size_t)(job->argc) + 3) * sizeof(*argv)))); + argv = new; + memmove(argv + 2, argv, ((size_t)(job->argc) + 1) * sizeof(*argv)); + argv[0] = getenv("SAT_HOOK_PATH"); + argv[1] = (strstr)(hook, hook); /* strstr: just to remove a warning */ + } + + if (!(pid = fork())) { + close(STATE_FILENO), close(BOOT_FILENO), close(REAL_FILENO); + (void)(status = chdir(envp[0])); + environ = envp + 1; + execvp(*argv, argv); + exit(1); + } + + t ((pid < 0) || (waitpid(pid, &status, 0) != pid)); +fail: + S(free(args), free(argv), free(envp)); + return status ? 1 : -!!saved_errno; +} + + +/** + * Removes (and optionally runs) a job. + * + * @param jobno The job number, `NULL` for any job. + * @param runjob Shall we run the job too? 2 if its time has expired (not forced). + * @return 0 on success, -1 on error. + * + * @throws 0 The job is not in the queue. + */ +int +remove_job(const char *jobno, int runjob) +{ + char *end; + char *buf = NULL; + size_t no = 0, off = sizeof(size_t), n; + ssize_t r; + struct stat attr; + struct job job; + struct job *job_full = NULL; + int rc = 0, saved_errno = 0; + + if (jobno) { + no = (errno = 0, strtoul)(jobno, &end, 10); + if (errno || *end || !isdigit(*jobno)) + return 0; + } + + t (flock(STATE_FILENO, LOCK_EX)); + t (fstat(STATE_FILENO, &attr)); + for (n = (size_t)(attr.st_size); off < n; off += sizeof(job) + job.n) { + t (preadn(STATE_FILENO, &job, sizeof(job), off) < (ssize_t)sizeof(job)); + if (!jobno || (job.no == no)) + goto found_it; + } + flock(STATE_FILENO, LOCK_UN); /* Failure isn't fatal. */ + return errno = 0, -1; + +found_it: + t (!(job_full = malloc(sizeof(job) + job.n))); + *job_full = job; + t (preadn(STATE_FILENO, job_full->payload, job.n, off + sizeof(job)) < (ssize_t)(job.n)); + n -= off + sizeof(job) + job.n; + t (!(buf = malloc(n))); + t (r = preadn(STATE_FILENO, buf, n, off + sizeof(job) + job.n), r < 0); + t (pwriten(STATE_FILENO, buf, (size_t)r, off) < 0); + t (ftruncate(STATE_FILENO, (off_t)r + (off_t)off)); + free(buf), buf = NULL; + fsync(STATE_FILENO); + + if (runjob) { + run_job_or_hook(job_full, runjob == 2 ? "expired" : "forced"); + rc = run_job_or_hook(job_full, NULL); + saved_errno = errno; + run_job_or_hook(job_full, rc ? "failure" : "success"); + rc = rc == 1 ? 0 : rc; + } else { + run_job_or_hook(job_full, "removed"); + } + + free(job_full); + flock(STATE_FILENO, LOCK_UN); /* Unlock late so that hooks are synchronised. Failure isn't fatal. */ + errno = saved_errno; + return rc; + +fail: + S(flock(STATE_FILENO, LOCK_UN), free(buf), free(job_full)); + return -1; +} + + +/** + * Get a `NULL`-terminated list of all queued jobs. + * + * @return A `NULL`-terminated list of all queued jobs. `NULL` on error. + */ +struct job ** +get_jobs(void) +{ + size_t off = sizeof(size_t), n, j = 0; + struct stat attr; + struct job **js = NULL; + struct job job; + int saved_errno; + + t (flock(STATE_FILENO, LOCK_SH)); + t (fstat(STATE_FILENO, &attr)); + n = (size_t)(attr.st_size); + t (!(js = malloc((n / sizeof(**js) + 1) * sizeof(*js)))); + while (off < n) { + t (preadn(STATE_FILENO, &job, sizeof(job), off) < (ssize_t)sizeof(job)); + off += sizeof(job); + t (!(js[j] = malloc(sizeof(job) + job.n))); + *(js[j]) = job; + t (preadn(STATE_FILENO, js[j++]->payload, job.n, off) < (ssize_t)(job.n)); + off += job.n; + } + t (flock(STATE_FILENO, LOCK_UN)); + return js[j] = NULL, js; + +fail: + saved_errno = errno; + flock(STATE_FILENO, LOCK_UN); + while (j--) free(js[j]); + free(js); + errno = saved_errno; + return NULL; +} + + +/** + * Duplicate a file descriptor, and + * open /dev/null to the old file descriptor. + * However, if `old` is 3 or greater, it will + * be closed rather than /dev/null. + * + * @param old The old file descriptor. + * @param new The new file descriptor. + * @return `new`, -1 on error. + */ +int +dup2_and_null(int old, int new) +{ + int fd = -1, saved_errno; + + if (old == new) return new; t (DUP2_AND_CLOSE(old, new)); + if (old >= 3) return new; t (fd = open("/dev/null", O_RDWR), fd == -1); + if (fd == old) return new; t (DUP2_AND_CLOSE(fd, old)); + return new; +fail: + return S(close(fd)), -1; +} + + +/** + * Create or open the state file. + * + * @param open_flags Flags (the second parameter) for `open`. + * @param state_path Output parameter for the state file's pathname. + * May be `NULL`; + * @return A file descriptor to the state file, -1 on error. + * + * @throws 0 `!(open_flags & O_CREAT)` and the file does not exist. + */ +int +open_state(int open_flags, char **state_path) +{ + const char *dir; + char *path; + int fd = -1, saved_errno; + + /* Create directory. */ + dir = getenv("XDG_RUNTIME_DIR"), dir = (dir ? dir : "/run"); + t (!(path = malloc(strlen(dir) * sizeof(char) + sizeof("/" PACKAGE "/state")))); + stpcpy(stpcpy(path, dir), "/" PACKAGE "/state"); + t (fd = open(path, open_flags, S_IRUSR | S_IWUSR), fd == -1); + + if (state_path) *state_path = path, path = NULL; + else free(path), path = NULL; +fail: + S(free(path)); + if (!(open_flags & O_CREAT) && ((errno == ENOENT) || (errno == ENOTDIR))) + errno = 0; + return fd; +} + + +/** + * Let the daemon know that it may need to + * update the timers, and perhaps exit. + * + * @param start Start the daemon if it is not running? + * @param name The name of the process. + * @return 0 on success, -1 on error. + */ +int +poke_daemon(int start, const char *name) +{ + char *path = NULL; + const char *dir; + pid_t pid; + int fd = -1, status, saved_errno; + + /* Get the lock file's pathname. */ + dir = getenv("XDG_RUNTIME_DIR"), dir = (dir ? dir : "/run"); + t (!(path = malloc(strlen(dir) * sizeof(char) + sizeof("/" PACKAGE "/lock")))); + stpcpy(stpcpy(path, dir), "/" PACKAGE "/lock"); + + /* Any daemon listening? */ + fd = open(path, O_RDONLY); + if (fd == -1) { + t ((errno != ENOENT) && (errno != ENOTDIR)); + } else { + if (flock(fd, LOCK_SH | LOCK_NB /* and LOCK_DRY if that was ever added... */)) + t (start = 0, errno != EWOULDBLOCK); + else + flock(fd, LOCK_UN); + t (read(fd, &pid, sizeof(pid)) < (ssize_t)sizeof(pid)); + close(fd), fd = -1; + } + + /* Start daemon if not running, otherwise poke it. */ + if (start) { + switch ((pid = fork())) { + case -1: + goto fail; + case 0: + execl(BINDIR "/satd", BINDIR "/satd", NULL); + perror(name); + exit(1); + default: + t (waitpid(pid, &status, 0) != pid); + t (errno = 0, status); + break; + } + } else { + t (kill(pid, SIGCHLD)); + } + + return 0; +fail: + return S(close(fd), free(path)), -1; +} + + +/** + * Construct the pathname for the hook script. + * + * @param env The environment variable to use for the beginning + * of the pathname, `NULL` for the home directory. + * @param suffix The rest of the pathname. + * @return The pathname. + * + * @throws 0 The environment variable is not set, or, if `env` is + * `NULL` the user is root or homeless. + */ +static char * +hookpath(const char *env, const char *suffix) +{ + struct passwd *pwd; + const char *prefix = NULL; + char *path; + + if (env) { + prefix = getenv(env); + } else if (getuid()) { + pwd = getpwuid(getuid()); + prefix = pwd ? pwd->pw_dir : NULL; + } + if (!prefix || !*prefix) + return errno = 0, NULL; + + t (!(path = malloc((strlen(prefix) + strlen(suffix) + 1) * sizeof(char)))); + stpcpy(stpcpy(path, prefix), suffix); +fail: + return path; +} + + +/** + * Set SAT_HOOK_PATH. + * + * @return 0 on success, -1 on error. + */ +int +set_hookpath(void) +{ +#define HOOKPATH(PRE, SUF) \ + t (path = path ? path : hookpath(PRE, SUF), !path && errno) + char *path = NULL; + int saved_errno; + if (!getenv("SAT_HOOK_PATH")) { + HOOKPATH("XDG_CONFIG_HOME", "/sat/hook"); + HOOKPATH("HOME", "/.config/sat/hook"); + HOOKPATH(NULL, "/.config/sat/hook"); + t (setenv("SAT_HOOK_PATH", path ? path : "/etc/sat/hook", 1)); + } + return free(path), 0; +fail: + return S(free(path)), -1; +} + |