]> git.saurik.com Git - apple/libc.git/blob - libdarwin/ctl.c
Libc-1353.100.2.tar.gz
[apple/libc.git] / libdarwin / ctl.c
1 #include "internal.h"
2
3 #pragma mark Definitions
4 #define CTL_OUTPUT_WIDTH (80)
5 #define CTL_OUTPUT_OPTARG_PAD (28)
6 #define CTL_OUTPUT_LIST_PAD (4)
7 #define SUBCOMMAND_LINKER_SET "__subcommands"
8
9 #define OS_SUBCOMMAND_OPTIONS_FOREACH(_osco_i, _osc, _which, _i) \
10 while (((_osco_i) = &osc->osc_ ## _which[(_i)]) && \
11 ((_i) += 1, 1) && \
12 !((_osco_i)->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_TERMINATOR))
13
14 #pragma mark Types
15 OS_ENUM(os_subcommand_option_spec_fmt, uint64_t,
16 OS_SUBCOMMAND_OPTION_SPEC_SHORT,
17 OS_SUBCOMMAND_OPTION_SPEC_LONG,
18 OS_SUBCOMMAND_OPTION_SPEC_COMBINED,
19 );
20
21 #pragma mark Forward Declarations
22 static void _print_header(
23 FILE *f,
24 const char *hdr,
25 bool *already_done);
26 static const os_subcommand_t *_os_subcommand_find(
27 const char *name);
28 static void _os_subcommand_print_usage(
29 const os_subcommand_t *osc,
30 FILE *f);
31 static void _os_subcommand_print_help_line(
32 const os_subcommand_t *osc,
33 FILE *f);
34
35 #pragma mark Module Globals
36 static const os_subcommand_t __help_cmd;
37 static const os_subcommand_t *_help_cmd = &__help_cmd;
38
39 static const os_subcommand_t __main_cmd;
40 static const os_subcommand_t *_main_cmd = &__main_cmd;
41 static const os_subcommand_t *_internal_main_cmd = &__main_cmd;
42
43 #pragma mark Main Subcommand
44 static int _main_invoke(const os_subcommand_t *osc,
45 int argc,
46 const char *argv[]);
47
48 static const os_subcommand_option_t _main_positional[] = {
49 [0] = {
50 .osco_version = OS_SUBCOMMAND_OPTION_VERSION,
51 .osco_flags = 0,
52 .osco_option = NULL,
53 .osco_argument_usage = "subcommand",
54 .osco_argument_human = "The subcommand to invoke",
55 },
56 OS_SUBCOMMAND_OPTION_TERMINATOR,
57 };
58
59 static const os_subcommand_t __main_cmd = {
60 .osc_version = OS_SUBCOMMAND_VERSION,
61 .osc_flags = OS_SUBCOMMAND_FLAG_MAIN,
62 .osc_name = "_main",
63 .osc_desc = "main command",
64 .osc_optstring = NULL,
65 .osc_options = NULL,
66 .osc_required = NULL,
67 .osc_optional = NULL,
68 .osc_positional = _main_positional,
69 .osc_invoke = &_main_invoke,
70 };
71
72 static int
73 _main_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
74 {
75 return 0;
76 }
77
78 #pragma mark Help Subcommand
79 static int _help_invoke(const os_subcommand_t *osc,
80 int argc,
81 const char *argv[]);
82
83 static const os_subcommand_option_t _help_positional[] = {
84 [0] = {
85 .osco_version = OS_SUBCOMMAND_OPTION_VERSION,
86 .osco_flags = 0,
87 .osco_option = NULL,
88 .osco_argument_usage = "subcommand",
89 .osco_argument_human = "The subcommand to query for help",
90 },
91 OS_SUBCOMMAND_OPTION_TERMINATOR,
92 };
93
94 static const os_subcommand_t __help_cmd = {
95 .osc_version = OS_SUBCOMMAND_VERSION,
96 .osc_flags = 0,
97 .osc_name = "help",
98 .osc_desc = "prints helpful information",
99 .osc_optstring = NULL,
100 .osc_options = NULL,
101 .osc_required = NULL,
102 .osc_optional = NULL,
103 .osc_positional = _help_positional,
104 .osc_invoke = &_help_invoke,
105 };
106
107 static int
108 _help_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
109 {
110 int xit = -1;
111 const char *cmdname = NULL;
112 const os_subcommand_t *target = NULL;
113 FILE *f = stdout;
114
115 if (argc > 1) {
116 cmdname = argv[1];
117 }
118
119 if (cmdname) {
120 // Print usage information for the requested subcommand.
121 target = _os_subcommand_find(argv[1]);
122 if (!target) {
123 os_subcommand_fprintf(osc, stderr, "unrecognized subcommand: %s",
124 argv[1]);
125 xit = EX_USAGE;
126 } else {
127 xit = 0;
128 }
129 } else {
130 // Print general, top-level usage followed by a list of subcommands.
131 target = _main_cmd;
132 xit = 0;
133 }
134
135 if (xit) {
136 f = stderr;
137 }
138
139 _os_subcommand_print_usage(target, f);
140
141 if (target == _main_cmd) {
142 const os_subcommand_t **oscip = NULL;
143 const os_subcommand_t *osci = NULL;
144 bool header_printed = false;
145
146 LINKER_SET_FOREACH(oscip, const os_subcommand_t **,
147 SUBCOMMAND_LINKER_SET) {
148 osci = *oscip;
149
150 _print_header(f, "subcommands", &header_printed);
151 _os_subcommand_print_help_line(osci, f);
152 }
153
154 // Print the help subcommand last.
155 _os_subcommand_print_help_line(osc, f);
156 }
157
158 return xit;
159 }
160
161 #pragma mark Utilities
162 static void
163 _print_header(FILE *f, const char *hdr, bool *already_done)
164 {
165 if (*already_done) {
166 return;
167 }
168
169 crfprintf_np(f, "");
170 crfprintf_np(f, "%s:", hdr);
171 *already_done = true;
172 }
173
174 #pragma mark Module Routines
175 static char *
176 _os_subcommand_copy_option_spec_short(const os_subcommand_t *osc,
177 const os_subcommand_option_t *osco)
178 {
179 const struct option *opt = osco->osco_option;
180 char optbuff[64] = "";
181 char argbuff[64] = "";
182 char *final = NULL;
183 int ret = -1;
184
185 if (opt) {
186 snprintf(optbuff, sizeof(optbuff), "-%c", opt->val);
187
188 switch (opt->has_arg) {
189 case no_argument:
190 break;
191 case optional_argument:
192 snprintf(argbuff, sizeof(argbuff), "[%s]",
193 osco->osco_argument_usage);
194 break;
195 case required_argument:
196 snprintf(argbuff, sizeof(argbuff), "<%s>",
197 osco->osco_argument_usage);
198 break;
199 default:
200 __builtin_unreachable();
201 }
202 } else {
203 snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage);
204 }
205
206 ret = asprintf(&final, "%s%s", optbuff, argbuff);
207 if (ret < 0) {
208 os_assert_zero(ret);
209 }
210
211 return final;
212 }
213
214 static char *
215 _os_subcommand_copy_option_spec_long(const os_subcommand_t *osc,
216 const os_subcommand_option_t *osco)
217 {
218 const struct option *opt = osco->osco_option;
219 char optbuff[64] = "";
220 char argbuff[64] = "";
221 char *final = NULL;
222 int ret = -1;
223
224 if (opt) {
225 snprintf(optbuff, sizeof(optbuff), "--%s", opt->name);
226
227 switch (opt->has_arg) {
228 case no_argument:
229 break;
230 case optional_argument:
231 snprintf(argbuff, sizeof(argbuff), "[=%s]",
232 osco->osco_argument_usage);
233 break;
234 case required_argument:
235 snprintf(argbuff, sizeof(argbuff), "=<%s>",
236 osco->osco_argument_usage);
237 break;
238 default:
239 __builtin_unreachable();
240 }
241 } else {
242 snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage);
243 }
244
245 ret = asprintf(&final, "%s%s", optbuff, argbuff);
246 if (ret < 0) {
247 os_assert_zero(ret);
248 }
249
250 return final;
251 }
252
253 static char *
254 _os_subcommand_copy_option_spec(const os_subcommand_t *osc,
255 const os_subcommand_option_t *osco, os_subcommand_option_spec_fmt_t fmt)
256 {
257 int ret = -1;
258 char *spec = NULL;
259 char *__os_free spec_old = NULL;
260
261 switch (fmt) {
262 case OS_SUBCOMMAND_OPTION_SPEC_SHORT:
263 _os_subcommand_copy_option_spec_short(osc, osco);
264 break;
265 case OS_SUBCOMMAND_OPTION_SPEC_LONG:
266 _os_subcommand_copy_option_spec_long(osc, osco);
267 break;
268 case OS_SUBCOMMAND_OPTION_SPEC_COMBINED:
269 spec = _os_subcommand_copy_option_spec_long(osc, osco);
270 if (osco->osco_option) {
271 spec_old = spec;
272
273 ret = asprintf(&spec, "%s | -%c", spec, osco->osco_option->val);
274 if (ret < 0) {
275 os_assert_zero(ret);
276 }
277 }
278
279 break;
280 default:
281 __builtin_unreachable();
282 }
283
284 return spec;
285 }
286
287 static char *
288 _os_subcommand_copy_usage_line(const os_subcommand_t *osc)
289 {
290 char *usage_line = NULL;
291 size_t i = 0;
292 const os_subcommand_option_t *osco_i = NULL;
293 const char *optional_spec = "";
294 char subcmd_name[64];
295 int ret = -1;
296
297 // The usage line does not enumerate all possible optional options, just the
298 // required options. If there are optional options, then display that but
299 // otherwise leave them to be described by more extensive usage information.
300 if (osc->osc_optional) {
301 optional_spec = " [options]";
302 }
303
304 if (osc == _main_cmd) {
305 strlcpy(subcmd_name, "", sizeof(subcmd_name));
306 } else {
307 snprintf(subcmd_name, sizeof(subcmd_name), " %s", osc->osc_name);
308 }
309
310 ret = asprintf(&usage_line, "%s%s%s",
311 getprogname(), subcmd_name, optional_spec);
312 if (ret < 0) {
313 os_assert_zero(ret);
314 }
315
316 i = 0;
317 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, required, i) {
318 char *__os_free usage_line_old = NULL;
319 char *__os_free osco_spec = NULL;
320
321 usage_line_old = usage_line;
322
323 osco_spec = _os_subcommand_copy_option_spec_long(osc, osco_i);
324 ret = asprintf(&usage_line, "%s %s", usage_line, osco_spec);
325 if (ret < 0) {
326 os_assert_zero(ret);
327 }
328 }
329
330 i = 0;
331 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
332 char *__os_free usage_line_old = NULL;
333 char *__os_free osco_spec = NULL;
334 const char *braces[] = {
335 "<",
336 ">",
337 };
338
339 if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
340 braces[0] = "[";
341 braces[1] = "]";
342 }
343
344 usage_line_old = usage_line;
345
346 osco_spec = _os_subcommand_copy_option_spec_long(osc, osco_i);
347 ret = asprintf(&usage_line, "%s %s%s%s",
348 usage_line, braces[0], osco_spec, braces[1]);
349 if (ret < 0) {
350 os_assert_zero(ret);
351 }
352 }
353
354 if (osc == _main_cmd && osc != _internal_main_cmd) {
355 // Always include the positional subcommand when printing usage for the
356 // main subcommand. We do not expect it to be specified in a user-
357 // provided main subcommand.
358 char *__os_free usage_line_old = NULL;
359 char *__os_free osco_spec = NULL;
360
361 usage_line_old = usage_line;
362
363 osco_spec = _os_subcommand_copy_option_spec_long(osc,
364 &_main_positional[0]);
365 ret = asprintf(&usage_line, "%s <%s>", usage_line, osco_spec);
366 if (ret < 0) {
367 os_assert_zero(ret);
368 }
369 }
370
371 return usage_line;
372 }
373
374 static void
375 _os_subcommand_print_option_usage(const os_subcommand_t *osc,
376 const os_subcommand_option_t *osco, FILE *f)
377 {
378 char *__os_free opt_spec = NULL;
379 ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD;
380
381 opt_spec = _os_subcommand_copy_option_spec(osc, osco,
382 OS_SUBCOMMAND_OPTION_SPEC_COMBINED);
383 fprintf(f, " %-24s", opt_spec);
384
385 // If the usage specifier is long, start the description on the next line.
386 if (strlen(opt_spec) >= CTL_OUTPUT_OPTARG_PAD) {
387 initpad = CTL_OUTPUT_OPTARG_PAD;
388 crfprintf_np(f, "");
389 }
390
391 wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD,
392 CTL_OUTPUT_WIDTH, "%s",
393 osco->osco_argument_human);
394 }
395
396 static void
397 _os_subcommand_print_help_line(const os_subcommand_t *osc, FILE *f)
398 {
399 ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD;
400
401 fprintf(f, " %-24s", osc->osc_name);
402
403 // If the usage specifier is long, start the description on the next line.
404 if (strlen(osc->osc_name) >= CTL_OUTPUT_OPTARG_PAD) {
405 initpad = CTL_OUTPUT_OPTARG_PAD;
406 crfprintf_np(f, "");
407 }
408
409 wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD,
410 CTL_OUTPUT_WIDTH, "%s",
411 osc->osc_desc);
412 }
413
414 static void
415 _os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f)
416 {
417 size_t i = 0;
418 const os_subcommand_option_t *osco_i = NULL;
419 char *__os_free usage_line = NULL;
420 bool header_printed = false;
421
422 usage_line = _os_subcommand_copy_usage_line(osc);
423 wfprintf_np(f, 0, 0, CTL_OUTPUT_WIDTH, "usage: %s", usage_line);
424
425 if (osc->osc_required || osc->osc_positional || osc == _main_cmd) {
426 i = 0;
427 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, required, i) {
428 _print_header(f, "required", &header_printed);
429
430 crfprintf_np(f, "");
431 _os_subcommand_print_option_usage(osc, osco_i, f);
432 }
433
434 i = 0;
435 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
436 _print_header(f, "required", &header_printed);
437
438 if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
439 continue;
440 }
441
442 crfprintf_np(f, "");
443 _os_subcommand_print_option_usage(osc, osco_i, f);
444 }
445
446 if (osc == _main_cmd && osc != _internal_main_cmd) {
447 // We do not expect the user's main command to specify that a
448 // subcommand must follow, so always defer to ours.
449 _print_header(f, "required", &header_printed);
450
451 crfprintf_np(f, "");
452 _os_subcommand_print_option_usage(osc, &_main_positional[0], f);
453 }
454 }
455
456 header_printed = false;
457
458 if (osc->osc_optional || osc->osc_positional) {
459 i = 0;
460 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, optional, i) {
461 _print_header(f, "optional", &header_printed);
462
463 crfprintf_np(f, "");
464 _os_subcommand_print_option_usage(osc, osco_i, f);
465 }
466
467 i = 0;
468 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
469 if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
470 _print_header(f, "optional", &header_printed);
471
472 crfprintf_np(f, "");
473 _os_subcommand_print_option_usage(osc, osco_i, f);
474 }
475 }
476 }
477 }
478
479 static const os_subcommand_t *
480 _os_subcommand_find(const char *name)
481 {
482 const os_subcommand_t **oscip = NULL;
483
484 if (strcmp(_help_cmd->osc_name, name) == 0) {
485 return &__help_cmd;
486 }
487
488 LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) {
489 const os_subcommand_t *osci = *oscip;
490
491 if (osci->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) {
492 // The main subcommand cannot be invoked directly.
493 continue;
494 }
495
496 if (strcmp(osci->osc_name, name) == 0) {
497 return osci;
498 }
499 }
500
501 return NULL;
502 }
503
504 #pragma mark API
505 int
506 os_subcommand_main(int argc, const char *argv[],
507 os_subcommand_main_flags_t flags)
508 {
509 int xit = -1;
510 const char *cmdname = NULL;
511 const os_subcommand_t *osc = NULL;
512 const os_subcommand_t **oscip = NULL;
513
514 if (argc < 2) {
515 os_subcommand_fprintf(NULL, stderr, "please provide a subcommand");
516 xit = EX_USAGE;
517 goto __out;
518 }
519
520 // Find the main subcommand if any exists. Otherwise we'll just use our pre-
521 // canned main subcommand.
522 LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) {
523 osc = *oscip;
524 if (osc->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) {
525 _main_cmd = osc;
526 break;
527 }
528 }
529
530 osc = NULL;
531
532 // Invoke the main subcommand to snarf any global options. Our default
533 // implementation does nothing and just returns 0.
534 xit = _main_cmd->osc_invoke(_main_cmd, argc, argv);
535 if (xit) {
536 goto __out;
537 }
538
539 // Advance argument pointer and make the subcommand argv[0].
540 argc -= optind;
541 argv += optind;
542 cmdname = argv[0];
543
544 if (argc < 1) {
545 os_subcommand_fprintf(NULL, stderr, "please provide a subcommand");
546 xit = EX_USAGE;
547 goto __out;
548 }
549
550 osc = _os_subcommand_find(cmdname);
551 if (osc) {
552 if (osc->osc_flags & OS_SUBCOMMAND_FLAG_REQUIRE_ROOT) {
553 if (geteuid()) {
554 os_subcommand_fprintf(osc, stderr,
555 "subcommand requires root: %s",
556 cmdname);
557 xit = EX_NOPERM;
558 goto __out;
559 }
560 }
561
562 if (osc->osc_flags & OS_SUBCOMMAND_FLAG_TTYONLY) {
563 if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) {
564 os_subcommand_fprintf(osc, stderr,
565 "subcommand requires a tty: %s",
566 cmdname);
567 xit = EX_UNAVAILABLE;
568 goto __out;
569 }
570 }
571
572 xit = osc->osc_invoke(osc, argc, argv);
573 } else {
574 os_subcommand_fprintf(NULL, stderr, "unknonwn subcommand: %s", cmdname);
575 xit = EX_USAGE;
576 }
577
578 __out:
579 if (xit == EX_USAGE) {
580 if (!osc) {
581 osc = _main_cmd;
582 }
583
584 _os_subcommand_print_usage(osc, stderr);
585 }
586
587 return xit;
588 }
589
590 void
591 os_subcommand_fprintf(const os_subcommand_t *osc, FILE *f,
592 const char *fmt, ...)
593 {
594 va_list ap;
595
596 va_start(ap, fmt);
597 vcrfprintf_np(f, fmt, ap);
598 va_end(ap);
599 }
600
601 void
602 os_subcommand_vfprintf(const os_subcommand_t *osc, FILE *f,
603 const char *fmt, va_list ap)
604 {
605 if (!osc || (osc->osc_flags & OS_SUBCOMMAND_FLAG_MAIN)) {
606 fprintf(f, "%s: ", getprogname());
607 } else {
608 fprintf(f, "%s::%s: ", getprogname(), osc->osc_name);
609 }
610
611 vcrfprintf_np(f, fmt, ap);
612 }