/ libdarwin / ctl.c
ctl.c
  1  #include "internal.h"
  2  
  3  #pragma mark Definitions
  4  #define CTL_OUTPUT_WIDTH (80)
  5  #define CTL_OUTPUT_OPTARG_INDENT (32)
  6  #define CTL_OUTPUT_OPTARG_OVERFLOW (CTL_OUTPUT_OPTARG_INDENT - 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  static void _print_subcommand_list(
 35  		const os_subcommand_t *osc,
 36  		FILE *f);
 37  
 38  #pragma mark Module Globals
 39  static const os_subcommand_t __help_cmd;
 40  static const os_subcommand_t *_help_cmd = &__help_cmd;
 41  
 42  static const os_subcommand_t __main_cmd;
 43  static const os_subcommand_t *_main_cmd = &__main_cmd;
 44  static const os_subcommand_t *_internal_main_cmd = &__main_cmd;
 45  
 46  static struct ttysize __ttys = {
 47  	.ts_lines = 24,
 48  	.ts_cols = CTL_OUTPUT_WIDTH,
 49  };
 50  
 51  static const struct ttysize *_ttys = &__ttys;
 52  
 53  #pragma mark Module Private
 54  static void
 55  _init_column_count(void)
 56  {
 57  	const char *columns_env = NULL;
 58  	char *end = NULL;
 59  	struct ttysize ttys = {
 60  		.ts_lines = 24,
 61  		.ts_cols = CTL_OUTPUT_WIDTH,
 62  	};
 63  	int ret = -1;
 64  
 65  	columns_env = getenv("COLUMNS");
 66  	if (columns_env) {
 67  		unsigned short cols = -1;
 68  
 69  		cols = strtoul(columns_env, &end, 0);
 70  		if (end != columns_env && end[0] != 0) {
 71  			ttys.ts_lines = cols;
 72  		}
 73  	} else {
 74  		ret = ioctl(0, TIOCGSIZE, &ttys);
 75  		if (ret) {
 76  			ttys.ts_lines = 24;
 77  			ttys.ts_cols = CTL_OUTPUT_WIDTH;
 78  		}
 79  	}
 80  
 81  	__ttys = ttys;
 82  }
 83  
 84  static void
 85  _stoupper(char *str)
 86  {
 87  	size_t i = 0;
 88  	size_t len = strlen(str);
 89  
 90  	for (i = 0; i < len; i++) {
 91  		char *cp = &str[i];
 92  		*cp = ___toupper(*cp);
 93  	}
 94  }
 95  
 96  #pragma mark Main Subcommand
 97  static int _main_invoke(const os_subcommand_t *osc,
 98  	int argc,
 99  	const char *argv[]);
100  
101  static const os_subcommand_option_t _main_positional[] = {
102  	[0] = {
103  		.osco_version = OS_SUBCOMMAND_OPTION_VERSION,
104  		.osco_flags = 0,
105  		.osco_option = NULL,
106  		.osco_argument_usage = "subcommand",
107  		.osco_argument_human = "The subcommand to invoke",
108  	},
109  	OS_SUBCOMMAND_OPTION_TERMINATOR,
110  };
111  
112  static const os_subcommand_t __main_cmd = {
113  	.osc_version = OS_SUBCOMMAND_VERSION,
114  	.osc_flags = OS_SUBCOMMAND_FLAG_MAIN,
115  	.osc_name = "_main",
116  	.osc_desc = "main command",
117  	.osc_optstring = NULL,
118  	.osc_options = NULL,
119  	.osc_required = NULL,
120  	.osc_optional = NULL,
121  	.osc_positional = _main_positional,
122  	.osc_invoke = &_main_invoke,
123  };
124  
125  static int
126  _main_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
127  {
128  	return 0;
129  }
130  
131  #pragma mark Help Subcommand
132  static int _help_invoke(const os_subcommand_t *osc,
133  	int argc,
134  	const char *argv[]);
135  
136  static const os_subcommand_option_t _help_positional[] = {
137  	[0] = {
138  		.osco_version = OS_SUBCOMMAND_OPTION_VERSION,
139  		.osco_flags = 0,
140  		.osco_option = NULL,
141  		.osco_argument_usage = "SUBCOMMAND",
142  		.osco_argument_human = "The subcommand to query for help",
143  	},
144  	OS_SUBCOMMAND_OPTION_TERMINATOR,
145  };
146  
147  static const os_subcommand_t __help_cmd = {
148  	.osc_version = OS_SUBCOMMAND_VERSION,
149  	.osc_flags = 0,
150  	.osc_name = "help",
151  	.osc_desc = "prints helpful information",
152  	.osc_optstring = NULL,
153  	.osc_options = NULL,
154  	.osc_required = NULL,
155  	.osc_optional = NULL,
156  	.osc_positional = _help_positional,
157  	.osc_invoke = &_help_invoke,
158  };
159  
160  static int
161  _help_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
162  {
163  	int xit = -1;
164  	const char *cmdname = NULL;
165  	const os_subcommand_t *target = NULL;
166  	FILE *f = stdout;
167  
168  	if (argc > 1) {
169  		cmdname = argv[1];
170  	}
171  
172  	// Print usage information for the requested subcommand.
173  	target = _os_subcommand_find(cmdname);
174  	if (!target) {
175  		// If it's a bogus subcommand, just print top-level usage.
176  		fprintf(stderr, "unrecognized subcommand: %s\n", cmdname);
177  		target = _main_cmd;
178  		xit = EX_UNAVAILABLE;
179  	} else {
180  		xit = 0;
181  	}
182  
183  	if (xit) {
184  		f = stderr;
185  	}
186  
187  	_os_subcommand_print_usage(target, f);
188  
189  	if (target == _main_cmd) {
190  		_print_subcommand_list(_help_cmd, f);
191  	}
192  
193  	return xit;
194  }
195  
196  #pragma mark Utilities
197  static void
198  _print_header(FILE *f, const char *hdr, bool *already_done)
199  {
200  	if (already_done && *already_done) {
201  		return;
202  	}
203  
204  	crfprintf_np(f, "");
205  	crfprintf_np(f, "%s:", hdr);
206  	crfprintf_np(f, "");
207  
208  	if (already_done) {
209  		*already_done = true;
210  	}
211  }
212  
213  #pragma mark Module Routines
214  static char *
215  _os_subcommand_copy_option_spec_short(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), "-%c", opt->val);
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  
242  		if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
243  			_stoupper(argbuff);
244  		}
245  	} else {
246  		snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage);
247  		if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
248  			_stoupper(optbuff);
249  		}
250  	}
251  
252  	ret = asprintf(&final, "%s%s", optbuff, argbuff);
253  	if (ret < 0) {
254  		os_assert_zero(ret);
255  	}
256  
257  	return final;
258  }
259  
260  static char *
261  _os_subcommand_copy_option_spec_long(const os_subcommand_t *osc,
262  		const os_subcommand_option_t *osco)
263  {
264  	const struct option *opt = osco->osco_option;
265  	char optbuff[64] = "";
266  	char argbuff[64] = "";
267  	char *final = NULL;
268  	int ret = -1;
269  
270  	if (opt) {
271  		snprintf(optbuff, sizeof(optbuff), "--%s", opt->name);
272  
273  		switch (opt->has_arg) {
274  		case no_argument:
275  			break;
276  		case optional_argument:
277  			snprintf(argbuff, sizeof(argbuff), "[=%s]",
278  					osco->osco_argument_usage);
279  			break;
280  		case required_argument:
281  			snprintf(argbuff, sizeof(argbuff), "=<%s>",
282  					osco->osco_argument_usage);
283  			break;
284  		default:
285  			__builtin_unreachable();
286  		}
287  
288  		if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
289  			_stoupper(argbuff);
290  		}
291  	} else {
292  		snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage);
293  		if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
294  			_stoupper(optbuff);
295  		}
296  	}
297  
298  	ret = asprintf(&final, "%s%s", optbuff, argbuff);
299  	if (ret < 0) {
300  		os_assert_zero(ret);
301  	}
302  
303  	return final;
304  }
305  
306  static char *
307  _os_subcommand_copy_option_spec(const os_subcommand_t *osc,
308  		const os_subcommand_option_t *osco, os_subcommand_option_spec_fmt_t fmt)
309  {
310  	int ret = -1;
311  	char *spec = NULL;
312  	char *__os_free spec_old = NULL;
313  
314  	switch (fmt) {
315  	case OS_SUBCOMMAND_OPTION_SPEC_SHORT:
316  		_os_subcommand_copy_option_spec_short(osc, osco);
317  		break;
318  	case OS_SUBCOMMAND_OPTION_SPEC_LONG:
319  		_os_subcommand_copy_option_spec_long(osc, osco);
320  		break;
321  	case OS_SUBCOMMAND_OPTION_SPEC_COMBINED:
322  		spec = _os_subcommand_copy_option_spec_long(osc, osco);
323  		if (osco->osco_option) {
324  			spec_old = spec;
325  
326  			ret = asprintf(&spec, "-%c | %s", osco->osco_option->val, spec);
327  			if (ret < 0) {
328  				os_assert_zero(ret);
329  			}
330  		}
331  
332  		break;
333  	default:
334  		__builtin_unreachable();
335  	}
336  
337  	return spec;
338  }
339  
340  static char *
341  _os_subcommand_copy_usage_line(const os_subcommand_t *osc)
342  {
343  	char *usage_line = NULL;
344  	size_t i = 0;
345  	const os_subcommand_option_t *osco_i = NULL;
346  	const char *optional_spec = "";
347  	char subcmd_name[64];
348  	int ret = -1;
349  
350  	// The usage line does not enumerate all possible optional options, just the
351  	// required options. If there are optional options, then display that but
352  	// otherwise leave them to be described by more extensive usage information.
353  	if (osc->osc_optional) {
354  		optional_spec = " [options]";
355  	}
356  
357  	if (osc == _main_cmd) {
358  		strlcpy(subcmd_name, "", sizeof(subcmd_name));
359  	} else {
360  		snprintf(subcmd_name, sizeof(subcmd_name), " %s", osc->osc_name);
361  	}
362  
363  	ret = asprintf(&usage_line, "%s%s%s",
364  			getprogname(), subcmd_name, optional_spec);
365  	if (ret < 0) {
366  		os_assert_zero(ret);
367  	}
368  
369  	i = 0;
370  	OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, required, i) {
371  		char *__os_free usage_line_old = NULL;
372  		char *__os_free osco_spec = NULL;
373  
374  		usage_line_old = usage_line;
375  
376  		osco_spec = _os_subcommand_copy_option_spec_long(osc, osco_i);
377  		ret = asprintf(&usage_line, "%s %s", usage_line, osco_spec);
378  		if (ret < 0) {
379  			os_assert_zero(ret);
380  		}
381  	}
382  
383  	i = 0;
384  	OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
385  		char *__os_free usage_line_old = NULL;
386  		char *__os_free osco_spec = NULL;
387  		const char *braces[] = {
388  			"<",
389  			">",
390  		};
391  
392  		if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
393  			braces[0] = "[";
394  			braces[1] = "]";
395  		}
396  
397  		usage_line_old = usage_line;
398  
399  		osco_spec = _os_subcommand_copy_option_spec_long(osc, osco_i);
400  		ret = asprintf(&usage_line, "%s %s%s%s",
401  				usage_line, braces[0], osco_spec, braces[1]);
402  		if (ret < 0) {
403  			os_assert_zero(ret);
404  		}
405  	}
406  
407  	if (osc == _main_cmd && osc != _internal_main_cmd) {
408  		// Always include the positional subcommand when printing usage for the
409  		// main subcommand. We do not expect it to be specified in a user-
410  		// provided main subcommand.
411  		const os_subcommand_option_t *subopt = &_main_positional[0];
412  		char *__os_free usage_line_old = NULL;
413  		char *__os_free osco_spec = NULL;
414  
415  		usage_line_old = usage_line;
416  
417  		osco_spec = _os_subcommand_copy_option_spec_long(osc, subopt);
418  		ret = asprintf(&usage_line, "%s <%s>", usage_line, osco_spec);
419  		if (ret < 0) {
420  			os_assert_zero(ret);
421  		}
422  	}
423  
424  	return usage_line;
425  }
426  
427  static void
428  _os_subcommand_print_option_usage(const os_subcommand_t *osc,
429  		const os_subcommand_option_t *osco, FILE *f)
430  {
431  	char *__os_free opt_spec = NULL;
432  	ssize_t initpad = -CTL_OUTPUT_OPTARG_INDENT;
433  
434  	opt_spec = _os_subcommand_copy_option_spec(osc, osco,
435  			OS_SUBCOMMAND_OPTION_SPEC_COMBINED);
436  	fprintf(f, "    %-24s    ", opt_spec);
437  
438  	// If the usage specifier is long, start the description on the next line.
439  	if (strlen(opt_spec) >= CTL_OUTPUT_OPTARG_OVERFLOW) {
440  		initpad = CTL_OUTPUT_OPTARG_INDENT;
441  		crfprintf_np(f, "");
442  	}
443  
444  	wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_INDENT, _ttys->ts_cols, "%s",
445  			osco->osco_argument_human);
446  }
447  
448  static void
449  _os_subcommand_print_help_line(const os_subcommand_t *osc, FILE *f)
450  {
451  	ssize_t initpad = -CTL_OUTPUT_OPTARG_INDENT;
452  
453  	fprintf(f, "    %-24s    ", osc->osc_name);
454  
455  	// If the usage specifier is long, start the description on the next line.
456  	if (strlen(osc->osc_name) >= CTL_OUTPUT_OPTARG_OVERFLOW) {
457  		initpad = CTL_OUTPUT_OPTARG_INDENT;
458  		crfprintf_np(f, "");
459  	}
460  
461  	wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_INDENT, _ttys->ts_cols, "%s",
462  			osc->osc_desc);
463  }
464  
465  static void
466  _os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f)
467  {
468  	size_t i = 0;
469  	const os_subcommand_option_t *osco_i = NULL;
470  	char *__os_free usage_line = NULL;
471  	bool header_printed = false;
472  
473  	usage_line = _os_subcommand_copy_usage_line(osc);
474  
475  	wfprintf_np(f, 0, 4, _ttys->ts_cols, "USAGE:");
476  	crfprintf_np(f, "");
477  	wfprintf_np(f, 4, 4, _ttys->ts_cols, "%s", usage_line);
478  
479  	if (osc->osc_long_desc) {
480  		// The long description gets printed in its own paragraph.
481  		_print_header(f, "DESCRIPTION", NULL);
482  		wfprintf_np(f, 4, 4, _ttys->ts_cols, "%s", osc->osc_long_desc);
483  	} else if (osc->osc_desc) {
484  		// The short description gets printed on the same line.
485  		crfprintf_np(f, "");
486  		wfprintf_np(f, 0, 4, _ttys->ts_cols, "DESCRIPTION: %s",
487  				osc->osc_desc);
488  	}
489  
490  	if (osc->osc_required || osc->osc_positional || osc == _main_cmd) {
491  		i = 0;
492  		OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, required, i) {
493  			_print_header(f, "REQUIRED", &header_printed);
494  			_os_subcommand_print_option_usage(osc, osco_i, f);
495  		}
496  
497  		i = 0;
498  		OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
499  			_print_header(f, "REQUIRED", &header_printed);
500  
501  			if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
502  				continue;
503  			}
504  
505  			_os_subcommand_print_option_usage(osc, osco_i, f);
506  		}
507  
508  		if (osc == _main_cmd && osc != _internal_main_cmd) {
509  			// We do not expect the user's main command to specify that a
510  			// subcommand must follow, so always defer to ours.
511  			_print_header(f, "REQUIRED", &header_printed);
512  			_os_subcommand_print_option_usage(osc, &_main_positional[0], f);
513  		}
514  	}
515  
516  	header_printed = false;
517  
518  	if (osc->osc_optional || osc->osc_positional) {
519  		i = 0;
520  		OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, optional, i) {
521  			_print_header(f, "OPTIONAL", &header_printed);
522  			_os_subcommand_print_option_usage(osc, osco_i, f);
523  		}
524  
525  		i = 0;
526  		OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
527  			if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
528  				_print_header(f, "OPTIONAL", &header_printed);
529  				_os_subcommand_print_option_usage(osc, osco_i, f);
530  			}
531  		}
532  	}
533  }
534  
535  static const os_subcommand_t *
536  _os_subcommand_find(const char *name)
537  {
538  	const os_subcommand_t **oscip = NULL;
539  
540  	if (!name) {
541  		return _main_cmd;
542  	}
543  
544  	if (strcmp(_help_cmd->osc_name, name) == 0) {
545  		return &__help_cmd;
546  	}
547  
548  	LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) {
549  		const os_subcommand_t *osci = *oscip;
550  
551  		if (osci->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) {
552  			// The main subcommand cannot be invoked directly.
553  			continue;
554  		}
555  
556  		if (strcmp(osci->osc_name, name) == 0) {
557  			return osci;
558  		}
559  	}
560  
561  	return NULL;
562  }
563  
564  static int
565  _os_subcommand_be_helpful(const os_subcommand_t *osc,
566  		int argc, const char *argv[])
567  {
568  	int res = 0;
569  
570  	if (osc->osc_flags & OS_SUBCOMMAND_FLAG_HELPFUL) {
571  		if (argc == 1) {
572  			_os_subcommand_print_usage(osc, stdout);
573  			res = 1;
574  			goto __out;
575  		}
576  	}
577  
578  	if (osc->osc_flags & OS_SUBCOMMAND_FLAG_HELPFUL_FIRST_OPTION) {
579  		if (argc == 2 && (strcmp(argv[1], "help") == 0 ||
580  				strcmp(argv[1], "-h") == 0 ||
581  				strcmp(argv[1], "-help") == 0 ||
582  				strcmp(argv[1], "--help") == 0)) {
583  			_os_subcommand_print_usage(osc, stdout);
584  			res = 1;
585  			goto __out;
586  		}
587  	}
588  
589  __out:
590  	return res;
591  }
592  
593  static void
594  _print_subcommand_list(const os_subcommand_t *osc, FILE *f)
595  {
596  	const os_subcommand_t **oscip = NULL;
597  	bool header_printed = false;
598  
599  	LINKER_SET_FOREACH(oscip, const os_subcommand_t **,
600  			SUBCOMMAND_LINKER_SET) {
601  		const os_subcommand_t *osci = *oscip;
602  
603  		_print_header(f, "SUBCOMMANDS", &header_printed);
604  
605  		if ((osci->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) ||
606  				(osci->osc_flags & OS_SUBCOMMAND_FLAG_HIDDEN)) {
607  			continue;
608  		}
609  
610  		_os_subcommand_print_help_line(osci, f);
611  	}
612  
613  	// Print the help subcommand last.
614  	_os_subcommand_print_help_line(osc, f);
615  }
616  
617  #pragma mark API
618  int
619  os_subcommand_main(int argc, const char *argv[],
620  		os_subcommand_main_flags_t flags)
621  {
622  	int xit = -1;
623  	const char *cmdname = NULL;
624  	const os_subcommand_t *osc = NULL;
625  	const os_subcommand_t **oscip = NULL;
626  
627  	_init_column_count();
628  
629  	// Find the main subcommand if any exists. Otherwise we'll just use our pre-
630  	// canned main subcommand.
631  	LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) {
632  		osc = *oscip;
633  		if (osc->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) {
634  			_main_cmd = osc;
635  			break;
636  		}
637  	}
638  
639  	osc = NULL;
640  
641  	// See if we just need to print help for the main command.
642  	if (_os_subcommand_be_helpful(_main_cmd, argc, argv)) {
643  		_print_subcommand_list(_help_cmd, stdout);
644  		xit = 0;
645  		goto __out;
646  	}
647  
648  	// Invoke the main subcommand to snarf any global options. Our default
649  	// implementation does nothing and just returns 0.
650  	xit = _main_cmd->osc_invoke(_main_cmd, argc, argv);
651  	if (xit) {
652  		goto __out;
653  	}
654  
655  	// Advance argument pointer and make the subcommand argv[0].
656  	argc -= optind;
657  	argv += optind;
658  	cmdname = argv[0];
659  
660  	if (argc < 1) {
661  		os_subcommand_fprintf(NULL, stderr, "please provide a subcommand");
662  		xit = EX_USAGE;
663  		goto __out;
664  	}
665  
666  	osc = _os_subcommand_find(cmdname);
667  	if (osc) {
668  		if (osc->osc_flags & OS_SUBCOMMAND_FLAG_REQUIRE_ROOT) {
669  			if (geteuid()) {
670  				os_subcommand_fprintf(osc, stderr,
671  						"subcommand requires root: %s",
672  						cmdname);
673  				xit = EX_NOPERM;
674  				goto __out;
675  			}
676  		}
677  
678  		if (osc->osc_flags & OS_SUBCOMMAND_FLAG_TTYONLY) {
679  			if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) {
680  				os_subcommand_fprintf(osc, stderr,
681  						"subcommand requires a tty: %s",
682  						cmdname);
683  				xit = EX_UNAVAILABLE;
684  				goto __out;
685  			}
686  		}
687  
688  		if (_os_subcommand_be_helpful(osc, argc, argv)) {
689  			xit = 0;
690  			goto __out;
691  		}
692  
693  		xit = osc->osc_invoke(osc, argc, argv);
694  	} else {
695  		os_subcommand_fprintf(NULL, stderr, "unknown subcommand: %s", cmdname);
696  		xit = EX_USAGE;
697  	}
698  
699  __out:
700  	if (xit == EX_USAGE) {
701  		if (!osc) {
702  			// If we couldn't find the subcommand, then print the list of known
703  			// subcommands.
704  			_print_subcommand_list(_help_cmd, stderr);
705  		} else {
706  			_os_subcommand_print_usage(osc, stderr);
707  		}
708  	}
709  
710  	return xit;
711  }
712  
713  void
714  os_subcommand_fprintf(const os_subcommand_t *osc, FILE *f,
715  		const char *fmt, ...)
716  {
717  	va_list ap;
718  
719  	va_start(ap, fmt);
720  	vcrfprintf_np(f, fmt, ap);
721  	va_end(ap);
722  }
723  
724  void
725  os_subcommand_vfprintf(const os_subcommand_t *osc, FILE *f,
726  		const char *fmt, va_list ap)
727  {
728  	if (!osc || (osc->osc_flags & OS_SUBCOMMAND_FLAG_MAIN)) {
729  		fprintf(f, "%s: ", getprogname());
730  	} else {
731  		fprintf(f, "%s-%s: ", getprogname(), osc->osc_name);
732  	}
733  
734  	vcrfprintf_np(f, fmt, ap);
735  }