1 /* Copyright 2012-present Facebook, Inc.
2  * Licensed under the Apache License, Version 2.0 */
3 
4 #include "watchman.h"
5 #include <getopt.h>
6 
7 #define IS_REQUIRED(x)  (x) == REQ_STRING
8 
9 /* One does not simply use getopt_long() */
10 
usage(struct watchman_getopt * opts,FILE * where)11 void usage(struct watchman_getopt *opts, FILE *where)
12 {
13   int i;
14   size_t len;
15   size_t longest = 0;
16   const char *label;
17 
18   fprintf(where, "Usage: watchman [opts] command\n");
19 
20   /* measure up option names so we can format nicely */
21   for (i = 0; opts[i].optname; i++) {
22     label = opts[i].arglabel ? opts[i].arglabel : "ARG";
23 
24     len = strlen(opts[i].optname);
25     switch (opts[i].argtype) {
26       case REQ_STRING:
27         len += strlen(label) + strlen("=");
28         break;
29       default:
30         ;
31     }
32 
33     if (opts[i].shortopt) {
34       len += strlen("-X, ");
35     }
36 
37     if (len > longest) {
38       longest = len;
39     }
40   }
41 
42   /* space between option definition and help text */
43   longest += 3;
44 
45   for (i = 0; opts[i].optname; i++) {
46     char buf[80];
47 
48     if (!opts[i].helptext) {
49       // This is a signal that this option shouldn't be printed out.
50       continue;
51     }
52 
53     label = opts[i].arglabel ? opts[i].arglabel : "ARG";
54 
55     fprintf(where, "\n ");
56     if (opts[i].shortopt) {
57       fprintf(where, "-%c, ", opts[i].shortopt);
58     } else {
59       fprintf(where, "    ");
60     }
61     switch (opts[i].argtype) {
62       case REQ_STRING:
63         snprintf(buf, sizeof(buf), "--%s=%s", opts[i].optname, label);
64         break;
65       default:
66         snprintf(buf, sizeof(buf), "--%s", opts[i].optname);
67         break;
68     }
69 
70     fprintf(where, "%-*s ", (unsigned int)longest, buf);
71 
72     fprintf(where, "%s", opts[i].helptext);
73     fprintf(where, "\n");
74   }
75 
76   print_command_list_for_help(where);
77 
78   fprintf(
79       where,
80       "\n"
81       "See https://github.com/facebook/watchman#watchman for more help\n"
82       "\n"
83       "Watchman, by Wez Furlong.\n"
84       "Copyright 2012-2017 Facebook, Inc.\n");
85 
86   exit(1);
87 }
88 
w_getopt(struct watchman_getopt * opts,int * argcp,char *** argvp,char *** daemon_argvp)89 bool w_getopt(struct watchman_getopt *opts, int *argcp, char ***argvp,
90     char ***daemon_argvp)
91 {
92   int num_opts, i;
93   char *nextshort;
94   int argc = *argcp;
95   char **argv = *argvp;
96   int long_pos = -1;
97   int res;
98   int num_daemon = 0;
99 
100   /* first build up the getopt_long bits that we need */
101   for (num_opts = 0; opts[num_opts].optname; num_opts++) {
102     ;
103   }
104 
105   /* to hold the args we pass to the daemon */
106   auto daemon_argv = (char**)calloc(num_opts + 1, sizeof(char*));
107   if (!daemon_argv) {
108     perror("calloc daemon opts");
109     abort();
110   }
111   *daemon_argvp = daemon_argv;
112 
113   /* something to hold the long options */
114   auto long_opts = (option*)calloc(num_opts + 1, sizeof(struct option));
115   if (!long_opts) {
116     perror("calloc struct option");
117     abort();
118   }
119 
120   /* and the short options */
121   auto shortopts = (char*)malloc((1 + num_opts) * 2);
122   if (!shortopts) {
123     perror("malloc shortopts");
124     abort();
125   }
126   nextshort = shortopts;
127   nextshort[0] = ':';
128   nextshort++;
129 
130   /* now transfer information into the space we made */
131   for (i = 0; i < num_opts; i++) {
132     long_opts[i].name = (char*)opts[i].optname;
133     long_opts[i].val = opts[i].shortopt;
134     switch (opts[i].argtype) {
135       case OPT_NONE:
136         long_opts[i].has_arg = no_argument;
137         break;
138       case REQ_STRING:
139       case REQ_INT:
140         long_opts[i].has_arg = required_argument;
141         break;
142     }
143 
144     if (opts[i].shortopt) {
145       nextshort[0] = (char)opts[i].shortopt;
146       nextshort++;
147 
148       if (long_opts[i].has_arg != no_argument) {
149         nextshort[0] = ':';
150         nextshort++;
151       }
152     }
153   }
154 
155   nextshort[0] = 0;
156 
157   while ((res = getopt_long(argc, argv, shortopts,
158         long_opts, &long_pos)) != -1) {
159     struct watchman_getopt *o;
160 
161     switch (res) {
162       case ':':
163         /* missing option argument.
164          * Check to see if it was actually optional */
165         for (long_pos = 0; long_pos < num_opts; long_pos++) {
166           if (opts[long_pos].shortopt == optopt) {
167             if (IS_REQUIRED(opts[long_pos].argtype)) {
168               fprintf(stderr, "--%s (-%c) requires an argument",
169                   opts[long_pos].optname,
170                   opts[long_pos].shortopt);
171               return false;
172             }
173           }
174         }
175         break;
176 
177       case '?':
178         /* unknown option */
179         fprintf(stderr, "Unknown or invalid option! %s\n", argv[optind-1]);
180         usage(opts, stderr);
181         return false;
182 
183       default:
184         if (res == 0) {
185           /* we got a long option */
186           o = &opts[long_pos];
187         } else {
188           /* map short option to the real thing */
189           o = NULL;
190           for (long_pos = 0; long_pos < num_opts; long_pos++) {
191             if (opts[long_pos].shortopt == res) {
192               o = &opts[long_pos];
193               break;
194             }
195           }
196         }
197 
198         if (o->is_daemon) {
199           char *val;
200           ignore_result(asprintf(&val, "--%s=%s", o->optname, optarg));
201           daemon_argv[num_daemon++] = val;
202         }
203 
204         /* store the argument if we found one */
205         if (o->argtype != OPT_NONE && o->val && optarg) {
206           switch (o->argtype) {
207             case REQ_INT:
208             {
209               auto ival = json_integer(atoi(optarg));
210               *(int*)o->val = (int)json_integer_value(ival);
211               cfg_set_arg(o->optname, ival);
212               break;
213             }
214             case REQ_STRING:
215             {
216               auto sval = typed_string_to_json(optarg, W_STRING_UNICODE);
217               *(char**)o->val = strdup(optarg);
218               cfg_set_arg(o->optname, sval);
219               break;
220             }
221             case OPT_NONE:
222               ;
223           }
224         }
225         if (o->argtype == OPT_NONE && o->val) {
226           auto bval = json_true();
227           *(int*)o->val = 1;
228           cfg_set_arg(o->optname, bval);
229         }
230     }
231 
232     long_pos = -1;
233   }
234 
235   free(long_opts);
236   free(shortopts);
237 
238   *argcp = argc - optind;
239   *argvp = argv + optind;
240   return true;
241 }
242 
243 /* vim:ts=2:sw=2:et:
244  */
245