Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
/*
 * Copyright (c) 2018 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*!
 * @header
 * Interfaces to implement subcommand-style command line utilities (e.g.
 * launchctl(1)) and automatically generate usage output. The usage generated by
 * these interfaces assumes a long option convention (cf. getopt_long(3)) and is
 * loosely based on the docopt convention described at
 *
 *     http://docopt.org
 *
 * The user may define each subcommand taken by the utility as:
 *
 *     static const struct option _template_opts[] = {
 *         [0] = {
 *             .name = "bar",
 *             .has_arg = required_argument,
 *             .flag = NULL,
 *             .val = 'f',
 *         }, {
 *             .name = "baz",
 *             .has_arg = optional_argument,
 *             .flag = NULL,
 *             .val = 'b',
 *         }, {
 *             .name = NULL,
 *             .has_arg = 0,
 *             .flag = NULL,
 *             .val = 0,
 *         },
 *     };
 *
 *     static const os_subcommand_option_t _template_required[] = {
 *         [0] = {
 *             .osco_template = OS_SUBCOMMAND_OPTION_VERSION,
 *             .osco_flags = 0,
 *             .osco_option = &_template_opts[0],
 *             .osco_argument_usage = "thing-to-bar",
 *             .osco_argument_human = "The thing to bar. May be specified as a "
 *             "bar that has a baz. This baz should have a shnaz.",
 *         },
 *         OS_SUBCOMMAND_OPTION_TERMINATOR,
 *     };
 *
 *     static const os_subcommand_option_t _template_optional[] = {
 *         [0] = {
 *             .osco_template = OS_SUBCOMMAND_OPTION_VERSION,
 *             .osco_flags = 0,
 *             .osco_option = &_template_opts[1],
 *             .osco_argument_usage = "thing-to-baz",
 *             .osco_argument_human = "The baz of which to propagate a foo.",
 *         },
 *         OS_SUBCOMMAND_OPTION_TERMINATOR,
 *     };
 *
 *     static const os_subcommand_option_t _template_positional[] = {
 *         [0] = {
 *             .osco_template = OS_SUBCOMMAND_OPTION_VERSION,
 *             .osco_flags = 0,
 *             .osco_option = NULL,
 *             .osco_argument_usage = "positional-baz",
 *             .osco_argument_human = "A baz specified by position.",
 *         },
 *         OS_SUBCOMMAND_OPTION_TERMINATOR,
 *     };
 *
 *     static const os_subcommand_t _template_cmd = {
 *         .osc_template = OS_SUBCOMMAND_VERSION,
 *         .osc_flags = 0,
 *         .osc_name = "foo",
 *         .osc_desc = "foo a bar or maybe baz",
 *         .osc_optstring = "f:b:",
 *         .osc_options = _foo_opts,
 *         .osc_required = _foo_required,
 *         .osc_optional = _foo_optional,
 *         .osc_positional = _template_positional,
 *         .osc_invoke = &_foo_invoke,
 *     }
 *     OS_SUBCOMMAND_REGISTER(_foo_cmd);
 * };
 *
 * When {@link os_subcommand_main} is called, the tool's "help" subcommand will
 * display approximately the following:
 *
 * $ tool help
 * usage: tool <subcommand>
 *
 * subcommands:
 *     foo             foo a bar or maybe baz
 *     help            Prints helpful information
 *
 * $ tool help foo
 * usage: tool foo [options] --bar=<thing-to-bar> <positional-baz>
 *
 * required options:
 *     --bar=<thing-to-bar>    The thing to bar. May be specified as a bar that
 *                             has a baz. This baz should have a shnaz.
 *
 *     positional-baz          A baz specified by position.
 *
 * optional options:
 *     --baz[=thing-to-baz]    The baz of which to propagate a foo.
 */
#ifndef __DARWIN_CTL_H
#define __DARWIN_CTL_H

#include <os/base.h>
#include <os/api.h>
#include <os/linker_set.h>
#include <sys/cdefs.h>
#include <stdio.h>
#include <getopt.h>

#if DARWIN_TAPI
#include "tapi.h"
#endif

__BEGIN_DECLS;

/*!
 * @define OS_SUBCOMMAND_REGISTER
 * Registers a {@link os_subcommand_t} with the runtime. Subcommands may only be
 * declared as part of the main executable image -- subcommands declared in
 * dynamic libraries or bundles will not be recognized.
 */
#define OS_SUBCOMMAND_REGISTER(_subcommand) \
		LINKER_SET_ENTRY(__subcommands, _subcommand)

/*!
 * @typedef os_subcommand_t
 * The formal type name for the _os_subcommand structure.
 */
DARWIN_API_AVAILABLE_20181020
typedef struct _os_subcommand os_subcommand_t;

/*!
 * @const OS_SUBCOMMAND_OPTION_VERSION
 * The maximum version of the {@link os_subcommand_option_t} structure supported
 * by the implementation.
 */
#define OS_SUBCOMMAND_OPTION_VERSION ((os_struct_version_t)0)

/*!
 * @typedef os_subcommand_option_flags_t
 * Flags describing an option for a subcommand.
 *
 * @const OS_SUBCOMMAND_OPTION_FLAG_INIT
 * No flags set. This value is suitable for initialization purposes.
 *
 * @const OS_SUBCOMMAND_OPTION_FLAG_TERMINATOR
 * The option terminates an array of {@link os_subcommand_option_t} structures
 * and does not contain any useful information.
 *
 * @const OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS
 * The option is a positional option (that is, not identified by a long or short
 * flag) and is not required for the subcommand to execute successfully.
 *
 * @const OS_SUBCOMMAND_OPTION_FLAG_ENUM
 * The option has an explicitly-defined list of valid inputs that are enumerated
 * in the option's {@link osco_argument_usage} field. When printing usage
 * information for this option, the implementation will not transform the string
 * in this field in any way.
 *
 * For example, an option named "--stream" might have three valid inputs:
 * "stdin", "stdout", and "stderr", and the usage string may be specified as
 *
 *     "stdin|stdout|stderr"
 *
 * Without this flag, the implementation would print this string as a parameter
 * name, i.e. in all caps:
 *
 *     "<STDIN|STDOUT|STDERR>"
 *
 * With this flag, the string will be printed as it is given.
 */
DARWIN_API_AVAILABLE_20181020
OS_CLOSED_ENUM(os_subcommand_option_flags, uint64_t,
	OS_SUBCOMMAND_OPTION_FLAG_INIT = 0,
	OS_SUBCOMMAND_OPTION_FLAG_TERMINATOR = (1 << 0),
	OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS = (1 << 1),
	OS_SUBCOMMAND_OPTION_FLAG_ENUM = (1 << 2),
);

/*!
 * @typedef os_subcommand_option_t
 * A structure describing human-readable information about a particular option
 * taken by a subcommand. This structure is to be returned when the
 * implementation invokes a {@link os_subcommand_option_info_t} function to
 * query about a command's options individually. This is done when the
 * implementation is synthesizing a usage string.
 *
 * @field osco_version
 * The version of the structure. Initialize to
 * {@link OS_SUBCOMMAND_OPTION_VERSION}.
 *
 * @field osco_flags
 * A set of flags describing information about the option.
 *
 * @field osco_option
 * A pointer to the option structure ingested by getopt_long(3) which
 * corresponds to this option.
 *
 * @field osco_argument_usage
 * The short-form name of the argument given to the option, appropriate for
 * display in a usage specifier. For example, if the subcommand takes a "--file"
 * option with a required argument, this might be the string "FILE-PATH", and
 * the resulting usage specifier would be
 *
 *     --file=<FILE-PATH>
 *
 * @field osco_argument_human
 * The long-form description of the argument given to the option. Extending the
 * above example, this might be the string "The path to a file to take as input.
 * This path must be absolute; relative paths are not supported." and the
 * resulting usage specifier would be
 *
 *     --file=<FILE-PATH> The path to a file to take as input. This path must be
 *                        absolute; relative paths are not supported.
 */
DARWIN_API_AVAILABLE_20191015
typedef struct _os_subcommand_option {
	const os_struct_version_t osco_version;
	os_subcommand_option_flags_t osco_flags;
	const struct option *osco_option;
	const char *osco_argument_usage;
	const char *osco_argument_human;
} os_subcommand_option_t;

/*!
 * @const OS_SUBCOMMAND_OPTION_TERMINATOR
 * A convenience terminator for an array of {@link os_subcommand_option_t}
 * structures.
 */
#define OS_SUBCOMMAND_OPTION_TERMINATOR (os_subcommand_option_t){ \
	.osco_version = OS_SUBCOMMAND_OPTION_VERSION, \
	.osco_flags = OS_SUBCOMMAND_OPTION_FLAG_TERMINATOR, \
	.osco_option = NULL, \
	.osco_argument_usage = NULL, \
	.osco_argument_human = NULL, \
}

/*!
* @const OS_SUBCOMMAND_GETOPT_TERMINATOR
* A convenience terminator for an array of getopt(3) option structures.
*/
#define OS_SUBCOMMAND_GETOPT_TERMINATOR (struct option){ \
	.name = NULL, \
	.has_arg = 0, \
	.flag = NULL, \
	.val = 0, \
}

/*!
 * @const OS_SUBCOMMAND_VERSION
 * The maximum version of the {@link os_subcommand_t} structure supported by the
 * implementation.
 */
#define OS_SUBCOMMAND_VERSION ((os_struct_version_t)0)

/*!
 * @enum os_subcommand_flags_t
 * A type describing flags associated with a subcommand for a command line
 * utility.
 *
 * @const OS_SUBCOMMAND_FLAG_INIT
 * No flags set. This value is suitable for initialization purposes.
 *
 * @const OS_SUBCOMMAND_FLAG_REQUIRE_ROOT
 * This subcommand requires the user to be root. If the user is not root, the
 * {@link os_subcommand_dispatch} routine will return {@EX_PERM}.
 *
 * @const OS_SUBCOMMAND_FLAG_TTYONLY
 * This subcommand may only be invoked via a terminal interface, i.e. it should
 * not be invoked by scripts. Use this option to emphasize that a command's
 * output is human-readably only and should not be parsed.
 *
 * @const OS_SUBCOMMAND_FLAG_HIDDEN
 * This subcommand should not be displayed in the list of subcommands.
 *
 * @const OS_SUBCOMMAND_FLAG_MAIN
 * This subcommand is the "main" subcommand. Designating a main subcommand
 * allows the program to specify and parse global options using an
 * {@link os_subcommand_t} object and {@link os_subcommand_main}.
 *
 * This flag implies the behavior of {@link OS_SUBCOMMAND_FLAG_HIDDEN}.
 *
 * If the program specifies a main subcommand, that subcommand's invocation
 * routine is unconditionally called before calling the subcommand invocation,
 * if the user provided a subcommand. The invocation function for the main
 * subcommand should not exit on success and should instead return 0.
 *
 * If multiple subcommands in the same program set
 * {@link OS_SUBCOMMAND_FLAG_MAIN}, the implementation's behavior is undefined.
 *
 * @const OS_SUBCOMMAND_FLAG_HELPFUL
 * When invoked with no arguments, this subcommand will print usage information
 * to stdout and exit with status zero.
 *
 * @const OS_SUBCOMMAND_FLAG_HELPFUL_FIRST_OPTION
 * Allow the implementation to detect whether the user is attempting to print
 * usage information for the given subcommand. If the implementation concludes
 * that the user is seeking help, it will print the subcommand's usage
 * information to stdout and exit with status 0.
 *
 * The implementation will conclude that the user is seeking help if
 *
 *     - only one argument is provided to the subcommand, and
 *
 * any of the following
 *
 *     - the argument is "-h"
 *     - the argument is "-help"
 *     - the argument is "--help"
 *     - the argument is "help"
 */
DARWIN_API_AVAILABLE_20181020
OS_CLOSED_OPTIONS(os_subcommand_flags, uint64_t,
	OS_SUBCOMMAND_FLAG_INIT,
	OS_SUBCOMMAND_FLAG_REQUIRE_ROOT = (1 << 0),
	OS_SUBCOMMAND_FLAG_TTYONLY = (1 << 1),
	OS_SUBCOMMAND_FLAG_HIDDEN = (1 << 2),
	OS_SUBCOMMAND_FLAG_MAIN = (1 << 3),
	OS_SUBCOMMAND_FLAG_HELPFUL = (1 << 4),
	OS_SUBCOMMAND_FLAG_HELPFUL_FIRST_OPTION = (1 << 5),
);

/*!
 * @typedef os_subcommand_invoke_t
 * An type describing the invocation function for a subcommand.
 *
 * @param osc
 * The descriptor for the command being invoked.
 *
 * @param argc
 * The argument vector count.
 *
 * @param argv
 * The argument vector. The first element of this array is the name of the
 * subcommand.
 *
 * @result
 * An exit code, preferably from sysexits(3). Do not return a POSIX error code
 * directly from this routine.
 *
 * @discussion
 * You may exit directly on success or failure from this routine if desired. If
 * the routine is the invocation for a main subcommand, then on success, the
 * routine should return zero to the caller rather than exiting so that the
 * implementation may continue parsing the command line arguments.
 */
DARWIN_API_AVAILABLE_20181020
typedef int (*os_subcommand_invoke_t)(
	const os_subcommand_t *osc,
	int argc,
	const char *argv[]
);

/*!
 * @struct os_subcommand_t
 * A type describing a subcommand for a command line tool.
 *
 * @field osc_version
 * The version of the structure. Initialize to {@link OS_SUBCOMMAND_VERSION}.
 *
 * @field osc_flags
 * The flags for the subcommand.
 *
 * @field osc_name
 * The name of the subcommand. The second argument of user input will be matched
 * against this name.
 *
 * @field osc_desc
 * A brief description of the subcommand. This description will be displayed
 * next to the subcommand when the user lists all subcommands.
 *
 * @field osc_long_desc
 * A long description of the subcommand. This description will be displayed
 * when the user invokes help on the subcommand. If it's NULL the brief
 * description from osc_desc will be displayed.
 *
 * @field osc_optstring
 * The option string associated with the subcommand. The implementation does not
 * recognize this string directly; the intent of storing it here is to provide a
 * convenient place to access the string for the implementation function.
 * Combined with the {@link osc_options} field, this enables the following
 * pattern:
 *
 *     int ch = 0;
 *     while ((ch = getopt_long(argc, argv, cmd->osc_optstring,
 *             cmd->osc_options, NULL)) != -1) {
 *         switch (ch) {
 *             // process option
 *         }
 *     }
 *
 * This pattern keeps the option string and option structs co-located in code.
 *
 * @field osc_options
 * A pointer to an array of option structures describing the long options
 * recognized by the subcommand. This array must be terminated by a NULL entry
 * as expected by getopt_long(3).
 *
 * @field osc_required
 * A pointer to an array of subcommand option descriptors. The options described
 * in this array are required for the subcommand to execute successfully. This
 * array should be terminated with {@link OS_SUBCOMMAND_OPTION_TERMINATOR}.
 *
 * This array is consulted when printing usage information.
 *
 * @field osc_optional
 * A pointer to an array of subcommand option descriptors. The options described
 * in this array are parsed by the subcommand but not required for it to execute
 * successfully. This array should be terminated with
 * {@link OS_SUBCOMMAND_OPTION_TERMINATOR}.
 *
 * This array is consulted when printing usage information.
 *
 * @field osc_positional
 * A pointer to an array of subcommand option descriptors. The options described
 * in this array are expected to follow the required and optional arguments in
 * the command line invocation, in the order given in this array. This array
 * should be terminated with {@link OS_SUBCOMMAND_OPTION_TERMINATOR}.
 *
 * These options are expected to have their {@link osco_option} fields set to
 * NULL.
 *
 * This array is consulted when printing usage information.
 *
 * @field osc_info
 * A pointer to a function which returns information about the subcommand's
 * individual options.
 *
 * @field osc_invoke
 * The implementation for the subcommand.
 */
DARWIN_API_AVAILABLE_20200401
struct _os_subcommand {
	const os_struct_version_t osc_version;
	const os_subcommand_flags_t osc_flags;
	const char *const osc_name;
	const char *const osc_desc;
	const char *osc_optstring;
	const struct option *osc_options;
	const os_subcommand_option_t *osc_required;
	const os_subcommand_option_t *osc_optional;
	const os_subcommand_option_t *osc_positional;
	const os_subcommand_invoke_t osc_invoke;
	const char *const osc_long_desc;
};

/*!
 * @typedef os_subcommand_main_flags_t
 * Flags modifying the behavior of {@link os_subcommand_main}.
 *
 * @const OS_SUBCOMMAND_MAIN_FLAG_INIT
 * No flags set. This value is suitable for initialization purposes.
 */
OS_CLOSED_OPTIONS(os_subcommand_main_flags, uint64_t,
	OS_SUBCOMMAND_MAIN_FLAG_INIT,
);

/*!
 * @function os_subcommand_main
 * Dispatches the subcommand appropriate for the given arguments. All
 * subcommands will be implicitly discovered by virtue of their delcarations
 * with the OS_SUBCOMMAND_REGISTER attribute.
 *
 * @param argc
 * The argument count supplied to main().
 *
 * @param argv
 * The argument vector supplied to main().
 *
 * @param flags
 * Flags modifying the behavior of the implementation.
 *
 * @result
 * The exit status from the subcommand's invocation function or an exit status
 * from the implementation indicating that the subcommand could not be
 * dispatched. Exit codes that can be returned by the implementation are:
 *
 *     [EX_USAGE]       The subcommand was invoked with improper syntax. Usage
 *                      has been printed to stderr.
 *     [EX_USAGE]       The subcommand specified is not recognized.
 *     [EX_PERM]        The command required root privileges, and the caller is
 *                      not running as root.
 *     [EX_UNAVAILABLE] The command specified that it may only run within
 *                      the context of a tty(3), and either stdin or stdout are
 *                      not a tty(3).
 *
 * @discussion
 * In general, the code returned by this routine should immediately be passed to
 * exit(3). The reason this routine does not implicitly exit is to allow for the
 * caller to process multiple subcommand invocations as a batch.
 *
 * The caller should not print anything after this routine has returned -- the
 * expectation is that all relevant information has already been conveyed to the
 * user either by the implementation or from one of the subcommand invocation
 * routines.
 *
 * This routine implicitly implements a "help" subcommand.
 */
DARWIN_API_AVAILABLE_20191015
OS_EXPORT OS_WARN_RESULT OS_NONNULL2
int
os_subcommand_main(int argc, const char *argv[],
		os_subcommand_main_flags_t flags);

/*!
 * @function os_subcommand_fprintf
 * Prints a message in the context of a subcommand to the given output stream.
 *
 * @param osc
 * The subcommand which represents the context of the message.
 *
 * @param f
 * The stream to which the message shall be printed. If NULL, will be printed to
 * stderr(4).
 *
 * @param fmt
 * A printf(3)-style format string.
 *
 * @param ...
 * The arguments corresponding to {@link fmt}.
 *
 * @discussion
 * This routine provides a uniform method for printing messages in the context
 * of a subcommand. It will ensure that the message is prefixed appropriately
 * (e.g. with the program name and/or subcommand name).
 *
 * This routine should be used for printing messages intended for humans to
 * read; the implementation makes no guarantees about the output format's
 * stability. If any output is intended to be machine-parseable, it should be
 * written with fprintf(3) et al.
 */
DARWIN_API_AVAILABLE_20191015
OS_EXPORT OS_NONNULL3 OS_FORMAT_PRINTF(3, 4)
void
os_subcommand_fprintf(const os_subcommand_t *osc, FILE *f,
		const char *fmt, ...);

/*!
 * @function os_subcommand_vfprintf
 * Prints a message in the context of a subcommand to the given output stream.
 *
 * @param osc
 * The subcommand which represents the context of the message.
 *
 * @param f
 * The stream to which the message shall be printed. If NULL, will be printed to
 * stderr(4).
 *
 * @param fmt
 * A printf(3)-style format string.
 *
 * @param ap
 * The argument list corresponding to {@link fmt}.
 *
 * @discussion
 * See discussion for {@link os_subcommand_fprintf}.
 */
DARWIN_API_AVAILABLE_20191015
OS_EXPORT OS_NONNULL3
void
os_subcommand_vfprintf(const os_subcommand_t *osc, FILE *f,
		const char *fmt, va_list ap);

__END_DECLS;

#endif // __DARWIN_CTL_H