diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/README | 4 | ||||
-rw-r--r-- | src/daemon.c | 58 | ||||
-rw-r--r-- | src/daemon.h | 38 | ||||
-rw-r--r-- | src/satd-diminished.c | 1 | ||||
-rw-r--r-- | src/satd-list.c | 241 | ||||
-rw-r--r-- | src/satd.c | 57 | ||||
-rw-r--r-- | src/satq.c | 229 |
7 files changed, 324 insertions, 304 deletions
@@ -18,10 +18,10 @@ parse_time.[ch] Use by sat.c to parse the time argument. daemonise.[ch] From <http://github.com/maandree/slibc>; daemonisation of the process. Used by satd.c -client.[ch] Used by sat{,q,r,rm}.c, code for communicating +client.[ch] Used by sat{,r,rm}.c, code for communicating with satd, starts satd transparently if necessary. -daemon.[ch] Used by satd*.c, some shared code for daemons objects. +daemon.[ch] Used by sat{q,d*}.c, some shared code for daemons objects. common.h Used by sat{,q,r,rm,d*}.c, some shared code. Included via client.h and daemon.h. diff --git a/src/daemon.c b/src/daemon.c index ee05cf7..ab97dff 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -415,3 +415,61 @@ fail: 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: + saved_errno = errno, close(fd), errno = saved_errno; + return -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: + saved_errno = errno, free(path), errno = saved_errno; + if (!(open_flags & O_CREAT) && ((errno == ENOENT) || (errno == ENOTDIR))) + errno = 0; + return fd; +} + diff --git a/src/daemon.h b/src/daemon.h index cb6736a..dc8a355 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -125,6 +125,20 @@ fail: \ goto done; \ (void) argc +/** + * Call `CALL` which returns a file descriptor. + * Than make sure that the file descriptor's + * number is `WANT`. Go to `fail' on error. + * + * @param FD:int variable The variable where the file descriptor shall be stored. + * @param WANT:int The file descriptor the file should have. + * @parma CALL:int call Call to function that creates and returns the file descriptor. + */ +#define GET_FD(FD, WANT, CALL) \ + t (FD = CALL, FD == -1); \ + t (dup2_and_null(FD, WANT) == -1); \ + FD = WANT + /** @@ -238,3 +252,27 @@ int remove_job(const char *jobno, int runjob); */ struct job **get_jobs(void); +/** + * 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); + +/** + * 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); + diff --git a/src/satd-diminished.c b/src/satd-diminished.c index c8c0b53..845fa13 100644 --- a/src/satd-diminished.c +++ b/src/satd-diminished.c @@ -135,7 +135,6 @@ spawn(int command, int fd, char *argv[], char *envp[]) switch (command) { IMAGE(SAT_QUEUE, "add"); IMAGE(SAT_REMOVE, "rm"); - IMAGE(SAT_PRINT, "list"); IMAGE(SAT_RUN, "run"); IMAGE(-1, "timer"); default: diff --git a/src/satd-list.c b/src/satd-list.c deleted file mode 100644 index 12c06a7..0000000 --- a/src/satd-list.c +++ /dev/null @@ -1,241 +0,0 @@ -/** - * 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 "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) -#define N(I, S, B, Q) (I*in + S*sn + B*bn + Q*qn + rn) - - size_t in = 0; /* < ' ' or 127 */ - 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 (*s == 127) in++; - else if (UNSAFE(*s)) sn++; - else if (*s == '\\') bn++; - else if (*s == '\'') qn++; - else rn++; - } - if (N(1, 1, 1, 1) == rn) - return strdup(rn ? str : "''"); - - n = in ? (N(4, 1, 2, 2) + 3) : (N(0, 1, 1, 4) + 2); - t (!(rc = malloc((n + 1) * sizeof(char)))); - rc[i += !!in] = '$'; - rc[i += 1] = '\''; - if (in == 0) { - for (s = (const unsigned char *)str; *s; s++) { - rc[i++] = (char)*s; - if (*s == '\'') - rc[i++] = '\\', rc[i++] = '\'', rc[i++] = '\''; - } - } else { - for (s = (const unsigned char *)str; *s; s++) { - if ((*s < ' ') || (*s == 127)) { - 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; - } - } - 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 secs, mins, hours, sd = 0, md = 0, hd = 0; - secs = (int)(s % 60), s /= 60; - mins = (int)(s % 60), s /= 60; - hours = (int)(s % 24), s /= 24; - if (s) hd++, buf += sprintf(buf, "%llid", (long long int)s); - if (hd | hours) md++, buf += sprintf(buf, "%0*i:", ++hd, hours); - if (md | mins) sd++, buf += sprintf(buf, "%0*i:", ++md, mins); - /*just for alignment*/ buf += sprintf(buf, "%0*i:", ++sd, secs); -} - - -/** - * 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) -{ -#define FIX_NSEC(T) (((T)->tv_nsec < 0L) ? ((T)->tv_sec -= 1, (T)->tv_nsec += 1000000000L) : 0L) -#define ARRAY(LIST) \ - for (arg = LIST; *arg; arg++) { \ - free(qstr); \ - t (!(qstr = quote(*arg))); \ - t (send_string(SOCK_FILENO, STDOUT_FILENO, " ", qstr, NULL)); \ - } - - struct tm *tm; - struct timespec rem; - const char *clk; - char rem_s[3 * sizeof(time_t) + sizeof("d00:00:00")]; - char *qstr = NULL; - char *wdir = 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("-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_sec; - rem.tv_nsec = job->ts.tv_nsec - rem.tv_nsec; - FIX_NSEC(&rem); - 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. */ - if (job->clk == CLOCK_REALTIME) { - t (!(tm = localtime(&(job->ts.tv_sec)))); - strftime(timestr_a, sizeof(timestr_a), "%Y-%m-%d %H:%M:%S", tm); - } else { - strduration(timestr_a, job->ts.tv_sec); - } - 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)))); /* Includes wdir. */ - - /* Send message. */ - t (!(qstr = quote(args[0]))); - t (!(wdir = quote(envp[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 wdir: ", wdir, - "\n argv:", NULL)); - free(qstr); - ARRAY(argv); t (send_string(SOCK_FILENO, STDOUT_FILENO, "\n envp:", NULL)); - ARRAY(envp + 1); t (send_string(SOCK_FILENO, STDOUT_FILENO, "\n\n", NULL)); - -done: - saved_errno = errno; - free(qstr), free(args), free(argv), free(wdir), 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; -} - @@ -87,32 +87,6 @@ fail: /** - * Create the state file. - * - * @param state_path Output parameter for the state file's pathname. - * @return A file descriptor to the state file, -1 on error. - */ -static int -create_state(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, O_RDWR | O_CREAT /* but not O_EXCL or O_TRUNC */, S_IRUSR | S_IWUSR), fd == -1); - *state_path = path, path = NULL; - -fail: - saved_errno = errno, free(path), errno = saved_errno; - return fd; -} - - -/** * Create and lock the lock file, and its directory. * * @return A file descriptor to the lock file, -1 on error. @@ -187,31 +161,6 @@ fail: /** - * 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. - */ -static 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: - saved_errno = errno, close(fd), errno = saved_errno; - return -1; -} - - -/** * The sat daemon initialisation. * * @param argc Any value in [0, 2] is accepted. @@ -224,10 +173,6 @@ fail: int main(int argc, char *argv[]) { -#define GET_FD(FD, WANT, CALL) \ - t (FD = CALL, FD == -1); \ - t (dup2_and_null(FD, WANT) == -1); \ - FD = WANT #define HOOKPATH(PRE, SUF) \ t (path = path ? path : hookpath(PRE, SUF), !path && errno) @@ -253,7 +198,7 @@ main(int argc, char *argv[]) /* Open/create lock file and state file, and create socket. */ GET_FD(lock, LOCK_FILENO, create_lock()); - GET_FD(state, STATE_FILENO, create_state(&path)); + GET_FD(state, STATE_FILENO, open_state(O_RDWR | O_CREAT, &path)); GET_FD(sock, SOCK_FILENO, create_socket(&address)); /* Create timers. */ @@ -19,7 +19,8 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#include "client.h" +#include "daemon.h" +#include <stdarg.h> @@ -29,6 +30,210 @@ USAGE(NULL) /** + * 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) +#define N(I, S, B, Q) (I*in + S*sn + B*bn + Q*qn + rn) + + size_t in = 0; /* < ' ' or 127 */ + 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 (*s == 127) in++; + else if (UNSAFE(*s)) sn++; + else if (*s == '\\') bn++; + else if (*s == '\'') qn++; + else rn++; + } + if (N(1, 1, 1, 1) == rn) + return strdup(rn ? str : "''"); + + n = in ? (N(4, 1, 2, 2) + 3) : (N(0, 1, 1, 4) + 2); + t (!(rc = malloc((n + 1) * sizeof(char)))); + rc[i += !!in] = '$'; + rc[i += 1] = '\''; + if (in == 0) { + for (s = (const unsigned char *)str; *s; s++) { + rc[i++] = (char)*s; + if (*s == '\'') + rc[i++] = '\\', rc[i++] = '\'', rc[i++] = '\''; + } + } else { + for (s = (const unsigned char *)str; *s; s++) { + if ((*s < ' ') || (*s == 127)) { + 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; + } + } + 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 secs, mins, hours, sd = 0, md = 0, hd = 0; + secs = (int)(s % 60), s /= 60; + mins = (int)(s % 60), s /= 60; + hours = (int)(s % 24), s /= 24; + if (s) hd++, buf += sprintf(buf, "%llid", (long long int)s); + if (hd | hours) md++, buf += sprintf(buf, "%0*i:", ++hd, hours); + if (md | mins) sd++, buf += sprintf(buf, "%0*i:", ++md, mins); + /*just for alignment*/ buf += sprintf(buf, "%0*i", ++sd, secs); +} + + +/** + * Prints a series of strings without any restrict + * (in constrast to the `printf` function) of the + * length of the strings. + * + * @param s... The strings to print. `NULL`-terminated. + * @return 0 on success, -1 on error. + */ +static int +print(const char *s, ...) +{ + va_list args; + size_t i, n = 0; + ssize_t r = 1; + va_start(args, s); + do + for (i = 0, n = strlen(s); (r > 0) && (i < n); i += (size_t)r) + r = write(STDOUT_FILENO, s + i, n - i); + while ((r > 0) && ((s = va_arg(args, const char *)))); + va_end(args); + return r > 0 ? 0 : -1; +} + + +/** + * Dump a job to the socket. + * + * @param job The job. + * @return 0 on success, -1 on error. + */ +static int +print_job(struct job *job) +{ +#define FIX_NSEC(T) (((T)->tv_nsec < 0L) ? ((T)->tv_sec -= 1, (T)->tv_nsec += 1000000000L) : 0L) +#define ARRAY(LIST) \ + for (arg = LIST; *arg; arg++) { \ + free(qstr); \ + t (!(qstr = quote(*arg))); \ + t (print(" ", qstr, NULL)); \ + } + + struct tm *tm; + struct timespec rem; + const char *clk; + char rem_s[3 * sizeof(time_t) + sizeof("d00:00:00")]; + char *qstr = NULL; + char *wdir = 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("-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_sec; + rem.tv_nsec = job->ts.tv_nsec - rem.tv_nsec; + FIX_NSEC(&rem); + 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. */ + if (job->clk == CLOCK_REALTIME) { + t (!(tm = localtime(&(job->ts.tv_sec)))); + strftime(timestr_a, sizeof(timestr_a), "%Y-%m-%d %H:%M:%S", tm); + } else { + strduration(timestr_a, job->ts.tv_sec); + } + 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)))); /* Includes wdir. */ + + /* Send message. */ + t (!(qstr = quote(args[0]))); + t (!(wdir = quote(envp[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 (print(line, qstr, + "\n time: ", timestr_a, ".", timestr_b, + "\n wdir: ", wdir, + "\n argv:", NULL)); + ARRAY(argv); t (print("\n envp:", NULL)); + ARRAY(envp + 1); t (print("\n\n", NULL)); + +done: + saved_errno = errno; + free(qstr), free(args), free(argv), free(wdir), free(envp); + errno = saved_errno; + return rc; +fail: + rc = -1; + goto done; +} + + +/** * Print all queued jobs. * * @param argc Should be 1 or 0. @@ -36,15 +241,31 @@ USAGE(NULL) * @return 0 The process was successful. * @return 1 The process failed queuing the job. * @return 2 User error, you do not know what you are doing. - * @return 3 satd(1) failed. */ int main(int argc, char *argv[]) { + struct job **jobs = NULL; + struct job **job; + int state = -1; + if (argc > 0) argv0 = argv[0]; if (argc > 1) usage(); - SEND(SAT_PRINT, (size_t)0, NULL); - END(NULL); + GET_FD(state, STATE_FILENO, open_state(O_RDONLY, NULL)); + t (!(jobs = get_jobs())); + for (job = jobs; *job; job++) + t (print_job(*job)); + + errno = 0; +fail: + if (errno) + perror(argv[0]); + for (job = jobs; jobs && *job; job++) + free(*job); + free(jobs); + if (state >= 0) + close(state); + return !!errno; } |