[tpwd] Fix segfault when exactly one argument given
[tinyapps.git] / foreach.c
blobb4876bf65c9c3db7b9fe9eb4c4bab2706167cc4c
1 /*
2 * Runs a command for all specified arguments.
3 * Copyright (c) 2013 by Michal Nazareicz (mina86@mina86.com)
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 * This is part of Tiny Applications Collection
19 * -> http://tinyapps.sourceforge.net/
22 #define _POSIX_C_SOURCE 2
24 #include <errno.h>
25 #include <limits.h>
26 #include <signal.h>
27 #include <stdarg.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/select.h>
33 #include <sys/time.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <unistd.h>
39 static const char *const MARKER = "{+}";
42 static const char *ARGV0;
44 static void error(const char *fmt, ...) {
45 va_list ap;
46 va_start(ap, fmt);
47 fprintf(stderr, "%s: ", ARGV0);
48 vfprintf(stderr, fmt, ap);
49 va_end(ap);
53 static void usage(bool full) {
54 FILE *out = full ? stdout : stderr;
55 fprintf(out, "usage: %s [<options>] [--] <command> <arg>... -- <value> ...\n",
56 ARGV0);
57 if (full) {
58 fputs("Possible <options>:\n"
59 " -j<jobs> run <jobs> jobs at the same time\n"
60 " -J run one job per processor\n"
61 " -K stop once a job returns non-zero\n"
62 "<value> values to pass to the <command>\n"
63 "<command> command to run for each <value>\n"
64 "<arg> arguments to pass to the <command>,\n"
65 " {+} will be substituted by <value>,\n"
66 " if no {+} given, <value> will be passed at the end\n",
67 out);
68 } else {
69 fprintf(out, " %s --help\n", ARGV0);
74 struct options {
75 unsigned jobs;
76 bool keep_going;
77 const char **values, **command;
81 static unsigned parseJobs(char *arg) {
82 unsigned long val;
83 char *end;
85 errno = 0;
86 val = strtoul(arg, &end, 10);
87 if (!val || errno || val > UINT_MAX || *end) {
88 error("-j: %s: invalid argument\n", arg);
89 return 0;
92 return val;
95 static unsigned countProcessors(void) {
96 bool ignore = false;
97 unsigned count = 0;
98 char buffer[128];
99 FILE *fd;
101 fd = fopen("/proc/cpuinfo", "r");
102 if (!fd) {
103 error("/cpu/info: %s\n", strerror(errno));
104 error("assuming -j1\n");
105 return 1;
108 while (fgets(buffer, sizeof buffer, fd)) {
109 count += !ignore && strncmp(buffer, "processor\t", 10);
110 ignore = !strchr(buffer, '\n');
113 if (!count) {
114 error("no processors detected, assuming -j1\n");
115 count = 1;
117 return count;
121 static void takeArguments(const char ***argvp, const char ***outp,
122 const char *marker, bool wait_for_dd) {
123 const char **argv = *argvp, **out = *outp, *arg;
124 bool found = false;
126 if (*argv && !strcmp(**argvp, "--")) {
127 ++argv;
130 arg = "--"; /* Whatever non-NULL will suffice */
131 for (; arg; ++argv) {
132 arg = *argv;
133 if (arg) {
134 if (wait_for_dd && !strcmp(*argv, "--")) {
135 arg = NULL;
136 } else if (marker && !strcmp(arg, marker)) {
137 arg = marker;
138 found = true;
141 *out++ = arg;
144 if (marker && !found) {
145 out[-1] = marker;
146 *out++ = NULL;
149 *argvp = argv;
150 *outp = out;
154 static int parseArgs(int argc, char **_argv, struct options *opts) {
155 const char **argv, **out = (const char **)_argv;
156 int opt;
158 ARGV0 = _argv[0];
160 const char *arg = strrchr(ARGV0, '/');
161 if (arg) {
162 ARGV0 = arg + 1;
166 if (argc > 1 && !strcmp(_argv[1], "--help")) {
167 usage(true);
168 return -1;
171 opts->jobs = 1;
172 opts->keep_going = true;
173 opterr = 0;
174 while ((opt = getopt(argc, _argv, "+:j:JKh")) != -1) {
175 switch (opt) {
176 case 'h':
177 usage(true);
178 return -1;
179 case 'K':
180 opts->keep_going = false;
181 break;
182 case 'j':
183 opts->jobs = parseJobs(optarg);
184 if (!opts->jobs) {
185 goto usage;
187 break;
188 case 'J':
189 opts->jobs = countProcessors();
190 break;
191 case ':':
192 error("-%c: requires an argument\n", optopt);
193 goto usage;
194 default:
195 error("-%c: unknown option\n", optopt);
196 goto usage;
200 argv = (const char **)(_argv + optind);
202 opts->command = out;
203 takeArguments(&argv, &out, MARKER, true);
204 if (!*opts->command) {
205 error("no command given\n");
206 goto usage;
209 opts->values = out;
210 takeArguments(&argv, &out, NULL, false);
211 if (!*opts->values) {
212 error("no values given\n");
213 goto usage;
216 return 0;
218 usage:
219 usage(false);
220 return 1;
224 static bool run(struct options *opts, const char *value) {
225 const char **cmd;
226 pid_t pid;
228 pid = fork();
229 switch (pid) {
230 case -1:
231 error("fork: %s\n", strerror(errno));
232 return false;
234 case 0:
235 cmd = opts->command;
236 while (*cmd) {
237 if (*cmd == MARKER) {
238 *cmd = value;
240 ++cmd;
243 execvp(opts->command[0], (char **)opts->command);
244 error("%s: %s\n", opts->command[0], strerror(errno));
245 _exit(1);
248 return true;
252 static bool reap(int *rc, bool block) {
253 int status;
254 pid_t pid = waitpid(-1, &status, block ? 0 : WNOHANG);
255 if (pid <= 0) {
256 return false;
257 } else if (!status) {
258 /* nop */
259 } else if (WIFSIGNALED(status)) {
260 error("[%d]: killed by a signal: %d\n",
261 pid, WTERMSIG(status));
262 *rc |= 2;
263 } else {
264 error("[%d]: exited with exit code: %d\n",
265 pid, WEXITSTATUS(status));
266 *rc |= 4;
268 return true;
272 static int runCommands(struct options *opts) {
273 const char **value;
274 unsigned jobs = 0;
275 int rc = 0;
277 for (value = opts->values;
278 *value && (opts->keep_going || !rc);
279 ++value) {
280 if (!run(opts, *value)) {
281 continue;
284 ++jobs;
285 while (reap(&rc, jobs >= opts->jobs)) {
286 --jobs;
290 if (*value) {
291 error("terminating before all jobs were started\n");
294 while (jobs > 0) {
295 reap(&rc, true);
296 --jobs;
299 return rc;
303 int main(int argc, char **argv) {
304 struct options opts;
305 int rc;
307 rc = parseArgs(argc, argv, &opts);
308 if (rc) {
309 return rc < 0 ? 0 : rc;
312 return runCommands(&opts);