1 /*
2 ** Copyright (C) 2011-2020 by Carnegie Mellon University.
3 **
4 ** @OPENSOURCE_LICENSE_START@
5 ** See license information in ../../LICENSE.txt
6 ** @OPENSOURCE_LICENSE_END@
7 */
8 
9 /*
10 **  skoptionsctx.c
11 **
12 **    Support for --xargs, reading from stdin, and looping over
13 **    filenames on the command line.
14 **
15 **    Mark Thomas
16 **    May 2011
17 **
18 */
19 
20 #include <silk/silk.h>
21 
22 RCSIDENT("$SiLK: skoptionsctx.c ef14e54179be 2020-04-14 21:57:45Z mthomas $");
23 
24 #include <silk/utils.h>
25 #include <silk/skstream.h>
26 
27 
28 /* LOCAL DEFINES AND TYPEDEFS */
29 
30 #define PATH_IS_STDIN(path)                                     \
31     (0 == strcmp((path), "-") || 0 == strcmp((path), "stdin"))
32 #define PATH_IS_STDOUT(path)                                    \
33     (0 == strcmp((path), "-") || 0 == strcmp((path), "stdout"))
34 
35 /* typedef struct sk_options_ctx_st sk_options_ctx_t; */
36 struct sk_options_ctx_st {
37     sk_options_ctx_open_cb_t    open_cb_fn;
38     FILE           *print_filenames;
39     skstream_t     *xargs;
40     skstream_t     *copy_input;
41     const char     *input_pipe;
42     char          **argv;
43     int             argc;
44     int             arg_index;
45     unsigned int    flags;
46     unsigned        stdin_used      :1;
47     unsigned        stdout_used     :1;
48     unsigned        parse_ok        :1;
49     unsigned        init_ok         :1;
50     unsigned        init_failed     :1;
51     unsigned        read_stdin      :1;
52     unsigned        no_more_inputs  :1;
53 };
54 
55 
56 /* LOCAL VARIABLE DEFINITIONS */
57 
58 static const struct options_ctx_options_st {
59     struct option   opt;
60     const char     *help;
61 } options_ctx_options[] = {
62     {{"print-filenames", NO_ARG,       0, SK_OPTIONS_CTX_PRINT_FILENAMES},
63      ("Print input filenames while processing. Def. no")},
64     {{"copy-input",      REQUIRED_ARG, 0, SK_OPTIONS_CTX_COPY_INPUT},
65      ("Copy all input SiLK Flows to given pipe or file. Def. No")},
66     {{"input-pipe",      REQUIRED_ARG, 0, SK_OPTIONS_CTX_INPUT_PIPE},
67      ("Get input byte stream from pipe (stdin|pipe).\n"
68       "\tThis switch is deprecated and will be removed in a future release.\n"
69       "\tDefault is stdin if no filenames are given on the command line")},
70     {{"xargs",           OPTIONAL_ARG, 0, SK_OPTIONS_CTX_XARGS},
71      ("Read the names of the files to process from named text file,\n"
72       "\tone name per line, or from the standard input if no parameter."
73       " Def. no")},
74     {{0, 0, 0, 0}, 0}    /* sentinel */
75 };
76 
77 
78 
79 /* FUNCTION DEFINITIONS */
80 
81 static const char *
optionsCtxSwitchName(int opt_index)82 optionsCtxSwitchName(
83     int                 opt_index)
84 {
85     size_t i;
86 
87     for (i = 0; options_ctx_options[i].help; ++i) {
88         if (options_ctx_options[i].opt.val == opt_index) {
89             return options_ctx_options[i].opt.name;
90         }
91     }
92     skAbortBadCase(opt_index);
93 }
94 
95 static int
optionsCtxHandler(clientData cData,int opt_index,char * opt_arg)96 optionsCtxHandler(
97     clientData          cData,
98     int                 opt_index,
99     char               *opt_arg)
100 {
101     sk_options_ctx_t *arg_ctx = (sk_options_ctx_t*)cData;
102     int rv;
103 
104     if (opt_arg && strlen(opt_arg) == strspn(opt_arg, "\t\n\v\f\r ")) {
105         skAppPrintErr("Invalid %s: Argument contains only whitespace",
106                       optionsCtxSwitchName(opt_index));
107         return 1;
108     }
109 
110     switch (opt_index) {
111       case SK_OPTIONS_CTX_PRINT_FILENAMES:
112         arg_ctx->print_filenames = stderr;
113         break;
114 
115       case SK_OPTIONS_CTX_COPY_INPUT:
116         if (arg_ctx->copy_input) {
117             skAppPrintErr("Invalid %s: Switch used multiple times",
118                           optionsCtxSwitchName(opt_index));
119             return 1;
120         }
121         if (NULL == opt_arg || PATH_IS_STDOUT(opt_arg)) {
122             if (arg_ctx->stdout_used) {
123                 skAppPrintErr("Multiple outputs attempt"
124                               " to use standard output");
125                 return 1;
126             }
127             arg_ctx->stdout_used = 1;
128         }
129         if ((rv = skStreamCreate(&arg_ctx->copy_input, SK_IO_WRITE,
130                                  SK_CONTENT_SILK_FLOW))
131             || (rv = skStreamBind(arg_ctx->copy_input, opt_arg)))
132         {
133             skStreamPrintLastErr(arg_ctx->copy_input, rv, skAppPrintErr);
134             skStreamDestroy(&arg_ctx->copy_input);
135             return 1;
136         }
137         break;
138 
139       case SK_OPTIONS_CTX_XARGS:
140         if (arg_ctx->xargs) {
141             skAppPrintErr("Invalid %s: Switch used multiple times",
142                           optionsCtxSwitchName(opt_index));
143             return 1;
144         }
145         if (NULL == opt_arg || PATH_IS_STDIN(opt_arg)) {
146             if (arg_ctx->stdin_used) {
147                 skAppPrintErr("Multiple inputs attempt to use standard input");
148                 return 1;
149             }
150             arg_ctx->stdin_used = 1;
151         }
152         if ((rv = skStreamCreate(&arg_ctx->xargs, SK_IO_READ, SK_CONTENT_TEXT))
153             || (rv = skStreamBind(arg_ctx->xargs, (opt_arg ? opt_arg : "-"))))
154         {
155             skStreamPrintLastErr(arg_ctx->xargs, rv, &skAppPrintErr);
156             skStreamDestroy(&arg_ctx->xargs);
157             return 1;
158         }
159         break;
160 
161       case SK_OPTIONS_CTX_INPUT_PIPE:
162         if (arg_ctx->input_pipe) {
163             skAppPrintErr("Invalid %s: Switch used multiple times",
164                           optionsCtxSwitchName(opt_index));
165             return 1;
166         }
167         if (NULL == opt_arg || PATH_IS_STDIN(opt_arg)) {
168             if (FILEIsATty(stdin)
169                 && (arg_ctx->flags & (SK_OPTIONS_CTX_INPUT_BINARY
170                                       | SK_OPTIONS_CTX_INPUT_SILK_FLOW)))
171             {
172                 skAppPrintErr(("Invalid %s '%s': "
173                                "Will not read binary data on a terminal"),
174                               optionsCtxSwitchName(SK_OPTIONS_CTX_INPUT_PIPE),
175                               opt_arg);
176                 return 1;
177             }
178             if (arg_ctx->stdin_used) {
179                 skAppPrintErr("Multiple inputs attempt to use standard input");
180                 return 1;
181             }
182             arg_ctx->stdin_used = 1;
183         }
184         arg_ctx->input_pipe = opt_arg;
185         break;
186 
187       default:
188         skAbortBadCase(opt_index);
189     }
190 
191     return 0;
192 }
193 
194 
195 int
skOptionsCtxCopyStreamClose(sk_options_ctx_t * arg_ctx,sk_msg_fn_t err_fn)196 skOptionsCtxCopyStreamClose(
197     sk_options_ctx_t   *arg_ctx,
198     sk_msg_fn_t         err_fn)
199 {
200     int rv;
201 
202     if (arg_ctx->copy_input && arg_ctx->init_ok) {
203         rv = skStreamClose(arg_ctx->copy_input);
204         if (rv && err_fn) {
205             skStreamPrintLastErr(arg_ctx->copy_input, rv, err_fn);
206         }
207         return rv;
208     }
209     return 0;
210 }
211 
212 
213 int
skOptionsCtxCopyStreamIsActive(const sk_options_ctx_t * arg_ctx)214 skOptionsCtxCopyStreamIsActive(
215     const sk_options_ctx_t *arg_ctx)
216 {
217     return ((arg_ctx->copy_input) ? 1 : 0);
218 }
219 
220 
221 int
skOptionsCtxCopyStreamIsStdout(const sk_options_ctx_t * arg_ctx)222 skOptionsCtxCopyStreamIsStdout(
223     const sk_options_ctx_t *arg_ctx)
224 {
225     if (arg_ctx->copy_input) {
226         return PATH_IS_STDOUT(skStreamGetPathname(arg_ctx->copy_input));
227     }
228     return 0;
229 }
230 
231 
232 int
skOptionsCtxCountArgs(const sk_options_ctx_t * arg_ctx)233 skOptionsCtxCountArgs(
234     const sk_options_ctx_t *arg_ctx)
235 {
236     if (!arg_ctx->parse_ok) {
237         return -1;
238     }
239     return (arg_ctx->argc - arg_ctx->arg_index);
240 }
241 
242 
243 int
skOptionsCtxCreate(sk_options_ctx_t ** arg_ctx,unsigned int flags)244 skOptionsCtxCreate(
245     sk_options_ctx_t  **arg_ctx,
246     unsigned int        flags)
247 {
248     *arg_ctx = (sk_options_ctx_t*)calloc(1, sizeof(sk_options_ctx_t));
249     if (NULL == *arg_ctx) {
250         return -1;
251     }
252     (*arg_ctx)->flags = flags;
253     return 0;
254 }
255 
256 
257 int
skOptionsCtxDestroy(sk_options_ctx_t ** arg_ctx)258 skOptionsCtxDestroy(
259     sk_options_ctx_t  **arg_ctx)
260 {
261     sk_options_ctx_t *ctx;
262     int rv = 0;
263 
264     if (NULL == arg_ctx || NULL == *arg_ctx) {
265         return 0;
266     }
267     ctx = *arg_ctx;
268     *arg_ctx = NULL;
269 
270     skStreamDestroy(&ctx->xargs);
271     if (ctx->copy_input) {
272         if (ctx->init_ok) {
273             rv = skStreamClose(ctx->copy_input);
274         }
275         skStreamDestroy(&ctx->copy_input);
276     }
277     free(ctx);
278     return rv;
279 }
280 
281 
282 FILE *
skOptionsCtxGetPrintFilenames(const sk_options_ctx_t * arg_ctx)283 skOptionsCtxGetPrintFilenames(
284     const sk_options_ctx_t *arg_ctx)
285 {
286     return arg_ctx->print_filenames;
287 }
288 
289 
290 int
skOptionsCtxNextArgument(sk_options_ctx_t * arg_ctx,char ** arg)291 skOptionsCtxNextArgument(
292     sk_options_ctx_t   *arg_ctx,
293     char              **arg)
294 {
295     static char buf[PATH_MAX];
296     int rv;
297 
298     assert(arg_ctx);
299     assert(arg);
300 
301     if (arg_ctx->no_more_inputs) {
302         return 1;
303     }
304     if (!arg_ctx->parse_ok || arg_ctx->init_failed) {
305         return -1;
306     }
307     if (!arg_ctx->init_ok) {
308         rv = skOptionsCtxOpenStreams(arg_ctx, NULL);
309         if (rv) {
310             return rv;
311         }
312     }
313 
314     if (arg_ctx->xargs) {
315         for (;;) {
316             rv = skStreamGetLine(arg_ctx->xargs, buf, sizeof(buf), NULL);
317             if (SKSTREAM_OK == rv) {
318                 *arg = buf;
319                 return 0;
320             }
321             if (SKSTREAM_ERR_LONG_LINE == rv) {
322                 continue;
323             }
324             arg_ctx->no_more_inputs = 1;
325             if (SKSTREAM_ERR_EOF == rv) {
326                 return 1;
327             }
328             skStreamPrintLastErr(arg_ctx->xargs, rv, skAppPrintErr);
329             return -1;
330         }
331     }
332     if (arg_ctx->input_pipe) {
333         arg_ctx->no_more_inputs = 1;
334         *arg = (char*)arg_ctx->input_pipe;
335         return 0;
336     }
337     if (arg_ctx->read_stdin) {
338         arg_ctx->no_more_inputs = 1;
339         *arg = (char*)"-";
340         return 0;
341     }
342     if (arg_ctx->arg_index < arg_ctx->argc) {
343         *arg = arg_ctx->argv[arg_ctx->arg_index];
344         ++arg_ctx->arg_index;
345         return 0;
346     }
347     arg_ctx->no_more_inputs = 1;
348     return 1;
349 }
350 
351 
352 int
skOptionsCtxNextSilkFile(sk_options_ctx_t * arg_ctx,skstream_t ** stream,sk_msg_fn_t err_fn)353 skOptionsCtxNextSilkFile(
354     sk_options_ctx_t   *arg_ctx,
355     skstream_t        **stream,
356     sk_msg_fn_t         err_fn)
357 {
358     char *path;
359     int rv;
360 
361     for (;;) {
362         rv = skOptionsCtxNextArgument(arg_ctx, &path);
363         if (rv != 0) {
364             return rv;
365         }
366         rv = skStreamOpenSilkFlow(stream, path, SK_IO_READ);
367         if (rv != SKSTREAM_OK) {
368             if (err_fn) {
369                 skStreamPrintLastErr(*stream, rv, err_fn);
370                 skStreamDestroy(stream);
371             }
372             return -1;
373         }
374         if (arg_ctx->open_cb_fn) {
375             rv = arg_ctx->open_cb_fn(*stream);
376             if (rv) {
377                 if (rv > 0) {
378                     skStreamDestroy(stream);
379                     continue;
380                 }
381                 return rv;
382             }
383         }
384         if (arg_ctx->copy_input) {
385             skStreamSetCopyInput(*stream, arg_ctx->copy_input);
386         }
387         if (arg_ctx->print_filenames) {
388             fprintf(arg_ctx->print_filenames, "%s\n", path);
389         }
390         return 0;
391     }
392 }
393 
394 
395 int
skOptionsCtxOpenStreams(sk_options_ctx_t * arg_ctx,sk_msg_fn_t err_fn)396 skOptionsCtxOpenStreams(
397     sk_options_ctx_t   *arg_ctx,
398     sk_msg_fn_t         err_fn)
399 {
400     int rv;
401 
402     if (!arg_ctx->parse_ok) {
403         return -1;
404     }
405     if (arg_ctx->init_ok) {
406         return 0;
407     }
408     if (arg_ctx->init_failed) {
409         return -1;
410     }
411 
412     if (arg_ctx->xargs) {
413         rv = skStreamOpen(arg_ctx->xargs);
414         if (rv) {
415             if (err_fn) {
416                 skStreamPrintLastErr(arg_ctx->xargs, rv, err_fn);
417             }
418             arg_ctx->init_failed = 1;
419             return -1;
420         }
421     }
422     if (arg_ctx->copy_input) {
423         rv = skStreamOpen(arg_ctx->copy_input);
424         if (rv) {
425             if (err_fn) {
426                 skStreamPrintLastErr(arg_ctx->copy_input, rv, err_fn);
427             }
428             arg_ctx->init_failed = 1;
429             return -1;
430         }
431     }
432 
433     arg_ctx->init_ok = 1;
434     return 0;
435 }
436 
437 
438 /* FIXME: consider adding a separate flags parameter here */
439 int
skOptionsCtxOptionsParse(sk_options_ctx_t * arg_ctx,int argc,char ** argv)440 skOptionsCtxOptionsParse(
441     sk_options_ctx_t   *arg_ctx,
442     int                 argc,
443     char              **argv)
444 {
445     if (NULL == arg_ctx) {
446         return skOptionsParse(argc, argv);
447     }
448 
449     arg_ctx->argc = argc;
450     arg_ctx->argv = argv;
451     arg_ctx->arg_index = skOptionsParse(argc, argv);
452     if (arg_ctx->arg_index < 0) {
453         return arg_ctx->arg_index;
454     }
455 
456     /*
457      * if (ignore_non_switch_args) {
458      *     return arg_index;
459      * }
460      */
461 
462     /* handle case where all args are specified with switches */
463     if (arg_ctx->flags & SK_OPTIONS_CTX_SWITCHES_ONLY) {
464         if (arg_ctx->arg_index != argc) {
465             skAppPrintErr("Too many arguments or unrecognized switch '%s'",
466                           argv[arg_ctx->arg_index]);
467             return -1;
468         }
469         return 0;
470     }
471 
472     /* some sort of input is required */
473 
474     if (arg_ctx->xargs) {
475         if (arg_ctx->input_pipe) {
476             skAppPrintErr("May not use both --%s and --%s",
477                           optionsCtxSwitchName(SK_OPTIONS_CTX_XARGS),
478                           optionsCtxSwitchName(SK_OPTIONS_CTX_INPUT_PIPE));
479             return 1;
480         }
481         if (arg_ctx->arg_index != argc) {
482             skAppPrintErr("May not use --%s and give files on command line",
483                           optionsCtxSwitchName(SK_OPTIONS_CTX_XARGS));
484             return -1;
485         }
486         arg_ctx->parse_ok = 1;
487         return 0;
488     }
489 
490     if (arg_ctx->input_pipe) {
491         if (arg_ctx->arg_index != argc) {
492             skAppPrintErr("May not use --%s and give files on command line",
493                           optionsCtxSwitchName(SK_OPTIONS_CTX_INPUT_PIPE));
494             return -1;
495         }
496         arg_ctx->parse_ok = 1;
497         return 0;
498     }
499 
500     if (!(arg_ctx->flags & SK_OPTIONS_CTX_ALLOW_STDIN)) {
501         if (arg_ctx->arg_index == argc) {
502             skAppPrintErr("No input files specified on the command line");
503             return -1;
504         }
505         arg_ctx->parse_ok = 1;
506         return 0;
507     }
508 
509     /* stdin or files listed on command line allowed */
510 
511     if (arg_ctx->arg_index < argc) {
512         arg_ctx->parse_ok = 1;
513         return 0;
514     }
515 
516     if (FILEIsATty(stdin)
517         && (arg_ctx->flags &
518             (SK_OPTIONS_CTX_INPUT_BINARY | SK_OPTIONS_CTX_INPUT_SILK_FLOW)))
519     {
520         skAppPrintErr("No input files specified on the command line"
521                       " and standard input is a terminal");
522         return -1;
523     }
524     if (arg_ctx->stdin_used) {
525         skAppPrintErr("Multiple inputs attempt to use standard input");
526         return 1;
527     }
528     arg_ctx->stdin_used = 1;
529     arg_ctx->read_stdin = 1;
530 
531     arg_ctx->parse_ok = 1;
532     return 0;
533 }
534 
535 
536 int
skOptionsCtxOptionsRegister(const sk_options_ctx_t * arg_ctx)537 skOptionsCtxOptionsRegister(
538     const sk_options_ctx_t *arg_ctx)
539 {
540     size_t i;
541     int rv = 0;
542 
543     for (i = 0; options_ctx_options[i].help && 0 == rv; ++i) {
544         if (arg_ctx->flags & options_ctx_options[i].opt.val) {
545             rv = skOptionsRegisterCount(&options_ctx_options[i].opt, 1,
546                                         optionsCtxHandler,(clientData)arg_ctx);
547         }
548     }
549     return rv;
550 }
551 
552 void
skOptionsCtxOptionsUsage(const sk_options_ctx_t * arg_ctx,FILE * fh)553 skOptionsCtxOptionsUsage(
554     const sk_options_ctx_t *arg_ctx,
555     FILE                   *fh)
556 {
557     size_t i;
558 
559     for (i = 0; options_ctx_options[i].help; ++i) {
560         if (arg_ctx->flags & options_ctx_options[i].opt.val) {
561             fprintf(fh, "--%s %s. %s\n", options_ctx_options[i].opt.name,
562                     SK_OPTION_HAS_ARG(options_ctx_options[i].opt),
563                     options_ctx_options[i].help);
564         }
565     }
566 }
567 
568 
569 void
skOptionsCtxSetOpenCallback(sk_options_ctx_t * arg_ctx,sk_options_ctx_open_cb_t open_callback_fn)570 skOptionsCtxSetOpenCallback(
571     sk_options_ctx_t           *arg_ctx,
572     sk_options_ctx_open_cb_t    open_callback_fn)
573 {
574     assert(arg_ctx);
575     arg_ctx->open_cb_fn = open_callback_fn;
576 }
577 
578 
579 /*
580 ** Local Variables:
581 ** mode:c
582 ** indent-tabs-mode:nil
583 ** c-basic-offset:4
584 ** End:
585 */
586