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