GCC Code Coverage Report


.
File: para.c
Date: 2024-12-07 13:12:30
Lines:
811/919
88.2%
Functions:
30/30
100.0%
Branches:
498/692
72.0%

Line Branch Exec Source
1 /*
2 * Run jobs in parallel
3 *
4 * Copyright 2023 and 2024 Odin Kroeger.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License,
9 * or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #if !defined(PARA_C_AS_HDR) || !PARA_C_AS_HDR || !defined PARA_C || \
21 (defined(LINT) && LINT)
22 #define PARA_C
23
24 #if !defined(PARA_C_AS_HDR) || !PARA_C_AS_HDR
25 #define _ISOC99_SOURCE 1
26 #if defined(EXTENSIONS) && EXTENSIONS
27 #define _BSD_SOURCE 1
28 #define _DARWIN_C_SOURCE 1
29 #define _DEFAULT_SOURCE 1
30 #define _GNU_SOURCE 1
31 #else
32 #define _POSIX_C_SOURCE 200809L
33 #define _XOPEN_SOURCE 700
34 #endif /* defined(EXTENSIONS) && EXTENSIONS */
35 #endif /* !defined(PARA_C_AS_HDR) || !PARA_C_AS_HDR */
36
37 #include <sys/time.h>
38 #include <sys/types.h>
39 #include <sys/wait.h>
40
41 #include <assert.h>
42 #include <ctype.h>
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <float.h>
46 #include <inttypes.h>
47 #include <limits.h>
48 #include <signal.h>
49 #include <spawn.h>
50 #include <stdarg.h>
51 #include <stdbool.h>
52 #include <stddef.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57
58
59 /*
60 * Constants
61 */
62
63 /* ANSI escape sequence for clearing the current line */
64 #define CLEARLN "\033" "[0K"
65
66 /* Default time that jobs may take to terminate (in secs) */
67 #ifndef DEF_GRACE
68 #define DEF_GRACE 0
69 #else
70 #if DEF_GRACE < 0
71 #error DEF_GRACE must not be negative
72 #elif DEF_GRACE > INT_MAX
73 #error DEF_GRACE is greater than INT_MAX
74 #endif
75 #endif
76
77 /* Default maximum load; 0 means "no limit" */
78 #ifndef DEF_LOAD
79 #define DEF_LOAD 2.0
80 #else
81 #if DEF_LOAD < 0
82 #error DEF_LOAD must not be negative
83 #elif DEF_LOAD > DBL_MAX
84 #error DEF_LOAD is greater than DBL_MAX
85 #endif
86 #endif
87
88 /* Default number of jobs to run in pmrallel */
89 #ifndef DEF_NJOBS
90 #define DEF_NJOBS 2
91 #else
92 #if DEF_NJOBS < 1
93 #error DEF_NJOBS must be greater than zero
94 #elif DEF_NJOBS > PTRDIFF_MAX
95 #error DEF_NJOBS is greater than PTRDIFF_MAX
96 #endif
97 #endif
98
99 /* Default timeout (in secs); 0 means "forever" */
100 #ifndef DEF_TIMEOUT
101 #define DEF_TIMEOUT 0
102 #else
103 #if DEF_TIMEOUT < 0
104 #error DEF_TIMEOUT must not be negative
105 #elif DEF_TIMEOUT > LONG_MAX
106 #error DEF_TIMEOUT is greater than UINT_MAX
107 #endif
108 #endif
109
110 /* Exit status for usage errors */
111 #define EXIT_USAGE 2
112
113 /* Ensure EXTENSIONS is defined */
114 #ifndef EXTENSIONS
115 #define EXTENSIONS 0
116 #endif
117
118 /* Ensure HAVE_GETLOADAVG is defined */
119 #ifndef HAVE_GETLOADAVG
120 #define HAVE_GETLOADAVG 0
121 #endif
122
123 /* Half a second in microseconds */
124 #define HALF_SECOND (MAX_MICRO / 2)
125
126 /* Number of words to allocate memory for initially */
127 #if defined(NDEBUG) && NDEBUG
128 #define INIT_NWORDS 32U
129 #else
130 #define INIT_NWORDS 0U
131 #endif
132
133 /* Highest non-signal exit status */
134 #define MAX_EXIT 128
135
136 /* Maximum message format size, including the null terminator */
137 #define MAX_MSG_SIZE 128
138
139 /* Number of micro units per unit (10^6) */
140 #define MAX_MICRO 1000000L
141
142 /* Maximum number of jobs to run in parallel */
143 #ifndef MAX_NJOBS
144 #define MAX_NJOBS 64
145 #else
146 #if MAX_NJOBS < 1
147 #error MAX_NJOBS must be greater than zero
148 #elif MAX_NJOBS > PTRDIFF_MAX
149 #error MAX_NJOBS is greater than PTRDIFF_MAX
150 #elif MAX_NJOBS > INT_MAX
151 #error MAX_NJOBS is greater than INT_MAX
152 #endif
153 #endif
154
155 /* Maximum number of environment variables */
156 #ifndef MAX_NVARS
157 #define MAX_NVARS 256
158 #else
159 #if MAX_NVARS < 1
160 #error MAX_NVARS must not be smaller than one
161 #elif MAX_NVARS > PTRDIFF_MAX
162 #error MAX_NVARS is greater than PTRDIFF_MAX
163 #endif
164 #endif
165
166 /* Maximum number of elements a Words array may hold */
167 #if SIZE_MAX > PTRDIFF_MAX
168 #define MAX_NWORDS ((size_t) PTRDIFF_MAX)
169 #else
170 #define MAX_NWORDS SIZE_MAX
171 #endif
172
173 /* Maximum program name length */
174 #define MAX_PROGNAME_LEN 14
175
176 /* Maximum string size, including the null terminator */
177 #ifndef MAX_STR_SIZE
178 #define MAX_STR_SIZE 8192U
179 #else
180 #if MAX_STR_SIZE < 1024
181 #error MAX_STR_SIZE is smaller than 1024
182 #elif MAX_STR_SIZE > MAX_NWORDS
183 #error MAX_STR_SIZE is greater than MAX_NWORDS
184 #endif
185 #endif
186
187 /* Ensure NDEBUG is defined */
188 #ifndef NDEBUG
189 #define NDEBUG 0
190 #endif
191
192 /* Program name */
193 #define PROGNAME "para"
194
195 /* Program version */
196 #define PROGVERS "0.12"
197
198 /* Clang version */
199 #if defined(__clang__) && __clang__
200 #define VERSION_CLANG (__clang_major__ * 10000 + \
201 __clang_minor__ * 100 + \
202 __clang_patchlevel__)
203 #else
204 #define VERSION_CLANG 0
205 #endif
206
207 /* GCC version */
208 #if defined(__GNUC__) && __GNUC__
209 #define VERSION_GCC (__GNUC__ * 10000 + \
210 __GNUC_MINOR__ * 100 + \
211 __GNUC_PATCHLEVEL__)
212 #else
213 #define VERSION_GCC 0
214 #endif
215
216
217 /*
218 * Macros
219 */
220
221 /* Only use C attributes if GNU C is supported */
222 #if VERSION_GCC < 30000
223 #define __attribute__(attr)
224 #endif
225
226 /* Make some declarations global if built as object or included as header */
227 #if (defined(PARA_C_AS_OBJ) && PARA_C_AS_OBJ) || \
228 (defined(PARA_C_AS_HDR) && PARA_C_AS_HDR)
229 #define exportable
230 #else
231 #define exportable static
232 #endif
233
234 /* Get the larger of two values */
235 #define MAX(x, y) ((x) > (y) ? (x) : (y))
236
237 /* Dump coverage data */
238 #if defined(COV) && COV && VERSION_GCC >= 110000
239 #define gcov_dump() __gcov_dump()
240 #else
241 #define gcov_dump()
242 #endif
243
244 /* Check if an integer is greater than PTRDIFF_MAX */
245 #if INT_MAX > PTRDIFF_MAX
246 #define int_exceeds_ptr(num) ((uintmax_t) (num) > (uintmax_t) PTRDIFF_MAX)
247 #else
248 #define int_exceeds_ptr(num) false
249 #endif
250
251 /* Check if a long integer is greater than PTRDIFF_MAX */
252 #if LONG_MAX > PTRDIFF_MAX
253 #define long_exceeds_ptr(num) ((uintmax_t) (num) > (uintmax_t) PTRDIFF_MAX)
254 #else
255 #define long_exceeds_ptr(num) false
256 #endif
257
258 /* Check if a pointer difference is greater than SIZE_MAX */
259 #if PTRDIFF_MAX > SIZE_MAX
260 #define ptr_exceeds_size(ptr) ((uintmax_t) (ptr) > (uintmax_t) SIZE_MAX)
261 #else
262 #define ptr_exceeds_size(ptr) false
263 #endif
264
265 /* Check if a size is greater than PTRDIFF_MAX */
266 #if SIZE_MAX > PTRDIFF_MAX
267 #define size_exceeds_ptr(size) ((uintmax_t) (size) > (uintmax_t) PTRDIFF_MAX)
268 #else
269 #define size_exceeds_ptr(size) false
270 #endif
271
272
273 /*
274 * Data types
275 */
276
277 /* What to do when a job exits with a non-zero status */
278 typedef enum {
279 EH_IGNORE, /* Ingore */
280 EH_TERM, /* Terminate jobs and exit */
281 EH_WAIT /* Wait for running jobs and exit */
282 } ErrorHandler;
283
284 /* Record for started jobs */
285 typedef struct {
286 pid_t pid; /* Process ID */
287 char *comm; /* Command name */
288 int status; /* Exit status */
289 volatile sig_atomic_t exited; /* Job has exited? */
290 } Job;
291
292 /* Line termination actions */
293 typedef enum {
294 NL_NONE = 0, /* Do nothing */
295 NL_PRINT, /* Print newline if requested */
296 NL_REQUEST /* Request a newline */
297 } NewlineAction;
298
299 /* Array of words */
300 typedef struct {
301 char **vector; /* Words */
302 size_t count; /* Word count */
303 size_t max; /* Maximum words the vector can hold */
304 } Words;
305
306
307 /*
308 * Prototypes
309 */
310
311 /*
312 * Dump coverage data
313 */
314 #if defined(COV) && COV && VERSION_GCC >= 110000
315 void __gcov_dump(void);
316 #endif
317
318 /*
319 * Clear and allocate memory, but check for overflow.
320 */
321 __attribute__((malloc, warn_unused_result))
322 exportable void *calloc_s(size_t nelems, size_t elemwd);
323
324 /*
325 * Flush standard output and standard error.
326 */
327 exportable void flush(void);
328
329 /*
330 * Return the number of processors that are online.
331 *
332 * Return value:
333 * -1 _SC_NPROCESSORS_ONLN not available or EXTENSIONS unset.
334 * Otherwise Number of processors online.
335 */
336 __attribute__((warn_unused_result))
337 exportable long getnprocs(void);
338
339 /*
340 * Convert the current option argument into a number. If the argument is
341 * not a number, is smaller than the given minimum, or is greater than the
342 * given maximum, an error message is printed to standard error and the
343 * process exits with EXIT_USAGE.
344 *
345 * Side-effects:
346 * May write a message to standard error and terminate the process.
347 *
348 * Global variables:
349 * optarg Current option argument.
350 */
351 __attribute__((warn_unused_result))
352 exportable double getoptd(char opt, double min, double max);
353
354 /*
355 * Same as getoptd, but for long long integers.
356 */
357 __attribute__((warn_unused_result))
358 exportable long long getoptll(char opt, long long min, long long max);
359
360 /*
361 * If standard error is a terminal, print the program name, a colon (":"),
362 * a space, a message, another space, the ANSI escape code to clear the
363 * remainder of the line, and a carriage return to standard error,
364 * then request a newline to be printed. Otherwise, do nothing.
365 *
366 * Constants:
367 * CLEARLN ANSI escape code to clear the line.
368 * PROGNAME Program name.
369 */
370 __attribute__((format(printf, 1, 2)))
371 exportable void info(const char *format, ...);
372
373 /*
374 * Signal processes.
375 *
376 * Caveats:
377 * "jobs" must contain no more than "njobs" elements.
378 * Supernumery elements are ignored.
379 *
380 * Return value:
381 * 0 Success.
382 * EINVAL Signal invalid.
383 * EPERM Not permitted to signal jobs.
384 * ESRCH Job does not exist.
385 */
386 __attribute__((warn_unused_result))
387 exportable int jobskill(ptrdiff_t njobs, const Job *jobs, int signo);
388
389 /*
390 * Stop the process as well as the given subprocesses.
391 * Stopped subprocesses are continued when the process is continued.
392 *
393 * Caveats:
394 * "jobs" must contain no more than "njobs" elements.
395 * Supernumery elements are ignored.
396 *
397 * Return value:
398 * 0 Success.
399 * EPERM Not permitted to signal jobs.
400 * ESRCH Job does not exist.
401 */
402 __attribute__((warn_unused_result))
403 exportable int jobststp(ptrdiff_t njobs, const Job *jobs);
404
405 /*
406 * Check which, if any, of the given processes has exited and, if some
407 * of them have, save their exit statuses and that they have exited into
408 * the given job descriptions, then return the number of jobs that
409 * have exited in "nexited".
410 *
411 * Caveats:
412 * "jobs" must contain no more than "njobs" elemets.
413 * Supernumery elements are ignored.
414 *
415 * Return value:
416 * 0 Success.
417 * EINVAL Job could not be found in "jobs".
418 */
419 __attribute__((warn_unused_result))
420 exportable int jobswait(ptrdiff_t njobs, Job *jobs, int *nexited);
421
422 /*
423 * Add a variable to a NULL-terminated array of variables. If a variable of
424 * the same name has already been set, that variable is replaced; otherwise,
425 * the variable is added to the end of the array and the counter pointed to
426 * by "nvars" is incremented. "nvars" must point to a counter that holds
427 * the current number of variables in "vars", sans the NULL terminator.
428 *
429 * Caveats:
430 * "vars" must have space for the variable to be added.
431 *
432 * Return value:
433 * 0 Success.
434 * EINVAL Malformed variable.
435 * EOVERFLOW Variable name would overflow size_t.
436 */
437 __attribute__((warn_unused_result))
438 exportable int putvar(ptrdiff_t *nvars, char **vars, char *var);
439
440 /*
441 * Store a signal in the global "caught".
442 *
443 * Module variables:
444 * caught Most recently caught signal.
445 */
446 exportable void sigcatch(int signo);
447
448 /*
449 * Store that CHLD has been caught in the global "caughtchild".
450 *
451 * Module variables:
452 * caughtchild Whether CHLD needs to be handled.
453 */
454 exportable void sigcatchchild(int signo);
455
456 /*
457 * Drop signal.
458 */
459 exportable void sigdrop(int signo);
460
461 /*
462 * Request a newline to be printed or print a newline if requested.
463 */
464 exportable void termln(NewlineAction req);
465
466 /*
467 * Compare two points in time.
468 *
469 * Return value:
470 * < 0 T1 < T2.
471 * 0 T1 == T2.
472 * > 0 T1 > T2.
473 */
474 __attribute__((warn_unused_result))
475 exportable int timevalcomp(const struct timeval *timep1,
476 const struct timeval *timep2);
477
478 /*
479 * Calculate the difference between two points in time.
480 *
481 * Return value:
482 * See timevalcomp.
483 */
484 __attribute__((warn_unused_result, unused))
485 exportable int timevaldiff(struct timeval *diff,
486 const struct timeval *timep1,
487 const struct timeval *timep2);
488
489 /*
490 * Round a time value to seconds.
491 *
492 * Return value:
493 * 0 Success.
494 * -1 Failure.
495 *
496 * Errors:
497 * EOVERFLOW Rounding would overflow time_t.
498 */
499 __attribute__((warn_unused_result))
500 exportable int timevalround(time_t *secs, const struct timeval *timep);
501
502 /*
503 * Copies a word to the given NULL-terminated array of words.
504 * The array is resized if needed, keeping the NULL terminator.
505 *
506 * Return value:
507 * 0 Success.
508 * EINVAL Vector or word is null.
509 * ENOMEM Not enough memory.
510 * EOVERFLOW Number of words would overflow ptrdiff_t or vector size.
511 */
512 __attribute__((warn_unused_result))
513 exportable int wordsadd(Words *words, char *word);
514
515 /*
516 * Free the memory used by the given array of words.
517 *
518 * Return value:
519 * 0 Success.
520 * EINVAL Vector is null.
521 */
522 exportable int wordsfree(Words *words);
523
524 /*
525 * Initialise the given array of words to hold the given number of words,
526 * sans the NULL terminator.
527 *
528 * Return value:
529 * 0 Success.
530 * ENOMEM Not enough memory.
531 * EOVERFLOW Number of words would overflow counter or vector.
532 */
533 __attribute__((warn_unused_result))
534 exportable int wordsinit(Words *words, size_t count);
535
536 /*
537 * Join words into a string that can be split back into the same words.
538 *
539 * Return value:
540 * Non-NULL The joint string.
541 * NULL An error occurred.
542 *
543 * Errors:
544 * EINVAL Vector is null.
545 * ENOMEM Not enough memory.
546 * EOVERFLOW Word too long or too many words.
547 */
548 __attribute__((malloc, warn_unused_result))
549 exportable char *wordsjoin(const Words *words);
550
551 /*
552 * Split the given string, which must be of the given length, into words and
553 * save the resulting words to the given buffer and pointers to those words in
554 * the given array. The array is terminated with NULL. Mimics wordexp.
555 *
556 * Return value:
557 * 0 Success.
558 * EINVAL Syntax error.
559 * ENOMEM Not enough memory.
560 * EOVERFLOW Number of words would overflow counter or vector.
561 *
562 * Caveats:
563 * The array MUST be uninitialised. The buffer MUST be large enough to
564 * hold all words, including their null terminators (i.e., it must be as
565 * large as the string). The buffer and the string may overlap if, and
566 * only if, the address of the buffer is smaller than or equal to the
567 * address of the string.
568 */
569 __attribute__((warn_unused_result))
570 exportable int wordsplit(Words *words, char *buf,
571 size_t slen, const char *str);
572
573 /*
574 * Resize the memory area pointed to by the given array of words so that
575 * it can hold the given number of words, sans the NULL terminator.
576 *
577 * Return value:
578 * 0 Success.
579 * ENOMEM Not enough memory.
580 * EOVERFLOW Number of words would overflow counter or vector.
581 */
582 exportable int wordsrealloc(Words *words, size_t count);
583
584
585 /*
586 * Private prototypes
587 */
588
589 /* Excise definitions if para.c is included as header */
590 #if !defined(PARA_C_AS_HDR) || !PARA_C_AS_HDR
591
592 /*
593 * Print a message to standard error and exit with the given status.
594 * Otherwise the same as warn.
595 */
596 __attribute__((format(printf, 2, 3), noreturn, unused))
597 static void err(int status, const char *format, ...);
598
599 /*
600 * Print a message to standard error and exit with the given status.
601 * Otherwise the same as warnx.
602 */
603 __attribute__((format(printf, 2, 3), noreturn, unused))
604 static void errx(int status, const char *format, ...);
605
606 /*
607 * Same warn, but with variable arguments.
608 */
609 __attribute__((unused))
610 static void vwarn(const char *format, va_list argp);
611
612 /*
613 * Same as warnx, but with variable arguments.
614 */
615 __attribute__((unused))
616 static void vwarnx(const char *format, va_list argp);
617
618 /*
619 * Same as warnx, but also print the current error.
620 *
621 * Module variables:
622 * errno Current error.
623 */
624 __attribute__((format(printf, 1, 2), unused))
625 static void warn(const char *format, ...);
626
627 /*
628 * Print the program name, a colon (":"), a space, a message, the ANSI escape
629 * code to clear the remainder of the line if standard error is a terminal, and
630 * a newline to standard error. If a newline has been requested, print one
631 * before the program name.
632 *
633 * Constants:
634 * CLEARLN ANSI escape code to clear the line.
635 * PROGNAME Program name.
636 */
637 __attribute__((format(printf, 1, 2), unused))
638 static void warnx(const char *format, ...);
639
640
641 /*
642 * Global variables
643 */
644
645 /* The environment */
646 extern char **environ;
647
648
649 /*
650 * Module variables
651 */
652
653 /* Signal handler for catching signals other than SIGCHLD */
654 static const struct sigaction catchact = {.sa_handler = sigcatch};
655
656 /* Default signal handler */
657 static const struct sigaction defaultact = {.sa_handler = SIG_DFL};
658
659 /* Most recently caught signal other than SIGCHLD */
660 exportable volatile sig_atomic_t caught = 0;
661
662 /* Does SIGCHLD need to be handlded? */
663 exportable volatile sig_atomic_t caughtchild = 0;
664
665
666 /*
667 * Main
668 */
669
670 /* Excise main when built as object */
671 #if !defined(PARA_C_AS_OBJ) || !PARA_C_AS_OBJ
672
673 int
674 181 main(int argc, char **argv)
675 {
676 181 int retval = EXIT_SUCCESS; /* Exit status */
677 sig_atomic_t oldcaught; /* Previously caught signal */
678
679 /*
680 * Init
681 */
682
683
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 181 times.
181 if (atexit(flush) != 0) {
684 err(EXIT_FAILURE, "atexit");
685 }
686
687
688 /*
689 * Options
690 */
691
692 /* RATS: ignore; access to nonerrors is bounds-checked */
693 181 bool nonerrors[MAX_EXIT] = {0}; /* Non-errors */
694 181 char *compose = NULL; /* Command format */
695 181 const char *placeholder = "{}"; /* Placeholder */
696 181 double maxload __attribute__((unused)) = DEF_LOAD; /* Maximum load */
697 181 ptrdiff_t njobs = DEF_NJOBS; /* Parallel jobs */
698 181 unsigned int timeout = DEF_TIMEOUT; /* Timeout */
699 181 int errfd = -1; /* FD for errors */
700 181 int grace = DEF_GRACE; /* Grace period */
701 181 int olderrfd = -1; /* Old FD for errors */
702 int opt; /* Option character */
703 181 bool quiet = false; /* Suppress output? */
704 181 bool verbose = false; /* Be verbose? */
705 181 ErrorHandler errhdl = EH_TERM; /* Error handler */
706
707 181 const long nprocs = getnprocs();
708
1/2
✓ Branch 0 taken 181 times.
✗ Branch 1 not taken.
181 if (nprocs > 2L) {
709 /* LONG_MAX is < DBL_MAX */
710 181 maxload = (double) nprocs - 1.0;
711 181 njobs = long_exceeds_ptr(nprocs) ? PTRDIFF_MAX : (ptrdiff_t) nprocs;
712 }
713
714 /* NOLINTNEXTLINE(misc-redundant-expression); not redundant */
715 if (!HAVE_GETLOADAVG || !EXTENSIONS) {
716 maxload = 0.0;
717 }
718
719
4/8
✓ Branch 0 taken 181 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 181 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 181 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 181 times.
181 if (argc == 0 || !argv || !*argv || **argv == '\0') {
720 errx(EXIT_FAILURE, "Empty argument vector");
721 }
722
723 /* RATS: ignore; para is not security-critical */
724
2/2
✓ Branch 0 taken 173 times.
✓ Branch 1 taken 122 times.
295 while ((opt = getopt(argc, argv, "Cc:e:hij:k:l:n:r:qt:Vvw")) != -1) {
725
16/16
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 30 times.
✓ Branch 7 taken 14 times.
✓ Branch 8 taken 31 times.
✓ Branch 9 taken 11 times.
✓ Branch 10 taken 5 times.
✓ Branch 11 taken 4 times.
✓ Branch 12 taken 10 times.
✓ Branch 13 taken 12 times.
✓ Branch 14 taken 2 times.
✓ Branch 15 taken 8 times.
173 switch (opt) {
726 7 case 'C':
727 7 (void) printf("DEF_GRACE=%d\n", DEF_GRACE);
728 7 (void) printf("DEF_LOAD=%f\n", DEF_LOAD);
729 7 (void) printf("DEF_NJOBS=%d\n", DEF_NJOBS);
730 7 (void) printf("DEF_TIMEOUT=%d\n", DEF_TIMEOUT);
731 7 (void) printf("EXTENSIONS=%d\n", EXTENSIONS);
732 7 (void) printf("HAVE_GETLOADAVG=%d\n", HAVE_GETLOADAVG);
733 7 (void) printf("MAX_NJOBS=%d\n", MAX_NJOBS);
734 7 (void) printf("MAX_NVARS=%d\n", MAX_NVARS);
735 7 (void) printf("MAX_NWORDS=%zu\n", MAX_NWORDS);
736 7 (void) printf("MAX_STR_SIZE=%u\n", MAX_STR_SIZE);
737 7 (void) printf("NDEBUG=%d\n", NDEBUG);
738 7 return EXIT_SUCCESS;
739 1 case 'V':
740 1 (void) printf(
741 "%s %s\n"
742 "Copyright 2023 and 2024 Odin Kroeger.\n"
743 "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl>\n"
744 "This is free software. You are free to change and redistribute it.\n"
745 "This program comes with NO WARRANTY, to the extent permitted by law.\n",
746 PROGNAME, PROGVERS);
747 1 return EXIT_SUCCESS;
748 1 case 'h':
749 1 (void) printf(
750 "para - run jobs in parallel\n\n"
751 "Usage: para [name=value...] command [...]\n"
752 " para -c format [name=value...] argument [...]\n"
753 "\n"
754 "Options:\n"
755 " -c format Compose commands by replacing {}'s in format with arguments.\n"
756 " -e fd Write non-job output, including errors, to fd.\n"
757 " -i Ignore errors.\n"
758 " -j n Run n jobs in parallel (default: %lld).\n"
759 " -k n Kill jobs that do not exit within n secs after termination.\n"
760 " -l n Only start jobs if the load average is < n (default: %.1f).\n"
761 " -n status Do not treat a job exiting with status as error.\n"
762 " -r str Use str as placeholder in format (default: {}).\n"
763 " -q Suppress job output, including errors.\n"
764 " -t n Time out after n secs.\n"
765 " -v Be verbose.\n"
766 " -w Wait for running jobs to finish when a job returns an error.\n"
767 "\n"
768 "Report bugs to: <https://github.com/odkr/para/issues>\n"
769 "Home page: <https://odkr.codeberg.page/para>\n",
770 (long long) njobs, maxload);
771 1 return EXIT_SUCCESS;
772 23 case 'c':
773 23 compose = optarg;
774 23 break;
775 11 case 'e':
776 /*
777 * Option must be handled here,
778 * because it should take effect immediately
779 */
780 11 errfd = (int) getoptll('e', 3, (long long) INT_MAX);
781
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (fcntl(errfd, F_GETFD) < 0) {
782 1 err(EXIT_FAILURE, "fcntl");
783 }
784 4 (void) fflush(stderr);
785
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 while ((olderrfd = dup(2)) < 0) {
786 if (errno != EINTR) {
787 err(EXIT_FAILURE, "dup");
788 }
789 }
790
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 while ((dup2(errfd, 2)) < 0) {
791 if (errno != EINTR) {
792 err(EXIT_FAILURE, "dup2");
793 }
794 }
795
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(errfd >= 3);
796
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(olderrfd >= 3);
797
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(errfd != olderrfd);
798 4 break;
799 3 case 'i':
800 3 errhdl = EH_IGNORE;
801 3 break;
802 30 case 'j':
803 /* PTRDIFF_MIN is < 1 and MAX_NJOBS is < PTRDIFF_MAX */
804 30 njobs = (ptrdiff_t) getoptll('j', 1, MAX_NJOBS - 1);
805 23 break;
806 14 case 'k':
807 /* DBL_MIN is < 0 and INT_MAX < DBL_MAX */
808 14 grace = (int) getoptll('k', 0, (long long) INT_MAX);
809 7 break;
810 31 case 'l':
811 31 maxload = getoptd('l', 0.0, DBL_MAX);
812 /* NOLINTNEXTLINE(misc-redundant-expression); not redundant */
813 if ((!HAVE_GETLOADAVG || !EXTENSIONS) && maxload > 0.0) {
814 errx(EXIT_FAILURE, "-l: getloadavg(3) not available");
815 }
816 24 break;
817 11 case 'n':
818 /* PTRDIFF_MIN is < 1 and MAX_EXIT is < PTRDIFF_MAX */
819 11 nonerrors[(ptrdiff_t) getoptll('n', 1, MAX_EXIT - 1)] = true;
820 4 break;
821 5 case 'r':
822 5 placeholder = optarg;
823 5 break;
824 4 case 'q':
825 4 quiet = true;
826 4 break;
827 10 case 't':
828 10 timeout = (unsigned int) getoptll('t', 1, (long long) INT_MAX);
829 3 break;
830 12 case 'v':
831 12 verbose = true;
832 12 break;
833 2 case 'w':
834 2 errhdl = EH_WAIT;
835 2 break;
836 8 default:
837 8 return EXIT_USAGE;
838 }
839 }
840
841 122 argc -= optind;
842 122 argv += optind;
843
844
845 /*
846 * Signals
847 */
848
849 122 const struct sigaction childact = { /* Action that responds to SIGCHLD */
850 .sa_flags = SA_NOCLDSTOP,
851 .sa_handler = sigcatchchild
852 };
853
854 sigset_t sigmask; /* Signal mask */
855 sigset_t nosigs; /* Empty set */
856
857 122 (void) sigemptyset(&sigmask);
858
1/2
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
122 (void) sigemptyset(&nosigs);
859
860 /* GCC mistakes sigmask for an int and warns about a sign change */
861 #if VERSION_GCC >= 40300
862 #pragma GCC diagnostic push
863 #pragma GCC diagnostic ignored "-Wsign-conversion"
864 #elif VERSION_GCC >= 20950
865 #pragma GCC diagnostic push
866 #pragma GCC diagnostic ignored "-Wconversion"
867 #endif
868
869 122 if (sigaddset(&sigmask, SIGTSTP) != 0) {
870 /* NOTREACHED */
871 err(EXIT_FAILURE, "sigaddset");
872 }
873
874 122 if (sigaddset(&sigmask, SIGCHLD) != 0) {
875 /* NOTREACHED */
876 err(EXIT_FAILURE, "sigaddset");
877 }
878
879 #if VERSION_GCC >= 20950
880 #pragma GCC diagnostic pop
881 #endif
882
883
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigprocmask(SIG_BLOCK, &sigmask, NULL) != 0) {
884 /* NOTREACHED */
885 err(EXIT_FAILURE, "sigprocmask");
886 }
887
888
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigaction(SIGCHLD, &childact, NULL) != 0) {
889 /* NOTREACHED */
890 err(EXIT_FAILURE, "sigaction");
891 }
892
893
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigaction(SIGHUP, &catchact, NULL) != 0) {
894 /* NOTREACHED */
895 err(EXIT_FAILURE, "sigaction");
896 }
897
898
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigaction(SIGINT, &catchact, NULL) != 0) {
899 /* NOTREACHED */
900 err(EXIT_FAILURE, "sigaction");
901 }
902
903
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigaction(SIGALRM, &catchact, NULL) != 0) {
904 /* NOTREACHED */
905 err(EXIT_FAILURE, "sigaction");
906 }
907
908
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigaction(SIGTERM, &catchact, NULL) != 0) {
909 /* NOTREACHED */
910 err(EXIT_FAILURE, "sigaction");
911 }
912
913
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigaction(SIGTSTP, &catchact, NULL) != 0) {
914 /* NOTREACHED */
915 err(EXIT_FAILURE, "sigaction");
916 }
917
918
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (sigaction(SIGUSR1, &catchact, NULL) != 0) {
919 /* NOTREACHED */
920 err(EXIT_FAILURE, "sigaction");
921 }
922
923
924 /*
925 * Environment variables
926 */
927
928 /* RATS: ignore; writes to vars are bounds-checked */
929 122 char *vars[MAX_NVARS] = {0}; /* Job environment */
930 122 ptrdiff_t nvars = 0; /* Number of variables */
931 122 int varind = 0; /* Last variable in argv */
932
933
4/4
✓ Branch 0 taken 6922 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 6801 times.
✓ Branch 3 taken 121 times.
6923 for (; nvars < MAX_NVARS && environ[nvars]; ++nvars) {
934 6801 vars[nvars] = environ[nvars];
935 }
936
937
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 121 times.
122 if (nvars >= MAX_NVARS) {
938 1 errx(EXIT_FAILURE, "Environment full");
939 }
940
941
2/2
✓ Branch 0 taken 323 times.
✓ Branch 1 taken 7 times.
330 for (; varind < argc; ++varind) {
942 323 const char *const var = argv[varind];
943 323 const size_t varlen = strnlen(var, MAX_STR_SIZE);
944
945
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 322 times.
323 if (varlen >= MAX_STR_SIZE) {
946 1 errx(EXIT_FAILURE, "Argument too long");
947 }
948
949 322 errno = putvar(&nvars, vars, argv[varind]);
950
2/3
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
322 switch (errno) {
951 210 case 0:
952 210 break;
953 112 case EINVAL:
954 112 goto stop;
955 default:
956 /* NOTREACHED */
957 err(EXIT_FAILURE, __FILE__ ":%d: putvar %s", __LINE__, var);
958 }
959
960
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 209 times.
210 if (nvars >= MAX_NVARS - 1) {
961 1 errx(EXIT_FAILURE, "Too many variables");
962 }
963 }
964
965 7 stop:
966 /* Stop iterating over variables */;
967
968 119 argc -= varind;
969 119 argv += varind; /* cppcheck-suppress misra-c2012-10.3 */
970
971
972 /*
973 * Commands
974 */
975
976 119 Words commf = {.count = 0, .max = 0, .vector = NULL}; /* Format */
977 Words *comms; /* Commands */
978 119 int ncomms = 0; /* Counter */
979
980
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 97 times.
119 if (compose) {
981 22 const size_t composelen = strnlen(compose, MAX_STR_SIZE);
982
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 21 times.
22 if (composelen >= MAX_STR_SIZE) {
983 1 errx(EXIT_FAILURE, "-c: Format too long");
984 }
985
986 21 const size_t placeholdersize = strnlen(placeholder, MAX_STR_SIZE) + 1U;
987
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 20 times.
21 if (placeholdersize > MAX_STR_SIZE) {
988 1 errx(EXIT_FAILURE, "-r: Placeholder too long");
989 }
990
991 20 errno = wordsplit(&commf, compose, composelen, compose);
992
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
20 if (errno != 0) {
993 2 err(EXIT_FAILURE, "-c");
994 }
995
996 18 int nplaceholders = 0;
997
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 18 times.
60 for (ptrdiff_t i = 0; (size_t) i < commf.count; ++i) {
998
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 20 times.
42 if (strncmp(commf.vector[i], placeholder, placeholdersize) == 0) {
999
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (i >= INT_MAX) {
1000 /* NOTREACHED */
1001 errx(EXIT_FAILURE, "-c: Too many placeholders");
1002 }
1003 22 ++nplaceholders;
1004 }
1005 }
1006
1007
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
18 if (nplaceholders == 0) {
1008 3 errx(EXIT_USAGE, "-c: No placeholders");
1009 }
1010
1011
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (argc < nplaceholders) {
1012 2 errx(EXIT_USAGE, "Not enough arguments");
1013 }
1014
1015 13 const div_t commsdiv = div(argc, nplaceholders);
1016
1017
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
13 if (commsdiv.rem != 0) {
1018 1 errx(EXIT_USAGE, "Arguments not a multiple of %d", nplaceholders);
1019 }
1020
1021 12 ncomms = commsdiv.quot;
1022
1023 12 comms = (Words *) calloc_s((size_t) ncomms, sizeof(*comms));
1024
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!comms) {
1025 err(EXIT_FAILURE, "calloc");
1026 }
1027
1028 12 ptrdiff_t argind = 0;
1029
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 11 times.
41 for (int i = 0; i < ncomms; ++i) {
1030 30 errno = wordsinit(&comms[i], commf.count);
1031
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 if (errno != 0) {
1032 err(EXIT_FAILURE, "wordsinit");
1033 }
1034
1035
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 29 times.
112 for (ptrdiff_t j = 0; (size_t) j < commf.count; ++j) {
1036 83 char* const velem = commf.vector[j];
1037 83 char *argp = NULL;
1038
1039
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 38 times.
83 if (strncmp(velem, placeholder, placeholdersize) == 0) {
1040 45 argp = argv[argind++];
1041
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 44 times.
45 if (strnlen(argp, MAX_STR_SIZE) >= MAX_STR_SIZE) {
1042 1 errx(EXIT_FAILURE, "Argument too long");
1043 }
1044 } else {
1045 38 argp = velem;
1046 }
1047
1048
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 82 times.
82 assert(argp);
1049
1050 82 errno = wordsadd(&comms[i], argp);
1051
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 82 times.
82 if (errno != 0) {
1052 err(EXIT_FAILURE, "wordsadd");
1053 }
1054 }
1055 }
1056 } else {
1057
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 95 times.
97 if (argc == 0) {
1058
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (verbose) {
1059 1 warnx("No jobs given");
1060 }
1061
1062 2 return EXIT_SUCCESS;
1063 }
1064
1065 95 ncomms = argc;
1066
1067 95 comms = (Words *) calloc_s((size_t) ncomms, sizeof(*comms));
1068
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 95 times.
95 if (!comms) {
1069 err(EXIT_FAILURE, "calloc");
1070 }
1071
1072
2/2
✓ Branch 0 taken 1638 times.
✓ Branch 1 taken 87 times.
1725 for (int i = 0; i < argc; ++i) {
1073 1638 char *const cmd = argv[i];
1074 1638 const size_t cmdlen = strnlen(cmd, MAX_STR_SIZE);
1075
1076
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1637 times.
1638 if (cmdlen >= MAX_STR_SIZE) {
1077 1 errx(EXIT_FAILURE, "Command too long");
1078 }
1079
1080 1637 errno = wordsplit(&comms[i], cmd, cmdlen, cmd);
1081
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1630 times.
1637 if (errno != 0) {
1082 7 err(EXIT_FAILURE, "wordsplit");
1083 }
1084 }
1085 }
1086
1087 /* Some posix_spawnp implementations don't check filename length */
1088 #if defined(PATH_MAX) && PATH_MAX >= 0U
1089
2/2
✓ Branch 0 taken 1657 times.
✓ Branch 1 taken 97 times.
1754 for (int i = 0; i < ncomms; ++i) {
1090 1657 const char *fname = comms[i].vector[0];
1091
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1656 times.
1657 if (strnlen(fname, PATH_MAX) >= (size_t) PATH_MAX) {
1092 1 errx(EXIT_FAILURE, "Command filename too long");
1093 }
1094 }
1095 #endif
1096
1097
1098 /*
1099 * I/O redirection
1100 */
1101
1102 posix_spawn_file_actions_t fileacts; /* File actions */
1103
1104 97 errno = posix_spawn_file_actions_init(&fileacts);
1105
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1106 err(EXIT_FAILURE, "posix_spawn_file_actions_init");
1107 }
1108
1109
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 93 times.
97 if (quiet) {
1110 int fd;
1111
1112 /* RATS: ignore; open is safe */
1113
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 while ((fd = open("/dev/null", O_WRONLY)) < 0) {
1114 if (errno != EINTR) {
1115 /* NOTREACHED */
1116 err(EXIT_FAILURE, "open /dev/null");
1117 }
1118 }
1119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(fd >= 3);
1120
1121
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (int newfd = 1; newfd <= 2; ++newfd) {
1122 8 errno = posix_spawn_file_actions_adddup2(&fileacts, fd, newfd);
1123
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (errno != 0) {
1124 err(EXIT_FAILURE, "posix_spawn_file_actions_adddup2");
1125 }
1126 }
1127
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 89 times.
93 } else if (olderrfd > -1) {
1128 4 errno = posix_spawn_file_actions_adddup2(&fileacts, olderrfd, 2);
1129
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (errno != 0) {
1130 err(EXIT_FAILURE, "posix_spawn_file_actions_adddup2");
1131 }
1132 } /* cppcheck-suppress misra-c2012-15.7; no else needed */
1133
1134
1135 /*
1136 * Process attributes
1137 */
1138
1139 posix_spawnattr_t attrs; /* Process attributes */
1140
1141 97 errno = posix_spawnattr_init(&attrs);
1142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1143 err(EXIT_FAILURE, "posix_spawnattr_init");
1144 }
1145
1146 97 errno = posix_spawnattr_setflags(
1147 &attrs,
1148 POSIX_SPAWN_SETSIGMASK |
1149 POSIX_SPAWN_SETSIGDEF |
1150 POSIX_SPAWN_SETPGROUP
1151 );
1152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1153 /* NOTREACHED */
1154 err(EXIT_FAILURE, "posix_spawnattr_setflags");
1155 }
1156
1157 97 errno = posix_spawnattr_setpgroup(&attrs, 0);
1158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1159 /* NOTREACHED */
1160 err(EXIT_FAILURE, "posix_spawnattr_setpgroup");
1161 }
1162
1163 97 errno = posix_spawnattr_setsigmask(&attrs, &nosigs);
1164
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1165 /* NOTREACHED */
1166 err(EXIT_FAILURE, "posix_spawnattr_setsigmask");
1167 }
1168
1169 97 errno = posix_spawnattr_setsigdefault(&attrs, &sigmask);
1170
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1171 /* NOTREACHED */
1172 err(EXIT_FAILURE, "posix_spawnattr_setsigdefault");
1173 }
1174
1175
1176 /*
1177 * Main loop
1178 */
1179
1180 97 Job jobs[MAX_NJOBS] = {0}; /* Currently running jobs */
1181 97 int ntotal = ncomms; /* Jobs to run */
1182 97 int nforked = 0; /* Jobs forked */
1183 97 int nfinished = 0; /* Jobs finished */
1184 97 int nrunning = 0; /* Jobs running */
1185 97 int nsucceeded = 0; /* Jobs succeeded */
1186 97 int nfailed = 0; /* Jobs failed */
1187
1188 97 (void) alarm(timeout);
1189
1190 while (true) {
1191 /* Handle signals */
1192
4/4
✓ Branch 0 taken 841 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 11 times.
854 switch (caught) {
1193 841 case 0:
1194 841 break;
1195 1 case SIGTSTP:
1196 1 errno = jobststp(njobs, jobs);
1197
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (errno != 0) {
1198 /* NOTREACHED */
1199 warn("jobststp");
1200 goto abort;
1201 }
1202 1 caught = 0;
1203 1 break;
1204 1 case SIGUSR1:
1205 1 warnx("Discarding pending jobs...");
1206 1 ntotal = nforked;
1207 1 retval = EXIT_FAILURE;
1208 1 caught = 0;
1209 1 break;
1210 11 default:
1211 11 warnx("%s", strsignal((int) caught));
1212 11 retval = MAX_EXIT + (int) caught;
1213 11 goto abort;
1214 }
1215
1216
2/2
✓ Branch 0 taken 6487 times.
✓ Branch 1 taken 828 times.
7315 for (ptrdiff_t i = 0; i < njobs; ++i) {
1217 6487 Job *job = &jobs[i];
1218
1219 /* Report status if finished */
1220
2/2
✓ Branch 0 taken 1624 times.
✓ Branch 1 taken 4863 times.
6487 if (job->exited) {
1221
3/4
✓ Branch 0 taken 1624 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1621 times.
1624 if (WIFSIGNALED(job->status)) {
1222 3 const int signo = WTERMSIG(job->status);
1223
1224 3 warnx("%s[%d]: %s",
1225 job->comm, job->pid, strsignal(signo));
1226 3 retval = MAX_EXIT + signo;
1227 3 ++nfailed;
1228 3 goto abort;
1229
1/2
✓ Branch 0 taken 1621 times.
✗ Branch 1 not taken.
1621 } else if (WIFEXITED(job->status)) {
1230 1621 const int status = WEXITSTATUS(job->status);
1231
1232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1621 times.
1621 if (status < 0 || int_exceeds_ptr(status)) {
1233 /* NOTREACHED */
1234 warnx("%s[%d] exited with bad status %d",
1235 job->comm, job->pid, status);
1236 retval = EXIT_FAILURE;
1237 ++nfailed;
1238 goto abort;
1239 }
1240
1241
2/2
✓ Branch 0 taken 1608 times.
✓ Branch 1 taken 13 times.
1621 if (status == 0) {
1242 1608 ++nsucceeded;
1243 } else {
1244 13 warnx("%s[%d] exited with status %d",
1245 job->comm, job->pid, status);
1246
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 10 times.
13 if (status < MAX_EXIT && nonerrors[status]) {
1247 2 ++nsucceeded;
1248 } else {
1249 11 retval = status;
1250 11 ++nfailed;
1251
1252
3/3
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 7 times.
11 switch (errhdl) {
1253 2 case EH_IGNORE:
1254 2 break;
1255 2 case EH_WAIT:
1256 2 ntotal = nforked;
1257 2 break;
1258 7 default:
1259 7 goto abort;
1260 }
1261 }
1262 }
1263 } else {
1264 /* NOTREACHED */
1265 warnx("%s[%d]: Unexpected exit", job->comm, job->pid);
1266 retval = EXIT_FAILURE;
1267 ++nfailed;
1268 goto abort;
1269 }
1270
1271 1614 job->pid = 0;
1272 1614 job->exited = 0;
1273 1614 ++nfinished;
1274 }
1275
1276 /* Start next job */
1277
4/4
✓ Branch 0 taken 2949 times.
✓ Branch 1 taken 3528 times.
✓ Branch 2 taken 1653 times.
✓ Branch 3 taken 1296 times.
6477 if (job->pid == 0 && nforked < ntotal) {
1278 1653 const Words *const comm = &comms[nforked];
1279
1280 #if EXTENSIONS && HAVE_GETLOADAVG
1281
4/4
✓ Branch 0 taken 1611 times.
✓ Branch 1 taken 42 times.
✓ Branch 2 taken 1534 times.
✓ Branch 3 taken 77 times.
1653 if (maxload > 0.0 && nrunning > 0) {
1282 double loadavg;
1283
1284 /* cppcheck-suppress misra-c2012-17.3; see stdlib.h */
1285
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1534 times.
1534 if (getloadavg(&loadavg, 1) < 1) {
1286 errx(EXIT_FAILURE, "getloadavg: Unobtainable");
1287 }
1288
1289
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1533 times.
1534 if (loadavg > maxload) {
1290 1 continue;
1291 }
1292 }
1293 #endif
1294
1295 /* comms and jobs effectively have the same lifetime */
1296 1652 job->comm = *comm->vector;
1297
1298 1652 errno = posix_spawnp(&job->pid, *comm->vector, &fileacts,
1299 1652 &attrs, comm->vector, vars);
1300
2/2
✓ Branch 0 taken 1647 times.
✓ Branch 1 taken 5 times.
1652 if (errno == 0) {
1301
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1647 times.
1647 assert(job->pid > 0);
1302
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1647 times.
1647 assert(!job->exited);
1303
1304
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1627 times.
1647 if (verbose) {
1305 20 char *const commstr = wordsjoin(comm);
1306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (!commstr) {
1307 err(EXIT_FAILURE, "wordsjoin");
1308 }
1309
1310 20 warnx("[%d] %s", job->pid, commstr);
1311 20 free(commstr);
1312 }
1313
1314 1647 ++nforked;
1315 1647 ++nrunning;
1316 } else {
1317 5 warn("posix_spawnp %s", *comm->vector);
1318 5 retval = EXIT_FAILURE;
1319 5 goto abort;
1320 }
1321 }
1322 }
1323
1324 /* Wait for jobs to finish */
1325
2/2
✓ Branch 0 taken 757 times.
✓ Branch 1 taken 71 times.
828 if (nrunning > 0) {
1326
4/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 741 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 2 times.
757 if (verbose && nforked == ntotal) {
1327
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 info("Waiting for %d job%s to finish...",
1328 nrunning, nrunning == 1 ? "" : "s");
1329 }
1330
1331 757 (void) sigsuspend(&nosigs);
1332 }
1333
1334 /* Collected exited jobs */
1335
2/2
✓ Branch 0 taken 741 times.
✓ Branch 1 taken 87 times.
828 if (caughtchild) {
1336 741 int nexited = 0;
1337
1338 741 errno = jobswait(njobs, jobs, &nexited);
1339
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 741 times.
741 if (errno != 0) {
1340 /* NOTREACHED */
1341 warn("waitpid");
1342 }
1343
1344 741 nrunning -= nexited;
1345 741 caughtchild = 0;
1346 }
1347
1348 /* Exit if all jobs are completed */
1349
2/2
✓ Branch 0 taken 71 times.
✓ Branch 1 taken 757 times.
828 if (nfinished >= ntotal) {
1350 71 goto exit;
1351 }
1352 }
1353
1354 26 abort:
1355 26 (void) alarm(0U);
1356
1357
2/2
✓ Branch 0 taken 143 times.
✓ Branch 1 taken 26 times.
169 for (ptrdiff_t i = 0; i < njobs; ++i) {
1358 143 const Job job = jobs[i];
1359
1360
4/4
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 110 times.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 10 times.
143 if (job.pid > 0 && !job.exited) {
1361
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 if (kill(job.pid, SIGTERM) != 0) {
1362 /*
1363 * kill can only fail because of ESRCH and EPERM,
1364 * which can only happen if the job has already exited.
1365 */
1366 continue;
1367 }
1368
1369 23 (void) kill(job.pid, SIGCONT);
1370 }
1371 }
1372
1373
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 5 times.
26 if (grace > 0) {
1374 5 const struct sigaction dropact = {.sa_handler = sigdrop};
1375 5 const struct timeval start = {.tv_sec = grace};
1376 5 struct timeval count = start;
1377 struct timeval t0;
1378
1379 /* cppcheck-suppress misra-c2012-9.2; braces are right there */
1380 5 const struct itimerval reset = {
1381 .it_value = {.tv_sec = 0}
1382 };
1383
1384 /* cppcheck-suppress misra-c2012-9.2; braces are right there */
1385 5 const struct itimerval pulse = {
1386 .it_value = {.tv_sec = 1},
1387 .it_interval = {.tv_sec = 1}
1388 };
1389
1390 5 oldcaught = caught;
1391 5 caught = 0;
1392
1393
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (sigaction(SIGALRM, &dropact, NULL) != 0) {
1394 /* NOTREACHED */
1395 warn("sigaction");
1396 goto wait;
1397 }
1398
1399
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (gettimeofday(&t0, NULL) != 0) {
1400 /* NOTREACHED */
1401 warn("gettimeofday");
1402 goto wait;
1403 }
1404
1405
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (setitimer(ITIMER_REAL, &pulse, NULL) != 0) {
1406 /* NOTREACHED */
1407 warn("setitimer");
1408 goto wait;
1409 }
1410
1411
5/6
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
13 while (nrunning > 0 && (count.tv_sec > 0 || count.tv_usec > 0)) {
1412 12 struct itimerval restart = {.it_interval = pulse.it_interval};
1413 struct timeval slept;
1414 struct timeval t1;
1415 time_t secs;
1416
1417
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (timevalround(&secs, &count) != 0) {
1418 /* NOTREACHED */
1419 secs = count.tv_sec;
1420 }
1421
1422
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5 times.
12 info("Waiting %llds for %d job%s to terminate...",
1423 (long long) secs, nrunning, nrunning == 1 ? "" : "s");
1424
1425 /* Signals would arrive between sigprocmask and nanosleep */
1426 12 (void) sigsuspend(&nosigs);
1427
1428
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (gettimeofday(&t1, NULL) != 0) {
1429 /* NOTREACHED */
1430 warn("gettimeofday");
1431 4 break;
1432 }
1433
1434 const int cmp __attribute__((unused)) = \
1435 12 timevaldiff(&slept, &t1, &t0);
1436
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 assert(cmp >= 0);
1437
1438 12 t0 = t1;
1439
1440
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
12 if (timevaldiff(&count, &count, &slept) < 0) {
1441 4 break;
1442 }
1443
1444
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (caughtchild) {
1445 1 int nexited = 0;
1446
1447 1 errno = jobswait(njobs, jobs, &nexited);
1448
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (errno != 0) {
1449 /* NOTREACHED */
1450 warn("waitpid");
1451 }
1452
1453 1 nrunning -= nexited;
1454 1 caughtchild = 0;
1455 }
1456
1457
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (caught == SIGTSTP) {
1458 1 restart.it_value.tv_usec = count.tv_usec;
1459
1460
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (setitimer(ITIMER_REAL, &reset, NULL) != 0) {
1461 /* NOTREACHED */
1462 warn("setitimer");
1463 break;
1464 }
1465
1466 1 errno = jobststp(njobs, jobs);
1467
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (errno != 0) {
1468 /* NOTREACHED */
1469 warn("jobststp");
1470 }
1471 1 caught = 0;
1472
1473
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (gettimeofday(&t0, NULL) != 0) {
1474 /* NOTREACHED */
1475 warn("gettimeofday");
1476 break;
1477 }
1478
1479
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (setitimer(ITIMER_REAL, &restart, NULL) != 0) {
1480 /* NOTREACHED */
1481 warn("setitimer");
1482 break;
1483 }
1484
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 } else if (caught != 0) {
1485 warnx("Already terminating");
1486 caught = 0;
1487 } /* cppcheck-suppress misra-c2012-15.7; no else needed */
1488 }
1489
1490
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (setitimer(ITIMER_REAL, &reset, NULL) != 0) {
1491 /* NOTREACHED */
1492 warn("setitimer");
1493 }
1494
1495
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 if (oldcaught != 0) {
1496 2 caught = oldcaught;
1497 }
1498
1499
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (nrunning > 0) {
1500
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 4 times.
24 for (ptrdiff_t i = 0; i < njobs; ++i) {
1501 20 const Job job = jobs[i];
1502
1503
4/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 3 times.
20 if (job.pid > 0 && !job.exited) {
1504 6 warnx("Killing %s[%ld]...", job.comm, (long) job.pid);
1505 /* See comment on kill above. */
1506 6 (void) killpg(job.pid, SIGKILL);
1507 }
1508 }
1509
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (timevalcomp(&count, &start) != 0) {
1510 1 info("All jobs have terminated");
1511 } /* cppcheck-suppress misra-c2012-15.7; no else needed */
1512
1513 5 termln(NL_PRINT);
1514 }
1515
1516 21 wait:
1517 26 oldcaught = caught;
1518 26 caught = 0;
1519
1520
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) != 0) {
1521 /* NOTREACHED */
1522 warn("sigprocmask");
1523 }
1524
1525 /* cppcheck-suppress misra-c2012-17.3; see sys/wait.h */
1526
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 17 times.
47 while (wait(NULL) == -1) {
1527
2/3
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
30 switch (errno) {
1528 9 case ECHILD:
1529 9 goto exit;
1530 21 case EINTR:
1531 21 break;
1532 default:
1533 /* NOTREACHED */
1534 err(EXIT_FAILURE, "wait");
1535 }
1536
1537
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 if (sigprocmask(SIG_BLOCK, &sigmask, NULL) != 0) {
1538 /* NOTREACHED */
1539 warn("sigprocmask");
1540 }
1541
1542
3/3
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
21 switch (caught) {
1543 17 case 0:
1544 17 break;
1545 1 case SIGTSTP:
1546 1 errno = jobststp(njobs, jobs);
1547
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (errno != 0) {
1548 /* NOTREACHED */
1549 warn("jobststp");
1550 }
1551 1 caught = 0;
1552 1 break;
1553 3 default:
1554 3 warnx("Already terminating");
1555 3 caught = 0;
1556 3 break;
1557 }
1558
1559
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) != 0) {
1560 /* NOTREACHED */
1561 warn("sigprocmask");
1562 }
1563 }
1564
1565
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (sigprocmask(SIG_BLOCK, &sigmask, NULL) != 0) {
1566 /* NOTREACHED */
1567 warn("sigprocmask");
1568 }
1569
1570
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 10 times.
17 if (oldcaught != 0) {
1571 10 caught = oldcaught;
1572 }
1573
1574 7 exit:
1575
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 31 times.
97 if (retval == 0) {
1576
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 assert(nrunning == 0);
1577
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 assert(nfinished == ntotal);
1578
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 assert(nfinished == nsucceeded + nfailed);
1579 }
1580
1581
1582 /*
1583 * Cleanup
1584 */
1585
1586 97 (void) alarm(0U);
1587
1588 97 errno = posix_spawn_file_actions_destroy(&fileacts);
1589
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1590 /* NOTREACHED */
1591 warn("posix_spawn_file_actions_destroy");
1592 }
1593
1594 97 errno = posix_spawnattr_destroy(&attrs);
1595
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (errno != 0) {
1596 /* NOTREACHED */
1597 warn("posix_spawnattr_destroy");
1598 }
1599
1600 /* Pacify memory checkers */
1601
2/2
✓ Branch 0 taken 1656 times.
✓ Branch 1 taken 97 times.
1753 for (ptrdiff_t i = 0; i < ncomms; ++i) {
1602 1656 errno = wordsfree(&comms[i]);
1603
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1656 times.
1656 if (!NDEBUG && errno != 0) {
1604 /* NOTREACHED */
1605 warn(__FILE__ ":%d: wordsfree", __LINE__);
1606 }
1607 }
1608 97 free(comms);
1609
1610
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 87 times.
97 if (commf.vector) {
1611 10 errno = wordsfree(&commf);
1612
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!NDEBUG && errno != 0) {
1613 /* NOTREACHED */
1614 warn(__FILE__ ":%d: wordsfree", __LINE__);
1615 }
1616 }
1617
1618
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 87 times.
97 if (caught != 0) {
1619
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (sigaction((int) caught, &defaultact, NULL) != 0) {
1620 /* NOTREACHED */
1621 warn("sigaction");
1622 }
1623
1624 10 flush();
1625 10 gcov_dump();
1626
1627 if (raise((int) caught) != 0) {
1628 /* NOTREACHED */
1629 warn("raise");
1630 }
1631 /* NOTREACHED */
1632
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 80 times.
87 } else if (verbose) {
1633
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
7 if (errhdl != EH_TERM && nfinished >= ntotal) {
1634
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 warnx("%d job%s run, %d failed",
1635 nfinished, nfinished == 1 ? "" : "s", nfailed);
1636
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 } else if (retval == EXIT_SUCCESS) {
1637
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 warnx("%d job%s run",
1638 nfinished, nfinished == 1 ? "" : "s");
1639 } /* cppcheck-suppress misra-c2012-15.7; no else needed */
1640 } /* cppcheck-suppress misra-c2012-15.7; no else needed */
1641
1642 87 return retval;
1643 }
1644
1645 #endif /* !defined(PARA_C_AS_OBJ) */
1646
1647
1648 /*
1649 * Functions
1650 */
1651
1652 exportable void *
1653 207543 calloc_s(const size_t nelems, const size_t elemwd)
1654 {
1655
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 207543 times.
207543 assert(nelems > 0U);
1656
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 207543 times.
207543 assert(elemwd > 0U);
1657
1658 /* Most implementations of calloc check for overflow, but some don't */
1659
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 207542 times.
207543 if (SIZE_MAX / elemwd < nelems) {
1660 1 errno = EOVERFLOW;
1661 1 return NULL;
1662 }
1663
1664 207542 return calloc(nelems, elemwd);
1665 }
1666
1667
1668 static void
1669 24 err(const int status, const char *const format, ...)
1670 {
1671
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 assert(status < MAX_EXIT);
1672
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 assert(format);
1673
1674 va_list argp;
1675 24 va_start(argp, format);
1676 24 vwarn(format, argp);
1677 24 va_end(argp);
1678
1679 24 exit(status);
1680 }
1681
1682
1683 static void
1684 41 errx(const int status, const char *const format, ...)
1685 {
1686
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 41 times.
41 assert(status < MAX_EXIT);
1687
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 41 times.
41 assert(format);
1688
1689 va_list argp;
1690 41 va_start(argp, format);
1691 41 vwarnx(format, argp);
1692 41 va_end(argp);
1693
1694 41 exit(status);
1695 }
1696
1697
1698 exportable void
1699 181 flush(void)
1700 {
1701 181 (void) fflush(stdout);
1702 181 (void) fflush(stderr);
1703 181 }
1704
1705
1706 #if EXTENSIONS && defined(_SC_NPROCESSORS_ONLN)
1707
1708 exportable long
1709 181 getnprocs(void)
1710 {
1711 181 return sysconf(_SC_NPROCESSORS_ONLN);
1712 }
1713
1714 #else /* !EXTENSIONS || !defined(_SC_NPROCESSORS_ONLN) */
1715
1716 exportable long
1717 getnprocs(void)
1718 {
1719 return -1;
1720 }
1721
1722 #endif /* EXTENSIONS && defined(_SC_NPROCESSORS_ONLN) */
1723
1724
1725 exportable double
1726 31 getoptd(const char opt, const double min, const double max)
1727 {
1728
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 assert(optarg);
1729
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 assert(opt != '\0');
1730
1731 /* cppcheck-suppress misra-config; Cppcheck does not know about optarg */
1732
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 30 times.
31 if (*optarg == '\0') {
1733 1 errx(EXIT_USAGE, "-%c: Empty", opt);
1734 }
1735
1736 30 errno = 0;
1737 30 char *end = NULL;
1738 30 const double num = strtod(optarg, &end);
1739
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29 times.
30 if (errno != 0) {
1740 1 err(EXIT_USAGE, "-%c", opt);
1741 }
1742
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 26 times.
29 if (*end != '\0') {
1743 3 errx(EXIT_USAGE, "-%c: %s: Not a number", opt, optarg);
1744 }
1745
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
26 if (num < min) {
1746 2 errx(EXIT_USAGE, "-%c: Smaller than %.1f", opt, min);
1747 }
1748
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (num > max) {
1749 errx(EXIT_USAGE, "-%c: Greater than %.1f", opt, max);
1750 }
1751
1752 24 return num;
1753 }
1754
1755
1756 exportable long long
1757 76 getoptll(const char opt, const long long min, const long long max)
1758 {
1759
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
76 assert(optarg);
1760
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
76 assert(opt != '\0');
1761
1762 /* cppcheck-suppress misra-config; Cppcheck does not know about optarg */
1763
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 71 times.
76 if (*optarg == '\0') {
1764 5 errx(EXIT_USAGE, "-%c: Empty", opt);
1765 }
1766
1767 71 errno = 0;
1768 71 char *end = NULL;
1769 71 const long long num = strtoll(optarg, &end, 10);
1770 /* cppcheck-suppress [misra-c2012-7.2, misra-c2012-10.4]; out of my hands */
1771
7/8
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 46 times.
✓ Branch 6 taken 13 times.
✓ Branch 7 taken 12 times.
71 if ((num == 0 || num == LLONG_MIN || num == LLONG_MAX) && errno != 0) {
1772 13 err(EXIT_USAGE, "-%c", opt);
1773 }
1774
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 53 times.
58 if (*end != '\0') {
1775 5 errx(EXIT_USAGE, "-%c: %s: Not a number", opt, optarg);
1776 }
1777
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 44 times.
53 if (num < min) {
1778 9 errx(EXIT_USAGE, "-%c: Smaller than %lld", opt, min);
1779 }
1780
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 42 times.
44 if (num > max) {
1781 2 errx(EXIT_USAGE, "-%c: Greater than %lld", opt, max);
1782 }
1783
1784 42 return num;
1785 }
1786
1787
1788 /* info is only given literals */
1789 #if VERSION_CLANG >= 40000
1790 #pragma clang diagnostic push
1791 #pragma clang diagnostic ignored "-Wformat-nonliteral"
1792 #endif
1793
1794 exportable void
1795 27 info(const char *const format, ...) {
1796
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 assert(format);
1797
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 assert(*format != '\0');
1798
1799
1/2
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
27 if (!isatty(2)) {
1800 27 return;
1801 }
1802
1803 /* RATS: ignore; buffer is small */
1804 char buf[MAX_MSG_SIZE];
1805 int n __attribute__((unused));
1806
1807 /* RATS: ignore; sizeof(buf) cannot be false */
1808 n = snprintf(buf, sizeof(buf), "\r" PROGNAME ": %s " CLEARLN, format);
1809 assert(n > 0);
1810 assert((size_t) n < sizeof(buf));
1811
1812 va_list argp;
1813 va_start(argp, format);
1814 /* RATS: ignore; format always points to a literal */
1815 (void) vfprintf(stderr, buf, argp);
1816 va_end(argp);
1817
1818 termln(NL_REQUEST);
1819 }
1820
1821 #if VERSION_CLANG >= 40000
1822 #pragma clang diagnostic pop
1823 #endif
1824
1825
1826 exportable int
1827 71 jobskill(const ptrdiff_t njobs, const Job *const jobs, const int signo)
1828 {
1829
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 71 times.
71 assert(njobs >= 0);
1830
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 71 times.
71 assert(jobs);
1831
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 71 times.
71 assert(signo > 0);
1832
1833 71 int retval = 0;
1834
1835
2/2
✓ Branch 0 taken 2065 times.
✓ Branch 1 taken 71 times.
2136 for (ptrdiff_t i = 0; i < njobs; ++i) {
1836 2065 const Job *job = &jobs[i];
1837
1838
3/4
✓ Branch 0 taken 2023 times.
✓ Branch 1 taken 42 times.
✓ Branch 2 taken 2023 times.
✗ Branch 3 not taken.
2065 if (job->pid > 0 && !job->exited) {
1839
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2022 times.
2023 if (kill(job->pid, signo) != 0) {
1840 1 retval = errno;
1841 }
1842 }
1843 }
1844
1845 71 return retval;
1846 }
1847
1848
1849 exportable int
1850 3 jobststp(const ptrdiff_t njobs, const Job *const jobs)
1851 {
1852 sigset_t oldmask;
1853 sigset_t tstpmask;
1854 int retval;
1855
1856
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 (void) sigemptyset(&tstpmask);
1857
1858 /* GCC mistakes tstpmask for an int and warns about a sign change */
1859 #if VERSION_GCC >= 40300
1860 #pragma GCC diagnostic push
1861 #pragma GCC diagnostic ignored "-Wsign-conversion"
1862 #elif VERSION_GCC >= 20950
1863 #pragma GCC diagnostic push
1864 #pragma GCC diagnostic ignored "-Wconversion"
1865 #endif
1866
1867 3 (void) sigaddset(&tstpmask, SIGTSTP);
1868
1869 #if VERSION_GCC >= 20950
1870 #pragma GCC diagnostic pop
1871 #endif
1872
1873 3 retval = jobskill(njobs, jobs, SIGTSTP);
1874
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (retval != 0) {
1875 /* NOTREACHED */
1876 return retval;
1877 }
1878
1879
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sigaction((int) SIGTSTP, &defaultact, NULL) != 0) {
1880 /* NOTREACHED */
1881 return errno;
1882 }
1883
1884
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (raise(SIGTSTP) != 0) {
1885 /* NOTREACHED */
1886 return errno;
1887 }
1888
1889
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sigprocmask(SIG_UNBLOCK, &tstpmask, &oldmask) != 0) {
1890 /* NOTREACHED */
1891 return errno;
1892 }
1893
1894
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0) {
1895 /* NOTREACHED */
1896 return errno;
1897 }
1898
1899
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sigaction(SIGTSTP, &catchact, NULL) != 0) {
1900 /* NOTREACHED */
1901 return errno;
1902 }
1903
1904 3 retval = jobskill(njobs, jobs, SIGCONT);
1905
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (retval != 0) {
1906 /* NOTREACHED */
1907 return retval;
1908 }
1909
1910 3 return 0;
1911 }
1912
1913
1914 exportable int
1915 745 jobswait(const ptrdiff_t njobs, Job *jobs, int *const nexited)
1916 {
1917
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 assert(njobs >= 0);
1918
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 assert(jobs);
1919
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 745 times.
745 assert(nexited);
1920
1921 745 *nexited = 0;
1922
1923 1627 while (true) {
1924 pid_t pid;
1925 int status;
1926
1927 /* cppcheck-suppress misra-c2012-17.3; see sys/wait.h */
1928
2/2
✓ Branch 0 taken 745 times.
✓ Branch 1 taken 1627 times.
2372 while ((pid = waitpid(-1, &status, WNOHANG)) <= 0) {
1929
2/2
✓ Branch 0 taken 662 times.
✓ Branch 1 taken 83 times.
745 if (pid == 0) {
1930 745 return 0;
1931 }
1932
1933
1/3
✓ Branch 0 taken 83 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
83 switch (errno) {
1934 83 case ECHILD:
1935 83 return 0;
1936 case EINTR:
1937 break;
1938 default:
1939 /* NOTREACHED */
1940 return errno;
1941 }
1942 }
1943
1944
1/2
✓ Branch 0 taken 6728 times.
✗ Branch 1 not taken.
6728 for (ptrdiff_t i = 0; i < njobs; ++i) {
1945 6728 Job *job = &jobs[i];
1946
1947
3/4
✓ Branch 0 taken 1627 times.
✓ Branch 1 taken 5101 times.
✓ Branch 2 taken 1627 times.
✗ Branch 3 not taken.
6728 if (job->pid == pid && !job->exited) {
1948 1627 job->status = status;
1949 1627 job->exited = 1;
1950 1627 ++*nexited;
1951 1627 goto next;
1952 }
1953 }
1954
1955 /* NOTREACHED */
1956 return EINVAL;
1957
1958 1627 next:
1959 /* Next child */;
1960 }
1961
1962 /* NOTREACHED */
1963 }
1964
1965
1966 exportable int
1967 28767 putvar(ptrdiff_t *const nvars, char **const vars, char *const var)
1968 {
1969
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28767 times.
28767 assert(nvars);
1970
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28767 times.
28767 assert(vars);
1971
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28767 times.
28767 assert(var);
1972
1973
2/2
✓ Branch 0 taken 3597 times.
✓ Branch 1 taken 25170 times.
28767 if (isdigit((int) *var)) {
1974 3597 return EINVAL;
1975 }
1976
1977 25170 char *ptr = var;
1978
4/4
✓ Branch 0 taken 5018 times.
✓ Branch 1 taken 58370 times.
✓ Branch 2 taken 33200 times.
✓ Branch 3 taken 25170 times.
63388 while (*ptr == '_' || isalnum((unsigned char) *ptr)) {
1979 38218 ++ptr;
1980 }
1981
1982 25170 const ptrdiff_t len = ptr - var;
1983
1984 if (ptr_exceeds_size(len)) {
1985 /* NOTREACHED */
1986 return EOVERFLOW;
1987 }
1988
1989
4/4
✓ Branch 0 taken 12418 times.
✓ Branch 1 taken 12752 times.
✓ Branch 2 taken 7018 times.
✓ Branch 3 taken 5400 times.
25170 if (*ptr != '=' || len < 1) {
1990 19770 return EINVAL;
1991 }
1992
1993 5400 ptrdiff_t ind = 0;
1994
4/4
✓ Branch 0 taken 31414 times.
✓ Branch 1 taken 5390 times.
✓ Branch 2 taken 31404 times.
✓ Branch 3 taken 10 times.
36804 while (ind < *nvars && strncmp(vars[ind], var, (size_t) len) != 0) {
1995 31404 ++ind;
1996 }
1997
1998 5400 vars[ind] = var;
1999
2/2
✓ Branch 0 taken 5390 times.
✓ Branch 1 taken 10 times.
5400 if (ind == *nvars) {
2000 5390 vars[++(*nvars)] = NULL;
2001 }
2002
2003 5400 return 0;
2004 }
2005
2006
2007 exportable void
2008 2034 sigcatch(const int signo)
2009 {
2010
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2034 times.
2034 assert(signo > 0);
2011 2034 caught = signo;
2012 2034 }
2013
2014
2015 exportable void
2016 760 sigcatchchild(const int signo __attribute__((unused)))
2017 {
2018
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 assert(signo == SIGCHLD);
2019 760 caughtchild = 1;
2020 760 }
2021
2022
2023 exportable void
2024 10 sigdrop(int signo __attribute__((unused)))
2025 {
2026
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 assert(signo > 0);
2027 10 }
2028
2029
2030 exportable void
2031 146 termln(const NewlineAction req)
2032 {
2033 static NewlineAction act = NL_NONE;
2034
2035
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 4 times.
146 if (req == NL_PRINT) {
2036
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 141 times.
142 if (act == NL_REQUEST) {
2037 1 (void) fputs("\n", stderr);
2038 1 act = NL_NONE;
2039 }
2040 } else {
2041 4 act = req;
2042 }
2043 146 }
2044
2045
2046 exportable int
2047 129 timevalcomp(const struct timeval *const timep1,
2048 const struct timeval *const timep2)
2049 {
2050
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
129 assert(timep1);
2051
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
129 assert(timep1->tv_usec >= 0L);
2052
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
129 assert(timep1->tv_usec < MAX_MICRO);
2053
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
129 assert(timep2);
2054
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
129 assert(timep2->tv_usec >= 0L);
2055
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
129 assert(timep2->tv_usec < MAX_MICRO);
2056
2057
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 93 times.
129 if (timep1->tv_sec > timep2->tv_sec) {
2058 36 return 1;
2059 }
2060
2061
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 75 times.
93 if (timep1->tv_sec < timep2->tv_sec) {
2062 18 return -1;
2063 }
2064
2065
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 58 times.
75 if (timep1->tv_usec > timep2->tv_usec) {
2066 17 return 1;
2067 }
2068
2069
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 45 times.
58 if (timep1->tv_usec < timep2->tv_usec) {
2070 13 return -1;
2071 }
2072
2073 45 return 0;
2074 }
2075
2076
2077 exportable int
2078 59 timevaldiff(struct timeval *const diff,
2079 const struct timeval *const timep1,
2080 const struct timeval *const timep2)
2081 {
2082
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 assert(diff);
2083
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 assert(timep1);
2084
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 assert(timep1->tv_usec >= 0);
2085
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 assert(timep1->tv_usec < MAX_MICRO);
2086
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 assert(timep2);
2087
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 assert(timep2->tv_usec >= 0);
2088
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 assert(timep2->tv_usec < MAX_MICRO);
2089
2090 59 const int retval = timevalcomp(timep1, timep2);
2091 const struct timeval *min;
2092 const struct timeval *sub;
2093
2094
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 23 times.
59 if (retval > 0) {
2095 36 min = timep1;
2096 36 sub = timep2;
2097
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 6 times.
23 } else if (retval < 0) {
2098 17 min = timep2;
2099 17 sub = timep1;
2100 } else {
2101 6 diff->tv_sec = 0;
2102 6 diff->tv_usec = 0;
2103 6 return retval;
2104 }
2105
2106 53 diff->tv_sec = min->tv_sec - sub->tv_sec;
2107 53 diff->tv_usec = min->tv_usec - sub->tv_usec;
2108
2109
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 36 times.
53 if (diff->tv_usec < 0) {
2110 17 diff->tv_sec -= 1;
2111 17 diff->tv_usec += MAX_MICRO;
2112 }
2113
2114
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 assert(diff->tv_usec >= 0);
2115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 assert(diff->tv_usec < MAX_MICRO);
2116
2117 53 return retval;
2118 }
2119
2120
2121 exportable int
2122 16 timevalround(time_t *const secs, const struct timeval *const timep)
2123 {
2124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 assert(timep);
2125
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 assert(timep->tv_usec >= 0);
2126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 assert(timep->tv_usec < MAX_MICRO);
2127
2128 16 *secs = timep->tv_sec;
2129
2130
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 9 times.
16 if (timep->tv_usec < HALF_SECOND) {
2131 7 return 0;
2132 }
2133
2134 9 ++(*secs);
2135
2136
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (*secs > timep->tv_sec) {
2137 9 return 0;
2138 }
2139
2140 errno = EOVERFLOW;
2141 return -1;
2142 }
2143
2144
2145 /* vwarn and vwarnx are only given literals, if indirectly */
2146 #if VERSION_CLANG >= 40000
2147 #pragma clang diagnostic push
2148 #pragma clang diagnostic ignored "-Wformat-nonliteral"
2149 #endif
2150
2151 static void
2152 29 vwarn(const char *const format, va_list argp)
2153 {
2154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 assert(format);
2155
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 assert(*format != '\0');
2156
2157 29 const int error = errno;
2158
2159 29 termln(NL_PRINT);
2160
2161 /* RATS: ignore; buffer is small */
2162 29 char buf[MAX_MSG_SIZE] = {0};
2163 int n __attribute__((unused));
2164
2165 /* RATS: ignore; sizeof(buf) cannot be false */
2166
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 n = snprintf(buf, sizeof(buf), PROGNAME ": %s: %s%s\n",
2167 format, strerror(error), isatty(2) ? CLEARLN : "");
2168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 assert(n > 0);
2169
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 assert((size_t) n < sizeof(buf));
2170
2171 /* RATS: ignore; format always points to a literal */
2172 29 (void) vfprintf(stderr, buf, argp);
2173 29 }
2174
2175
2176 static void
2177 104 vwarnx(const char *const format, va_list argp)
2178 {
2179
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert(format);
2180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert(*format != '\0');
2181
2182 104 termln(NL_PRINT);
2183
2184 /* RATS: ignore; buffer is small */
2185 104 char buf[MAX_MSG_SIZE] = {0};
2186 int n __attribute__((unused));
2187
2188 /* RATS: ignore; sizeof(buf) cannot be false */
2189
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 n = snprintf(buf, sizeof(buf), PROGNAME ": %s%s\n",
2190 format, isatty(2) ? CLEARLN : "");
2191
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert(n > 0);
2192
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert((size_t) n < sizeof(buf));
2193
2194 /* RATS: ignore; format always points to a literal */
2195 104 (void) vfprintf(stderr, buf, argp);
2196 104 }
2197
2198 #if VERSION_CLANG >= 40000
2199 #pragma clang diagnostic pop
2200 #endif
2201
2202
2203 static void
2204 5 warn(const char *const format, ...)
2205 {
2206
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(format);
2207
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(*format != '\0');
2208
2209 va_list argp;
2210 5 va_start(argp, format);
2211 5 vwarn(format, argp);
2212 5 va_end(argp);
2213 5 }
2214
2215
2216 static void
2217 63 warnx(const char *const format, ...)
2218 {
2219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 63 times.
63 assert(format);
2220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 63 times.
63 assert(*format != '\0');
2221
2222 va_list argp;
2223 63 va_start(argp, format);
2224 63 vwarnx(format, argp);
2225 63 va_end(argp);
2226 63 }
2227
2228
2229 exportable int
2230 133225 wordsadd(Words *const words, char *const word)
2231 {
2232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 133225 times.
133225 assert(words);
2233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 133225 times.
133225 assert(!size_exceeds_ptr(words->count));
2234
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 133225 times.
133225 assert(!size_exceeds_ptr(words->max));
2235
2236
3/4
✓ Branch 0 taken 133222 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 133222 times.
133225 if (!words->vector || !word) {
2237 3 return EINVAL;
2238 }
2239
2240 133222 const size_t oldcount = words->count;
2241
2242
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 133221 times.
133222 if (words->count >= MAX_NWORDS) {
2243 1 return EOVERFLOW;
2244 }
2245 133221 const size_t newcount = words->count + 1U;
2246
2247
2/2
✓ Branch 0 taken 52376 times.
✓ Branch 1 taken 80845 times.
133221 if (newcount > words->max) {
2248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52376 times.
52376 if (SIZE_MAX / 2U < words->max) {
2249 /* NOTREACHED */
2250 return EOVERFLOW;
2251 }
2252
2/2
✓ Branch 0 taken 1123 times.
✓ Branch 1 taken 51253 times.
52376 const size_t newmax = MAX(1U, words->max) * 2U;
2253
2254
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52376 times.
52376 assert(newmax > 0U);
2255
2256 52376 const int retval = wordsrealloc(words, newmax);
2257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52376 times.
52376 if (retval != 0) {
2258 return retval;
2259 }
2260 }
2261
2262 133221 words->vector[oldcount] = word;
2263 133221 words->vector[newcount] = NULL;
2264 133221 words->count = newcount;
2265
2266 133221 return 0;
2267 }
2268
2269
2270 exportable int
2271 184716 wordsfree(Words *const words)
2272 {
2273
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 184716 times.
184716 assert(words);
2274
2275
2/2
✓ Branch 0 taken 42832 times.
✓ Branch 1 taken 141884 times.
184716 if (!words->vector) {
2276 42832 return EINVAL;
2277 }
2278
2279 141884 free(words->vector);
2280 141884 words->vector = NULL;
2281
2282 141884 return 0;
2283 }
2284
2285
2286 exportable int
2287 141900 wordsinit(Words *const words, const size_t count)
2288 {
2289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 141900 times.
141900 assert(words);
2290
2291
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 141897 times.
141900 if (count >= (size_t) MAX_NWORDS - 1U) {
2292 3 return EOVERFLOW;
2293 }
2294
2295 141897 const size_t nptrs = count + 1U;
2296
2297 141897 words->vector = (char **) calloc_s(nptrs, sizeof(*words->vector));
2298
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 141896 times.
141897 if (!words->vector) {
2299 1 return errno;
2300 }
2301
2302 141896 words->vector[0] = NULL;
2303 141896 words->count = 0U;
2304 141896 words->max = count;
2305
2306 141896 return 0;
2307 }
2308
2309
2310 exportable char *
2311 94 wordsjoin(const Words *const words)
2312 {
2313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 assert(words);
2314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 assert(!size_exceeds_ptr(words->count));
2315
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 assert(!size_exceeds_ptr(words->max));
2316
2317
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 93 times.
94 if (!words->vector) {
2318 1 errno = EINVAL;
2319 1 return NULL;
2320 }
2321
2322 93 char *const dest = (char *) calloc(MAX_STR_SIZE, sizeof(char));
2323
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 93 times.
93 if (!dest) {
2324 return NULL;
2325 }
2326
2327 93 char *ptr = dest;
2328 93 const char *const lim = dest + (ptrdiff_t) MAX_STR_SIZE - 1;
2329
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 93 times.
93 assert(lim > dest);
2330
2331
2/2
✓ Branch 0 taken 133 times.
✓ Branch 1 taken 88 times.
221 for (ptrdiff_t i = 0; (size_t) i < words->count; ++i) {
2332 133 const char *const word = words->vector[i];
2333
2334
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 91 times.
133 if (i > 0) {
2335 42 *ptr++ = ' ';
2336
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 41 times.
42 if (ptr > lim) {
2337 1 goto overflow;
2338 }
2339 }
2340
2341
1/2
✓ Branch 0 taken 50011 times.
✗ Branch 1 not taken.
50011 for (ptrdiff_t j = 0; (size_t) j < MAX_STR_SIZE; ++j) {
2342 50011 const char ch = word[j];
2343
2344
3/3
✓ Branch 0 taken 129 times.
✓ Branch 1 taken 200 times.
✓ Branch 2 taken 49682 times.
50011 switch (ch) {
2345 129 case '\0':
2346
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 118 times.
129 if (j == 0) {
2347
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 10 times.
32 for (int k = 0; k < 2; ++k) {
2348 22 *ptr++ = '\'';
2349
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 21 times.
22 if (ptr > lim) {
2350 1 goto overflow;
2351 }
2352 }
2353 }
2354 128 goto next;
2355 200 case ' ':
2356 /* Falls through */
2357 case '\t':
2358 /* Falls through */
2359 case '\n':
2360 /* Falls through */
2361 case '\\':
2362 /* Falls through */
2363 case '\"':
2364 200 *ptr++ = '\\';
2365
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 199 times.
200 if (ptr > lim) {
2366 1 goto overflow;
2367 }
2368 /* Falls through */
2369 default:
2370 49881 *ptr++ = ch;
2371
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 49879 times.
49881 if (ptr > lim) {
2372 2 goto overflow;
2373 }
2374 }
2375 }
2376
2377 overflow:
2378 5 free(dest);
2379
2380 5 errno = EOVERFLOW;
2381 5 return NULL;
2382
2383 128 next:
2384 /* Next word */;
2385 }
2386
2387 88 return dest;
2388 }
2389
2390
2391 exportable int
2392 76328 wordsplit(Words *const words, char *const buf,
2393 const size_t slen, const char *const str)
2394 {
2395 76328 int retval = 0;
2396
2397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76328 times.
76328 assert(words);
2398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76328 times.
76328 assert(!size_exceeds_ptr(words->count));
2399
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76328 times.
76328 assert(!size_exceeds_ptr(words->max));
2400
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76328 times.
76328 assert(buf);
2401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76328 times.
76328 assert(!size_exceeds_ptr(slen));
2402
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76328 times.
76328 assert(str);
2403
3/4
✓ Branch 0 taken 74604 times.
✓ Branch 1 taken 1724 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 74604 times.
76328 assert(buf <= str || buf > &str[slen]);
2404
2405
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 76327 times.
76328 if (slen >= MAX_STR_SIZE) {
2406 1 return EOVERFLOW;
2407 }
2408
2409 /* cppcheck-suppress assertWithSideEffect; no side-effects here */
2410
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76327 times.
76327 assert(strnlen(str, MAX_STR_SIZE) == slen);
2411
2412 76327 retval = wordsinit(words, INIT_NWORDS);
2413
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76327 times.
76327 if (retval != 0) {
2414 return retval;
2415 }
2416
2417 76327 size_t pos = strspn(str, " \t\n");
2418 76327 char *ptr = buf;
2419 76327 bool done = false;
2420 76327 bool escape = false;
2421 76327 char quote = '\0';
2422
2423 34117 while (true) {
2424 110444 char *const word = ptr;
2425
2426
2/2
✓ Branch 0 taken 962193 times.
✓ Branch 1 taken 42840 times.
1005033 for (; pos <= slen; ++pos) {
2427 962193 const char ch = str[pos];
2428
2429
2/2
✓ Branch 0 taken 518240 times.
✓ Branch 1 taken 443953 times.
962193 if (quote != '\0') {
2430
2/2
✓ Branch 0 taken 18235 times.
✓ Branch 1 taken 500005 times.
518240 if (escape) {
2431 18235 escape = false;
2432
2/2
✓ Branch 0 taken 14164 times.
✓ Branch 1 taken 4071 times.
18235 switch (ch) {
2433 14164 default:
2434 14164 *ptr++ = '\\';
2435 /* Falls through */
2436 18235 case '\\':
2437 /* Falls through */
2438 case '\"':
2439 /* Falls through */
2440 case '\n':
2441 18235 *ptr++ = ch;
2442 18235 break;
2443 }
2444 } else {
2445
3/3
✓ Branch 0 taken 138816 times.
✓ Branch 1 taken 36462 times.
✓ Branch 2 taken 324727 times.
500005 switch (ch) {
2446 138816 case '\'':
2447 /* Falls through */
2448 case '"':
2449
2/2
✓ Branch 0 taken 69612 times.
✓ Branch 1 taken 69204 times.
138816 if (ch == quote) {
2450 69612 quote = '\0';
2451 } else {
2452 69204 *ptr++ = ch;
2453 }
2454 138816 break;
2455 36462 case '\\':
2456
2/2
✓ Branch 0 taken 18235 times.
✓ Branch 1 taken 18227 times.
36462 if (quote == '"') {
2457 18235 escape = true;
2458 18235 continue;
2459 }
2460 /* Falls through */
2461 default:
2462 342954 *ptr++ = ch;
2463 342954 break;
2464 }
2465 }
2466
2/2
✓ Branch 0 taken 30613 times.
✓ Branch 1 taken 413340 times.
443953 } else if (escape) {
2467 30613 escape = false;
2468 30613 *ptr++ = ch;
2469 } else {
2470
5/5
✓ Branch 0 taken 30613 times.
✓ Branch 1 taken 106620 times.
✓ Branch 2 taken 34164 times.
✓ Branch 3 taken 33440 times.
✓ Branch 4 taken 208503 times.
413340 switch (ch) {
2471 30613 case '\\':
2472 30613 escape = true;
2473 30613 break;
2474 106620 case '\'':
2475 /* Falls through */
2476 case '"':
2477 106620 quote = ch;
2478 106620 break;
2479 34164 case '\n':
2480 /* Falls through */
2481 case '\t':
2482 /* Falls through */
2483 case ' ':
2484 /* cppcheck-suppress misra-c2012-14.2; not a clean loop */
2485 34164 pos += strspn(&str[pos], " \t\n");
2486 /* Falls through */
2487 67604 case '\0':
2488 67604 done = str[pos] == '\0';
2489
2490 67604 *ptr++ = '\0';
2491 /* cppcheck-suppress assertWithSideEffect */
2492
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67604 times.
67604 assert(strnlen(word, MAX_STR_SIZE) <= slen);
2493
2494 67604 retval = wordsadd(words, word);
2495
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67604 times.
67604 if (retval != 0) {
2496 goto error;
2497 }
2498
2499
2/2
✓ Branch 0 taken 33487 times.
✓ Branch 1 taken 34117 times.
67604 if (done) {
2500 33487 goto exit;
2501 }
2502 34117 goto next;
2503 break;
2504 208503 default:
2505 208503 *ptr++ = ch;
2506 208503 break;
2507 }
2508 }
2509 }
2510
2511 42840 retval = EINVAL;
2512
2513 42840 error:
2514 42840 errno = wordsfree(words);
2515
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42840 times.
42840 if (!NDEBUG && errno != 0) {
2516 /* NOTREACHED */
2517 warn(__FILE__ ":%d: wordsfree", __LINE__);
2518 }
2519 42840 return retval;
2520
2521 34117 next:
2522 /* Next word */;
2523 }
2524
2525 33487 exit:
2526 33487 return wordsrealloc(words, words->count);
2527 }
2528
2529
2530 exportable int
2531 85868 wordsrealloc(Words *const words, const size_t count) {
2532
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85868 times.
85868 assert(words);
2533
2534
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 85865 times.
85868 if (count >= MAX_NWORDS) {
2535 3 return EOVERFLOW;
2536 }
2537
2538
2/2
✓ Branch 0 taken 74262 times.
✓ Branch 1 taken 11603 times.
85865 if (count != words->max) {
2539 74262 size_t nptrs = count + 1U;
2540
2541
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74262 times.
74262 if (SIZE_MAX / sizeof(*words->vector) < nptrs) {
2542 /* NOTREACHED */
2543 return EOVERFLOW;
2544 }
2545 74262 const size_t newsize = nptrs * sizeof(*words->vector);
2546
2547
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74262 times.
74262 assert(newsize > 0U);
2548
2549 /* RATS: ignore; everything past the NULL terminator is irrelevant */
2550 74262 char **const tmp = (char **) realloc(words->vector, newsize);
2551
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 74261 times.
74262 if (!tmp) {
2552 1 return errno;
2553 }
2554
2555 74261 words->vector = tmp;
2556 74261 words->max = count;
2557 }
2558
2559 85864 return 0;
2560 }
2561
2562
2563 #endif /* !defined(PARA_C_AS_HDR) */
2564
2565 #endif /* !defined(PARA_C_AS_HDR) || !PARA_C_AS_HDR || !defined PARA_C */
2566