/* * 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