]> git.saurik.com Git - apple/libc.git/blame - libdarwin/ctl.c
Libc-1353.100.2.tar.gz
[apple/libc.git] / libdarwin / ctl.c
CommitLineData
507116e3
A
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
e1ee4b85
A
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
15OS_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
22static void _print_header(
23 FILE *f,
24 const char *hdr,
25 bool *already_done);
26static const os_subcommand_t *_os_subcommand_find(
27 const char *name);
28static void _os_subcommand_print_usage(
29 const os_subcommand_t *osc,
30 FILE *f);
31static void _os_subcommand_print_help_line(
32 const os_subcommand_t *osc,
33 FILE *f);
34
507116e3 35#pragma mark Module Globals
e1ee4b85
A
36static const os_subcommand_t __help_cmd;
37static const os_subcommand_t *_help_cmd = &__help_cmd;
38
39static const os_subcommand_t __main_cmd;
40static const os_subcommand_t *_main_cmd = &__main_cmd;
41static const os_subcommand_t *_internal_main_cmd = &__main_cmd;
42
43#pragma mark Main Subcommand
44static int _main_invoke(const os_subcommand_t *osc,
45 int argc,
46 const char *argv[]);
47
48static 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
59static 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
72static 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
79static int _help_invoke(const os_subcommand_t *osc,
80 int argc,
81 const char *argv[]);
82
83static 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
94static 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
107static 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
162static 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}
507116e3
A
173
174#pragma mark Module Routines
175static char *
e1ee4b85
A
176_os_subcommand_copy_option_spec_short(const os_subcommand_t *osc,
177 const os_subcommand_option_t *osco)
507116e3 178{
e1ee4b85 179 const struct option *opt = osco->osco_option;
507116e3
A
180 char optbuff[64] = "";
181 char argbuff[64] = "";
182 char *final = NULL;
183 int ret = -1;
184
e1ee4b85
A
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 }
507116e3 205
e1ee4b85
A
206 ret = asprintf(&final, "%s%s", optbuff, argbuff);
207 if (ret < 0) {
208 os_assert_zero(ret);
507116e3
A
209 }
210
e1ee4b85
A
211 return final;
212}
213
214static 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);
507116e3
A
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
e1ee4b85
A
253static 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)
507116e3 256{
e1ee4b85
A
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();
507116e3
A
282 }
283
e1ee4b85 284 return spec;
507116e3
A
285}
286
e1ee4b85
A
287static char *
288_os_subcommand_copy_usage_line(const os_subcommand_t *osc)
507116e3 289{
e1ee4b85 290 char *usage_line = NULL;
507116e3 291 size_t i = 0;
e1ee4b85
A
292 const os_subcommand_option_t *osco_i = NULL;
293 const char *optional_spec = "";
294 char subcmd_name[64];
295 int ret = -1;
507116e3 296
e1ee4b85
A
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]";
507116e3
A
302 }
303
e1ee4b85
A
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 }
507116e3 309
e1ee4b85
A
310 ret = asprintf(&usage_line, "%s%s%s",
311 getprogname(), subcmd_name, optional_spec);
312 if (ret < 0) {
313 os_assert_zero(ret);
314 }
507116e3 315
e1ee4b85
A
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;
507116e3 320
e1ee4b85 321 usage_line_old = usage_line;
507116e3 322
e1ee4b85
A
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 }
507116e3 328 }
507116e3 329
e1ee4b85
A
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 }
507116e3 343
e1ee4b85 344 usage_line_old = usage_line;
507116e3 345
e1ee4b85
A
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 }
507116e3
A
352 }
353
e1ee4b85
A
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;
507116e3 360
e1ee4b85
A
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);
507116e3
A
368 }
369 }
370
e1ee4b85 371 return usage_line;
507116e3
A
372}
373
507116e3 374static void
e1ee4b85
A
375_os_subcommand_print_option_usage(const os_subcommand_t *osc,
376 const os_subcommand_option_t *osco, FILE *f)
507116e3 377{
e1ee4b85
A
378 char *__os_free opt_spec = NULL;
379 ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD;
507116e3 380
e1ee4b85
A
381 opt_spec = _os_subcommand_copy_option_spec(osc, osco,
382 OS_SUBCOMMAND_OPTION_SPEC_COMBINED);
383 fprintf(f, " %-24s", opt_spec);
507116e3 384
e1ee4b85
A
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, "");
507116e3
A
389 }
390
e1ee4b85
A
391 wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD,
392 CTL_OUTPUT_WIDTH, "%s",
393 osco->osco_argument_human);
507116e3
A
394}
395
e1ee4b85
A
396static void
397_os_subcommand_print_help_line(const os_subcommand_t *osc, FILE *f)
507116e3 398{
e1ee4b85 399 ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD;
507116e3 400
e1ee4b85 401 fprintf(f, " %-24s", osc->osc_name);
507116e3 402
e1ee4b85
A
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 }
507116e3 408
e1ee4b85
A
409 wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD,
410 CTL_OUTPUT_WIDTH, "%s",
411 osc->osc_desc);
507116e3
A
412}
413
414static void
e1ee4b85 415_os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f)
507116e3 416{
e1ee4b85
A
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;
507116e3 421
e1ee4b85
A
422 usage_line = _os_subcommand_copy_usage_line(osc);
423 wfprintf_np(f, 0, 0, CTL_OUTPUT_WIDTH, "usage: %s", usage_line);
507116e3 424
e1ee4b85
A
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 }
507116e3 475 }
507116e3
A
476 }
477}
478
e1ee4b85
A
479static const os_subcommand_t *
480_os_subcommand_find(const char *name)
507116e3 481{
e1ee4b85 482 const os_subcommand_t **oscip = NULL;
507116e3 483
e1ee4b85
A
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;
507116e3
A
494 }
495
e1ee4b85
A
496 if (strcmp(osci->osc_name, name) == 0) {
497 return osci;
498 }
507116e3
A
499 }
500
e1ee4b85 501 return NULL;
507116e3
A
502}
503
504#pragma mark API
505int
e1ee4b85
A
506os_subcommand_main(int argc, const char *argv[],
507 os_subcommand_main_flags_t flags)
507116e3 508{
e1ee4b85 509 int xit = -1;
507116e3 510 const char *cmdname = NULL;
e1ee4b85
A
511 const os_subcommand_t *osc = NULL;
512 const os_subcommand_t **oscip = NULL;
507116e3
A
513
514 if (argc < 2) {
e1ee4b85
A
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) {
507116e3
A
536 goto __out;
537 }
538
539 // Advance argument pointer and make the subcommand argv[0].
e1ee4b85
A
540 argc -= optind;
541 argv += optind;
507116e3
A
542 cmdname = argv[0];
543
e1ee4b85
A
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) {
507116e3 553 if (geteuid()) {
e1ee4b85
A
554 os_subcommand_fprintf(osc, stderr,
555 "subcommand requires root: %s",
556 cmdname);
557 xit = EX_NOPERM;
507116e3
A
558 goto __out;
559 }
560 }
561
e1ee4b85 562 if (osc->osc_flags & OS_SUBCOMMAND_FLAG_TTYONLY) {
507116e3 563 if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) {
e1ee4b85
A
564 os_subcommand_fprintf(osc, stderr,
565 "subcommand requires a tty: %s",
566 cmdname);
567 xit = EX_UNAVAILABLE;
507116e3
A
568 goto __out;
569 }
570 }
571
e1ee4b85 572 xit = osc->osc_invoke(osc, argc, argv);
507116e3 573 } else {
e1ee4b85
A
574 os_subcommand_fprintf(NULL, stderr, "unknonwn subcommand: %s", cmdname);
575 xit = EX_USAGE;
507116e3
A
576 }
577
578__out:
e1ee4b85
A
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
590void
591os_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
601void
602os_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);
507116e3 612}