/** * Copyright © 2015 Mattias Andrée * * 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 "daemon.h" /** * Quote a string, in shell (Bash-only if necessary) compatible * format, if necessary. Here, just adding quotes around all not * do. The string must be single line, and there must not be * any invisible characters; it should be possible to copy * a string from the terminal by marking it, hence all of this * ugliness. * * @param str The string. * @return Return a safe representation of the string, * `NULL` on error. */ static char * quote(const char *str) { #define UNSAFE(c) strchr(" \"$()[]{};|&^#!?*~`<>", c) size_t in = 0; /* < ' ' */ size_t sn = 0; /* in UNSAFE */ size_t bn = 0; /* = '\\' */ size_t qn = 0; /* = '\'' */ size_t rn = 0; /* other */ size_t n, i = 0; const unsigned char *s; char *rc = NULL; for (s = (const unsigned char *)str; *s; s++) { if (*s < ' ') in++; else if (UNSAFE(*s)) sn++; else if (*s == '\\') bn++; else if (*s == '\'') qn++; else rn++; } switch (in ? 2 : (sn + bn + qn) ? 1 : 0) { case 0: return strdup(str); case 1: n = rn + sn + bn + 4 * qn + 4 * in + 2; t (!(rc = malloc((n + 1) * sizeof(char)))); rc[i++] = '\''; for (s = (const unsigned char *)str; *s; s++) { rc[i++] = (char)*s; if (*s == '\'') rc[i++] = '\\', rc[i++] = '\'', rc[i++] = '\''; } break; default: n = 4 * in + rn + sn + 2 * bn + 2 * qn + 3; t (!(rc = malloc((n + 1) * sizeof(char)))); rc[i++] = '$'; rc[i++] = '\''; for (s = (const unsigned char *)str; *s; s++) { if (*s < ' ') { rc[i++] = '\\'; rc[i++] = 'x'; rc[i++] = "0123456789ABCDEF"[(*s >> 4) & 15]; rc[i++] = "0123456789ABCDEF"[(*s >> 0) & 15]; } else if (strchr("\\'", *s)) rc[i++] = '\\', rc[i++] = (char)*s; else rc[i++] = (char)*s; } break; } rc[i++] = '\''; rc[i] = '\0'; fail: return rc; } /** * Create a textual representation of the a duration. * * @param buffer Output buffer, with a size of at least * 10 `char`:s plus enough to encode a `time_t`. * @param s The duration in seconds. */ static void strduration(char *buffer, time_t s) { char *buf = buffer; int seconds, minutes, hours; seconds = (int)(s % 60), s /= 60; minutes = (int)(s % 60), s /= 60; hours = (int)(s % 24), s /= 24; if (s) { buf += sprintf(buf, "%llid", (long long int)s); buf += sprintf(buf, "%02i:", hours); buf += sprintf(buf, "%02i", minutes); } else if (hours) { buf += sprintf(buf, "%i:", hours); buf += sprintf(buf, "%02i", minutes); } else if (minutes) { buf += sprintf(buf, "%i", minutes); } sprintf(buf, "%02i", seconds); } /** * Dump a job to the socket. * * @param job The job. * @return 0 on success, -1 on error. */ static int send_job_human(struct job *job) { struct tm *tm; struct timespec rem; const char *clk; char rem_s[3 * sizeof(time_t) + sizeof("d00:00:00")]; char *qstr = NULL; char line[sizeof("job: %zu clock: unrecognised argc: %i remaining: , argv[0]: ") + 3 * sizeof(size_t) + 3 * sizeof(int) + sizeof(rem_s) + 9]; char timestr_a[sizeof("0000-00-00 00:00:00") + 3 * sizeof(time_t)]; char timestr_b[10]; char **args = NULL; char **arg; char **argv = NULL; char **envp = NULL; size_t argsn; int rc = 0, saved_errno; /* Get remaining time. */ if (clock_gettime(job->clk, &rem)) return errno == EINVAL ? 0 : -1; rem.tv_sec -= job->ts.tv_sec; rem.tv_nsec -= job->ts.tv_nsec; if (rem.tv_nsec < 0) { rem.tv_sec -= 1; rem.tv_nsec += 1000000000L; } if (rem.tv_sec < 0) /* This job will be removed momentarily, do not list it. (To simply things.) */ return 0; /* Get clock name. */ switch (job->clk) { case CLOCK_REALTIME: clk = "walltime"; break; case CLOCK_BOOTTIME: clk = "boottime"; break; default: clk = "unrecognised"; break; } /* Get textual representation of the remaining time. (Seconds only.) */ strduration(rem_s, rem.tv_sec); /* Get textual representation of the expiration time. */ switch (job->clk) { case CLOCK_REALTIME: t (!(tm = localtime(&(job->ts.tv_sec)))); strftime(timestr_a, sizeof(timestr_a), "%Y-%m-%d %H:%M:%S", tm); break; default: strduration(timestr_a, job->ts.tv_sec); break; } sprintf(timestr_b, "%09li", job->ts.tv_nsec); /* Get arguments. */ 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)))); /* Send message. */ t (!(qstr = quote(args[0]))); sprintf(line, "job: %zu clock: %s argc: %i remaining: %s.%09li argv[0]: ", job->no, clk, job->argc, rem_s, rem.tv_nsec); t (send_string(SOCK_FILENO, STDOUT_FILENO, line, qstr, "\n", " time: ", timestr_a, ".", timestr_b, "\n", " argv:", NULL)); for (arg = argv; *arg; arg++) { free(qstr); t (!(qstr = quote(*arg))); t (send_string(SOCK_FILENO, STDOUT_FILENO, " ", qstr, NULL)); } free(qstr), qstr = NULL; t (send_string(SOCK_FILENO, STDOUT_FILENO, "\n envp:", NULL)); for (arg = envp; *arg; arg++) { t (!(qstr = quote(*arg))); t (send_string(SOCK_FILENO, STDOUT_FILENO, " ", qstr, NULL)); free(qstr); } qstr = NULL; t (send_string(SOCK_FILENO, STDOUT_FILENO, "\n\n", NULL)); done: saved_errno = errno; free(qstr); free(args); free(argv); free(envp); errno = saved_errno; return rc; fail: rc = -1; goto done; } /** * Subroutine to the sat daemon: list jobs. * * @param argc Should be 3. * @param argv The name of the process, the pathname of the socket, * and the pathname to the state file. * @return 0 The process was successful. * @return 1 The process failed queuing the job. */ int main(int argc, char *argv[]) { size_t n = 0; char *message = NULL; struct job **jobs = NULL; struct job **job; DAEMON_PROLOGUE; /* Receive and validate message. */ t (readall(SOCK_FILENO, &message, &n) || n); /* Perform action. */ t (!(jobs = get_jobs())); for (job = jobs; *job; job++) t (send_job_human(*job)); DAEMON_CLEANUP_START; for (job = jobs; *job; job++) free(*job); free(jobs); free(message); DAEMON_CLEANUP_END; }