aboutsummaryrefslogtreecommitdiffstats
path: root/src/common.c
diff options
context:
space:
mode:
authorMattias Andrée <maandree@member.fsf.org>2016-01-01 22:34:34 +0100
committerMattias Andrée <maandree@member.fsf.org>2016-01-01 22:34:34 +0100
commit4cba520a67e615b43dc22994670a904daf8643ae (patch)
tree6649c99fa94d8b7842a8ca7df7478ccf722d4c67 /src/common.c
parentremove satd-rm and satd-r and let satrm and satr do everything (not done yet) (diff)
downloadsat-4cba520a67e615b43dc22994670a904daf8643ae.tar.gz
sat-4cba520a67e615b43dc22994670a904daf8643ae.tar.bz2
sat-4cba520a67e615b43dc22994670a904daf8643ae.tar.xz
massive simpliciation and small bug fixes (not tested yet)
Signed-off-by: Mattias Andrée <maandree@member.fsf.org>
Diffstat (limited to 'src/common.c')
-rw-r--r--src/common.c501
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;
+}
+