From e1ee4b85c2dcd2825b98e4d31d829e56ae31f66a Mon Sep 17 00:00:00 2001 From: Apple Date: Thu, 2 Apr 2020 17:10:42 +0000 Subject: [PATCH] Libc-1353.60.8.tar.gz --- Libc.xcodeproj/project.pbxproj | 20 +- include/struct.h | 20 +- libdarwin/ctl.c | 673 ++++++++++++++++++++++++--------- libdarwin/dirstat.c | 4 +- libdarwin/h/bsd.h | 4 + libdarwin/h/cleanup.h | 98 ++++- libdarwin/h/ctl.h | 429 +++++++++++++-------- libdarwin/h/err.h | 4 + libdarwin/h/errno.h | 4 + libdarwin/h/mach_exception.h | 4 + libdarwin/h/mach_utils.h | 40 ++ libdarwin/h/stdio.h | 123 +++++- libdarwin/h/stdlib.h | 166 ++++++++ libdarwin/h/string.h | 19 + libdarwin/internal.h | 18 - libdarwin/stdio.c | 57 +++ libdarwin/stdlib.c | 23 ++ libdarwin/string.c | 21 +- libdarwin/tapi.h | 59 +++ os/api.h | 10 +- os/assumes.h | 22 ++ tests/Makefile | 4 +- tests/abort_tests.c | 119 ++++-- tests/darwin_bsd.c | 4 + tests/locale.c | 3 + 25 files changed, 1529 insertions(+), 419 deletions(-) create mode 100644 libdarwin/tapi.h diff --git a/Libc.xcodeproj/project.pbxproj b/Libc.xcodeproj/project.pbxproj index 8576016..2423e7a 100644 --- a/Libc.xcodeproj/project.pbxproj +++ b/Libc.xcodeproj/project.pbxproj @@ -135,6 +135,7 @@ 4B2C64A915519BC800342BFA /* assumes.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B2C64A215519BAF00342BFA /* assumes.c */; }; 4B2C64AA15519BCB00342BFA /* assumes.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B2C64A215519BAF00342BFA /* assumes.c */; }; 4B2C64BA1551B03700342BFA /* assumes.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B2C64A215519BAF00342BFA /* assumes.c */; }; + 4B2D551E231706F9003DAFCE /* tapi.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B2D551D2317040F003DAFCE /* tapi.h */; }; 4B450FFB211A56DD0029AF5D /* ctl.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B450FFA211A56DC0029AF5D /* ctl.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4B450FFD211A56EC0029AF5D /* ctl.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B450FFC211A56EC0029AF5D /* ctl.c */; }; 4B4E643F2069E94A00C4D8D5 /* internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B2C50E41F8453FA005DA2B6 /* internal.h */; }; @@ -143,7 +144,6 @@ 4B6D181D206DEFBD00C00E37 /* mach_exception.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B6D181C206DEFBD00C00E37 /* mach_exception.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4B6D181F206DF1E200C00E37 /* exception.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B6D181E206DF1E200C00E37 /* exception.c */; }; 4B782979208926A80070E1FF /* api.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B782978208926A70070E1FF /* api.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 4B8A6F3221C99ACC00D00D67 /* linker_set.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B8A6F3121C99A0E00D00D67 /* linker_set.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4BA6E55F202AB35900F38D3A /* string.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BA6E55E202AB35900F38D3A /* string.c */; }; 4BA6E562202AC06300F38D3A /* err.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BA6E561202AC06300F38D3A /* err.c */; }; 4BA6E566202AC94800F38D3A /* stdlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BA6E565202AC94800F38D3A /* stdlib.c */; }; @@ -5741,6 +5741,7 @@ 4B2C50E41F8453FA005DA2B6 /* internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = internal.h; sourceTree = ""; }; 4B2C64A215519BAF00342BFA /* assumes.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = assumes.c; path = os/assumes.c; sourceTree = ""; }; 4B2C64AB15519C3400342BFA /* assumes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = assumes.h; path = os/assumes.h; sourceTree = ""; }; + 4B2D551D2317040F003DAFCE /* tapi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tapi.h; sourceTree = ""; }; 4B450FFA211A56DC0029AF5D /* ctl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ctl.h; sourceTree = ""; }; 4B450FFC211A56EC0029AF5D /* ctl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ctl.c; sourceTree = ""; }; 4B69E81220800BE9008D13D2 /* libdarwin_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libdarwin_init.h; sourceTree = ""; }; @@ -7136,6 +7137,7 @@ 4B2C50DE1F8453A6005DA2B6 /* h */, 9280EA171E59BC8A007A6F58 /* AppleInternalVariant.plist */, 4B2C50E41F8453FA005DA2B6 /* internal.h */, + 4B2D551D2317040F003DAFCE /* tapi.h */, 4BA6E56B202AD02900F38D3A /* bsd.c */, 4B450FFC211A56EC0029AF5D /* ctl.c */, 4B6D181E206DF1E200C00E37 /* exception.c */, @@ -9025,8 +9027,8 @@ 4B0899BC2046258F001360A4 /* cleanup.h in Headers */, 4BCC350F20659AD500A4CBAA /* linker_set.h in Headers */, 4B20DB53202B81A4005C2327 /* stdlib.h in Headers */, - 4B8A6F3221C99ACC00D00D67 /* linker_set.h in Headers */, 4B09323421C9C08F006063D6 /* mach_utils.h in Headers */, + 4B2D551E231706F9003DAFCE /* tapi.h in Headers */, 4B69E81320800D47008D13D2 /* libdarwin_init.h in Headers */, 4B20DB54202B81A4005C2327 /* string.h in Headers */, 4B075C8E208BE9F200FD4F23 /* variant_private.h in Headers */, @@ -9392,7 +9394,7 @@ C9B53597138D9A690028D27C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1140; TargetAttributes = { 925E7FE619E8945900AC7889 = { CreatedOnToolsVersion = 6.1; @@ -9499,7 +9501,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/bin/bash -e -x"; - shellScript = "mkdir -p ${TAPI_PUBLIC_HEADER_PATH}"; + shellScript = "mkdir -p ${TAPI_PUBLIC_HEADER_PATH}\n"; showEnvVarsInLog = 0; }; 3FF283291A4764370098AD2C /* Simulator Build Compat Symlink */ = { @@ -15790,9 +15792,10 @@ "$(DERIVED_FILES_DIR)/dtrace", "$(SRCROOT_SEARCH_PATHS)", "$(SYSTEM_FRAMEWORK_HEADERS)", + "$(PROJECT_DIR)/libdarwin", + "$(PROJECT_DIR)/libdarwin/h", "$(SDKROOT)/usr/local/include", "$(inherited)", - "$(PROJECT_DIR)/libdarwin/h", ); LIBRARY_SEARCH_PATHS = /usr/lib/system; LIBSYSTEM_DARWIN_LDFLAGS = "-all_load -nostdlib -L/usr/lib/system -umbrella System $(LIBCOMPILER_RT_LDFLAGS) $(LIBDYLD_LDFLAGS) $(LIBSYSCALL_LDFLAGS) $(LIBM_LDFLAGS) $(LIBMALLOC_LDFLAGS) $(LIBPLATFORM_LDFLAGS) $(LIBPTHREAD_LDFLAGS) $(LIBPLATFORM_LDFLAGS) $(LIBC_LDFLAGS) $(LIBDISPATCH_LDFLAGS) $(LIBXPC_LDFLAGS) -lmacho -ldyld -Wl,-upward-lsystem_trace"; @@ -15802,6 +15805,7 @@ PRODUCT_NAME = darwin; SKIP_INSTALL = NO; SUPPORTS_TEXT_BASED_API = YES; + TAPI_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/libdarwin"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -15837,18 +15841,20 @@ "$(DERIVED_FILES_DIR)/dtrace", "$(SRCROOT_SEARCH_PATHS)", "$(SYSTEM_FRAMEWORK_HEADERS)", + "$(PROJECT_DIR)/libdarwin", + "$(PROJECT_DIR)/libdarwin/h", "$(SDKROOT)/usr/local/include", "$(inherited)", - "$(PROJECT_DIR)/libdarwin/h", ); LIBRARY_SEARCH_PATHS = /usr/lib/system; LIBSYSTEM_DARWIN_LDFLAGS = "-all_load -nostdlib -L/usr/lib/system -umbrella System $(LIBCOMPILER_RT_LDFLAGS) $(LIBDYLD_LDFLAGS) $(LIBSYSCALL_LDFLAGS) $(LIBM_LDFLAGS) $(LIBMALLOC_LDFLAGS) $(LIBPLATFORM_LDFLAGS) $(LIBPTHREAD_LDFLAGS) $(LIBPLATFORM_LDFLAGS) $(LIBC_LDFLAGS) $(LIBDISPATCH_LDFLAGS) $(LIBXPC_LDFLAGS) -lmacho -ldyld -Wl,-upward-lsystem_trace"; OTHER_LDFLAGS = "$(LIBSYSTEM_DARWIN_LDFLAGS)"; - OTHER_TAPI_FLAGS = "$(inherited) -extra-public-header $(SRCROOT)/libdarwin/h/dirstat.h -extra-public-header $(SRCROOT)/libdarwin/internal.h -DDARWIN_TAPI=1 -extra-public-header $(SRCROOT)/os/variant_private.h"; + OTHER_TAPI_FLAGS = "$(inherited) -extra-public-header $(SRCROOT)/libdarwin/h/dirstat.h -extra-public-header $(SRCROOT)/libdarwin/tapi.h -DDARWIN_TAPI=1 -extra-public-header $(SRCROOT)/os/variant_private.h -extra-public-header $(SRCROOT)/os/api.h"; PRIVATE_HEADERS_FOLDER_PATH = "$(DARWIN_PRIVATE_HEADERS_FOLDER_PATH)"; PRODUCT_NAME = darwin; SKIP_INSTALL = NO; SUPPORTS_TEXT_BASED_API = YES; + TAPI_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/libdarwin"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/include/struct.h b/include/struct.h index 4dff678..c26849e 100644 --- a/include/struct.h +++ b/include/struct.h @@ -48,6 +48,13 @@ #define strbase(name, addr, field) \ ((struct name *)((char *)(addr) - fldoff(name, field))) +/* + * countof() cannot be safely used in a _Static_assert statement, so we provide + * an unsafe variant that does not verify the input array is statically-defined. + */ +#define countof_unsafe(arr) \ + (sizeof(arr) / sizeof(arr[0])) + /* Number of elements in a statically-defined array */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && __GNUC__ #define countof(arr) ({ \ @@ -58,18 +65,15 @@ }) #else #define countof(arr) \ - (sizeof(arr) / sizeof(arr[0])) + countof_unsafe(arr) #endif -/* - * countof() cannot be safely used in a _Static_assert statement, so we provide - * an unsafe variant that does not verify the input array is statically-defined. - */ -#define countof_unsafe(arr) \ - (sizeof(arr) / sizeof(arr[0])) - /* Length of a statically-defined string (does not include null terminator) */ #define lenof(str) \ (sizeof(str) - 1) +/* Last index of a statically-defined array */ +#define lastof(arr) \ + (countof(arr) - 1) + #endif /* !_STRUCT_H_ */ diff --git a/libdarwin/ctl.c b/libdarwin/ctl.c index 8015ba3..adcef27 100755 --- a/libdarwin/ctl.c +++ b/libdarwin/ctl.c @@ -6,37 +6,240 @@ #define CTL_OUTPUT_LIST_PAD (4) #define SUBCOMMAND_LINKER_SET "__subcommands" +#define OS_SUBCOMMAND_OPTIONS_FOREACH(_osco_i, _osc, _which, _i) \ + while (((_osco_i) = &osc->osc_ ## _which[(_i)]) && \ + ((_i) += 1, 1) && \ + !((_osco_i)->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_TERMINATOR)) + +#pragma mark Types +OS_ENUM(os_subcommand_option_spec_fmt, uint64_t, + OS_SUBCOMMAND_OPTION_SPEC_SHORT, + OS_SUBCOMMAND_OPTION_SPEC_LONG, + OS_SUBCOMMAND_OPTION_SPEC_COMBINED, +); + +#pragma mark Forward Declarations +static void _print_header( + FILE *f, + const char *hdr, + bool *already_done); +static const os_subcommand_t *_os_subcommand_find( + const char *name); +static void _os_subcommand_print_usage( + const os_subcommand_t *osc, + FILE *f); +static void _os_subcommand_print_help_line( + const os_subcommand_t *osc, + FILE *f); + #pragma mark Module Globals -static const os_subcommand_t _help_cmd; +static const os_subcommand_t __help_cmd; +static const os_subcommand_t *_help_cmd = &__help_cmd; + +static const os_subcommand_t __main_cmd; +static const os_subcommand_t *_main_cmd = &__main_cmd; +static const os_subcommand_t *_internal_main_cmd = &__main_cmd; + +#pragma mark Main Subcommand +static int _main_invoke(const os_subcommand_t *osc, + int argc, + const char *argv[]); + +static const os_subcommand_option_t _main_positional[] = { + [0] = { + .osco_version = OS_SUBCOMMAND_OPTION_VERSION, + .osco_flags = 0, + .osco_option = NULL, + .osco_argument_usage = "subcommand", + .osco_argument_human = "The subcommand to invoke", + }, + OS_SUBCOMMAND_OPTION_TERMINATOR, +}; + +static const os_subcommand_t __main_cmd = { + .osc_version = OS_SUBCOMMAND_VERSION, + .osc_flags = OS_SUBCOMMAND_FLAG_MAIN, + .osc_name = "_main", + .osc_desc = "main command", + .osc_optstring = NULL, + .osc_options = NULL, + .osc_required = NULL, + .osc_optional = NULL, + .osc_positional = _main_positional, + .osc_invoke = &_main_invoke, +}; + +static int +_main_invoke(const os_subcommand_t *osc, int argc, const char *argv[]) +{ + return 0; +} + +#pragma mark Help Subcommand +static int _help_invoke(const os_subcommand_t *osc, + int argc, + const char *argv[]); + +static const os_subcommand_option_t _help_positional[] = { + [0] = { + .osco_version = OS_SUBCOMMAND_OPTION_VERSION, + .osco_flags = 0, + .osco_option = NULL, + .osco_argument_usage = "subcommand", + .osco_argument_human = "The subcommand to query for help", + }, + OS_SUBCOMMAND_OPTION_TERMINATOR, +}; + +static const os_subcommand_t __help_cmd = { + .osc_version = OS_SUBCOMMAND_VERSION, + .osc_flags = 0, + .osc_name = "help", + .osc_desc = "prints helpful information", + .osc_optstring = NULL, + .osc_options = NULL, + .osc_required = NULL, + .osc_optional = NULL, + .osc_positional = _help_positional, + .osc_invoke = &_help_invoke, +}; + +static int +_help_invoke(const os_subcommand_t *osc, int argc, const char *argv[]) +{ + int xit = -1; + const char *cmdname = NULL; + const os_subcommand_t *target = NULL; + FILE *f = stdout; + + if (argc > 1) { + cmdname = argv[1]; + } + + if (cmdname) { + // Print usage information for the requested subcommand. + target = _os_subcommand_find(argv[1]); + if (!target) { + os_subcommand_fprintf(osc, stderr, "unrecognized subcommand: %s", + argv[1]); + xit = EX_USAGE; + } else { + xit = 0; + } + } else { + // Print general, top-level usage followed by a list of subcommands. + target = _main_cmd; + xit = 0; + } + + if (xit) { + f = stderr; + } + + _os_subcommand_print_usage(target, f); + + if (target == _main_cmd) { + const os_subcommand_t **oscip = NULL; + const os_subcommand_t *osci = NULL; + bool header_printed = false; + + LINKER_SET_FOREACH(oscip, const os_subcommand_t **, + SUBCOMMAND_LINKER_SET) { + osci = *oscip; + + _print_header(f, "subcommands", &header_printed); + _os_subcommand_print_help_line(osci, f); + } + + // Print the help subcommand last. + _os_subcommand_print_help_line(osc, f); + } + + return xit; +} + +#pragma mark Utilities +static void +_print_header(FILE *f, const char *hdr, bool *already_done) +{ + if (*already_done) { + return; + } + + crfprintf_np(f, ""); + crfprintf_np(f, "%s:", hdr); + *already_done = true; +} #pragma mark Module Routines static char * -_os_subcommand_copy_optarg_usage(const os_subcommand_t *osc, - const struct option *opt, os_subcommand_optarg_format_t format, - os_subcommand_option_t *scopt) +_os_subcommand_copy_option_spec_short(const os_subcommand_t *osc, + const os_subcommand_option_t *osco) { + const struct option *opt = osco->osco_option; char optbuff[64] = ""; char argbuff[64] = ""; char *final = NULL; int ret = -1; - snprintf(optbuff, sizeof(optbuff), "--%s", opt->name); + if (opt) { + snprintf(optbuff, sizeof(optbuff), "-%c", opt->val); + + switch (opt->has_arg) { + case no_argument: + break; + case optional_argument: + snprintf(argbuff, sizeof(argbuff), "[%s]", + osco->osco_argument_usage); + break; + case required_argument: + snprintf(argbuff, sizeof(argbuff), "<%s>", + osco->osco_argument_usage); + break; + default: + __builtin_unreachable(); + } + } else { + snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage); + } - if (osc->osc_info) { - osc->osc_info(osc, format, opt, scopt); + ret = asprintf(&final, "%s%s", optbuff, argbuff); + if (ret < 0) { + os_assert_zero(ret); } - switch (opt->has_arg) { - case no_argument: - break; - case optional_argument: - snprintf(argbuff, sizeof(argbuff), "[=%s]", scopt->osco_argdesc); - break; - case required_argument: - snprintf(argbuff, sizeof(argbuff), "=<%s>", scopt->osco_argdesc); - break; - default: - __builtin_unreachable(); + return final; +} + +static char * +_os_subcommand_copy_option_spec_long(const os_subcommand_t *osc, + const os_subcommand_option_t *osco) +{ + const struct option *opt = osco->osco_option; + char optbuff[64] = ""; + char argbuff[64] = ""; + char *final = NULL; + int ret = -1; + + if (opt) { + snprintf(optbuff, sizeof(optbuff), "--%s", opt->name); + + switch (opt->has_arg) { + case no_argument: + break; + case optional_argument: + snprintf(argbuff, sizeof(argbuff), "[=%s]", + osco->osco_argument_usage); + break; + case required_argument: + snprintf(argbuff, sizeof(argbuff), "=<%s>", + osco->osco_argument_usage); + break; + default: + __builtin_unreachable(); + } + } else { + snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage); } ret = asprintf(&final, "%s%s", optbuff, argbuff); @@ -47,243 +250,363 @@ _os_subcommand_copy_optarg_usage(const os_subcommand_t *osc, return final; } -static void -_os_subcommand_print_optarg_usage(const os_subcommand_t *osc, - const struct option *opt, FILE *f) +static char * +_os_subcommand_copy_option_spec(const os_subcommand_t *osc, + const os_subcommand_option_t *osco, os_subcommand_option_spec_fmt_t fmt) { - os_subcommand_option_t scopt = { - .osco_flags = 0, - .osco_argdesc = opt->name, - }; - char *__os_free usage = NULL; - char *braces[2] = { - "", - "", - }; - - usage = _os_subcommand_copy_optarg_usage(osc, opt, - OS_SUBCOMMAND_OPTARG_USAGE, &scopt); - if (scopt.osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL) { - braces[0] = "["; - braces[1] = "]"; + int ret = -1; + char *spec = NULL; + char *__os_free spec_old = NULL; + + switch (fmt) { + case OS_SUBCOMMAND_OPTION_SPEC_SHORT: + _os_subcommand_copy_option_spec_short(osc, osco); + break; + case OS_SUBCOMMAND_OPTION_SPEC_LONG: + _os_subcommand_copy_option_spec_long(osc, osco); + break; + case OS_SUBCOMMAND_OPTION_SPEC_COMBINED: + spec = _os_subcommand_copy_option_spec_long(osc, osco); + if (osco->osco_option) { + spec_old = spec; + + ret = asprintf(&spec, "%s | -%c", spec, osco->osco_option->val); + if (ret < 0) { + os_assert_zero(ret); + } + } + + break; + default: + __builtin_unreachable(); } - fprintf(f, " %s%s%s", braces[0], usage, braces[1]); + return spec; } -static void -_os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f) +static char * +_os_subcommand_copy_usage_line(const os_subcommand_t *osc) { - const struct option *opts = osc->osc_options; - const struct option *curopt = NULL; + char *usage_line = NULL; size_t i = 0; + const os_subcommand_option_t *osco_i = NULL; + const char *optional_spec = ""; + char subcmd_name[64]; + int ret = -1; - fprintf(f, "usage: %s %s", getprogname(), osc->osc_name); - - while ((curopt = &opts[i]) && curopt->name) { - _os_subcommand_print_optarg_usage(osc, curopt, f); - i++; + // The usage line does not enumerate all possible optional options, just the + // required options. If there are optional options, then display that but + // otherwise leave them to be described by more extensive usage information. + if (osc->osc_optional) { + optional_spec = " [options]"; } - fprintf(f, "\n"); -} + if (osc == _main_cmd) { + strlcpy(subcmd_name, "", sizeof(subcmd_name)); + } else { + snprintf(subcmd_name, sizeof(subcmd_name), " %s", osc->osc_name); + } -static void -_os_subcommand_print_optarg_human(const os_subcommand_t *osc, - const struct option *opt, FILE *f) -{ - os_subcommand_option_t scopt = { - .osco_flags = 0, - .osco_argdesc = opt->name, - }; - char *__os_free usage = NULL; - char *__os_free human = NULL; - - usage = _os_subcommand_copy_optarg_usage(osc, opt, - OS_SUBCOMMAND_OPTARG_USAGE, &scopt); - fprintf(f, " %-24s", usage); - - human = _os_subcommand_copy_optarg_usage(osc, opt, - OS_SUBCOMMAND_OPTARG_HUMAN, &scopt); - wfprintf_np(f, -CTL_OUTPUT_OPTARG_PAD, CTL_OUTPUT_OPTARG_PAD, - CTL_OUTPUT_WIDTH, "%s", scopt.osco_argdesc); -} + ret = asprintf(&usage_line, "%s%s%s", + getprogname(), subcmd_name, optional_spec); + if (ret < 0) { + os_assert_zero(ret); + } -static void -_os_subcommand_print_human(const os_subcommand_t *osc, FILE *f) -{ - const struct option *opts = osc->osc_options; - const struct option *curopt = NULL; - size_t i = 0; + i = 0; + OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, required, i) { + char *__os_free usage_line_old = NULL; + char *__os_free osco_spec = NULL; - _os_subcommand_print_usage(osc, f); + usage_line_old = usage_line; - while ((curopt = &opts[i]) && curopt->name) { - _os_subcommand_print_optarg_human(osc, curopt, f); - i++; + osco_spec = _os_subcommand_copy_option_spec_long(osc, osco_i); + ret = asprintf(&usage_line, "%s %s", usage_line, osco_spec); + if (ret < 0) { + os_assert_zero(ret); + } } -} -static void -_os_subcommand_print_list(const os_subcommand_t *osc, FILE *f) -{ - wfprintf_np(f, CTL_OUTPUT_LIST_PAD, 0, 0, "%-24s %s", - osc->osc_name, osc->osc_desc); -} + i = 0; + OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) { + char *__os_free usage_line_old = NULL; + char *__os_free osco_spec = NULL; + const char *braces[] = { + "<", + ">", + }; + + if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) { + braces[0] = "["; + braces[1] = "]"; + } -static const os_subcommand_t * -_os_subcommand_find(const char *name) -{ - const os_subcommand_t **oscip = NULL; + usage_line_old = usage_line; - if (strcmp(_help_cmd.osc_name, name) == 0) { - return &_help_cmd; + osco_spec = _os_subcommand_copy_option_spec_long(osc, osco_i); + ret = asprintf(&usage_line, "%s %s%s%s", + usage_line, braces[0], osco_spec, braces[1]); + if (ret < 0) { + os_assert_zero(ret); + } } - LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { - const os_subcommand_t *osci = *oscip; + if (osc == _main_cmd && osc != _internal_main_cmd) { + // Always include the positional subcommand when printing usage for the + // main subcommand. We do not expect it to be specified in a user- + // provided main subcommand. + char *__os_free usage_line_old = NULL; + char *__os_free osco_spec = NULL; - if (strcmp(osci->osc_name, name) == 0) { - return osci; + usage_line_old = usage_line; + + osco_spec = _os_subcommand_copy_option_spec_long(osc, + &_main_positional[0]); + ret = asprintf(&usage_line, "%s <%s>", usage_line, osco_spec); + if (ret < 0) { + os_assert_zero(ret); } } - return NULL; + return usage_line; } -#pragma mark Default Usage static void -_usage_default(FILE *f) +_os_subcommand_print_option_usage(const os_subcommand_t *osc, + const os_subcommand_option_t *osco, FILE *f) { - const os_subcommand_t **oscip = NULL; + char *__os_free opt_spec = NULL; + ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD; - crfprintf_np(f, "usage: %s [...] | help [subcommand]", - getprogname()); - crfprintf_np(f, ""); + opt_spec = _os_subcommand_copy_option_spec(osc, osco, + OS_SUBCOMMAND_OPTION_SPEC_COMBINED); + fprintf(f, " %-24s", opt_spec); - crfprintf_np(f, "subcommands:"); - LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { - const os_subcommand_t *osci = *oscip; - _os_subcommand_print_list(osci, f); + // If the usage specifier is long, start the description on the next line. + if (strlen(opt_spec) >= CTL_OUTPUT_OPTARG_PAD) { + initpad = CTL_OUTPUT_OPTARG_PAD; + crfprintf_np(f, ""); } - _os_subcommand_print_list(&_help_cmd, f); + wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD, + CTL_OUTPUT_WIDTH, "%s", + osco->osco_argument_human); } -static int -_usage(FILE *f) +static void +_os_subcommand_print_help_line(const os_subcommand_t *osc, FILE *f) { - _usage_default(f); - return EX_USAGE; -} + ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD; -#pragma mark Help Subcommand -static int _help_invoke(const os_subcommand_t *osc, - int argc, - const char *argv[] -); + fprintf(f, " %-24s", osc->osc_name); -static const os_subcommand_t _help_cmd = { - .osc_version = OS_SUBCOMMAND_VERSION, - .osc_flags = 0, - .osc_name = "help", - .osc_desc = "prints helpful information", - .osc_optstring = NULL, - .osc_options = NULL, - .osc_info = NULL, - .osc_invoke = &_help_invoke, -}; + // If the usage specifier is long, start the description on the next line. + if (strlen(osc->osc_name) >= CTL_OUTPUT_OPTARG_PAD) { + initpad = CTL_OUTPUT_OPTARG_PAD; + crfprintf_np(f, ""); + } -static void -_help_print_subcommand(const os_subcommand_t *osc, FILE *f) -{ - wfprintf_np(f, 4, 4, 76, "%-16s%s", osc->osc_name, osc->osc_desc); + wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD, + CTL_OUTPUT_WIDTH, "%s", + osc->osc_desc); } static void -_help_print_all(FILE *f) +_os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f) { - const os_subcommand_t **oscip = NULL; + size_t i = 0; + const os_subcommand_option_t *osco_i = NULL; + char *__os_free usage_line = NULL; + bool header_printed = false; - _usage_default(f); - crfprintf_np(f, ""); + usage_line = _os_subcommand_copy_usage_line(osc); + wfprintf_np(f, 0, 0, CTL_OUTPUT_WIDTH, "usage: %s", usage_line); - crfprintf_np(f, "subcommands:"); - LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { - const os_subcommand_t *osci = *oscip; - if (osci->osc_flags & OS_SUBCOMMAND_FLAG_HIDDEN) { - continue; + if (osc->osc_required || osc->osc_positional || osc == _main_cmd) { + i = 0; + OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, required, i) { + _print_header(f, "required", &header_printed); + + crfprintf_np(f, ""); + _os_subcommand_print_option_usage(osc, osco_i, f); + } + + i = 0; + OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) { + _print_header(f, "required", &header_printed); + + if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) { + continue; + } + + crfprintf_np(f, ""); + _os_subcommand_print_option_usage(osc, osco_i, f); + } + + if (osc == _main_cmd && osc != _internal_main_cmd) { + // We do not expect the user's main command to specify that a + // subcommand must follow, so always defer to ours. + _print_header(f, "required", &header_printed); + + crfprintf_np(f, ""); + _os_subcommand_print_option_usage(osc, &_main_positional[0], f); + } + } + + header_printed = false; + + if (osc->osc_optional || osc->osc_positional) { + i = 0; + OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, optional, i) { + _print_header(f, "optional", &header_printed); + + crfprintf_np(f, ""); + _os_subcommand_print_option_usage(osc, osco_i, f); + } + + i = 0; + OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) { + if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) { + _print_header(f, "optional", &header_printed); + + crfprintf_np(f, ""); + _os_subcommand_print_option_usage(osc, osco_i, f); + } } - _help_print_subcommand(osci, f); } } -static int -_help_invoke(const os_subcommand_t *osc, int argc, const char *argv[]) +static const os_subcommand_t * +_os_subcommand_find(const char *name) { - const os_subcommand_t *target = NULL; + const os_subcommand_t **oscip = NULL; - if (argc == 1) { - _help_print_all(stdout); - } else { - target = _os_subcommand_find(argv[1]); - if (!target) { - crfprintf_np(stderr, "unrecognized subcommand: %s", argv[1]); - _usage_default(stderr); - return EX_USAGE; + if (strcmp(_help_cmd->osc_name, name) == 0) { + return &__help_cmd; + } + + LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { + const os_subcommand_t *osci = *oscip; + + if (osci->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) { + // The main subcommand cannot be invoked directly. + continue; } - _os_subcommand_print_human(target, stdout); + if (strcmp(osci->osc_name, name) == 0) { + return osci; + } } - return 0; + return NULL; } #pragma mark API int -os_subcommand_main(int argc, const char *argv[]) +os_subcommand_main(int argc, const char *argv[], + os_subcommand_main_flags_t flags) { - int exitcode = -1; + int xit = -1; const char *cmdname = NULL; - const os_subcommand_t *osci = NULL; + const os_subcommand_t *osc = NULL; + const os_subcommand_t **oscip = NULL; if (argc < 2) { - exitcode = _usage(stderr); + os_subcommand_fprintf(NULL, stderr, "please provide a subcommand"); + xit = EX_USAGE; + goto __out; + } + + // Find the main subcommand if any exists. Otherwise we'll just use our pre- + // canned main subcommand. + LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { + osc = *oscip; + if (osc->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) { + _main_cmd = osc; + break; + } + } + + osc = NULL; + + // Invoke the main subcommand to snarf any global options. Our default + // implementation does nothing and just returns 0. + xit = _main_cmd->osc_invoke(_main_cmd, argc, argv); + if (xit) { goto __out; } // Advance argument pointer and make the subcommand argv[0]. - argc -= 1; - argv += 1; + argc -= optind; + argv += optind; cmdname = argv[0]; - osci = _os_subcommand_find(cmdname); - if (osci) { - if (osci->osc_flags & OS_SUBCOMMAND_FLAG_REQUIRE_ROOT) { + if (argc < 1) { + os_subcommand_fprintf(NULL, stderr, "please provide a subcommand"); + xit = EX_USAGE; + goto __out; + } + + osc = _os_subcommand_find(cmdname); + if (osc) { + if (osc->osc_flags & OS_SUBCOMMAND_FLAG_REQUIRE_ROOT) { if (geteuid()) { - crfprintf_np(stderr, "subcommand requires root: %s", cmdname); - exitcode = EX_NOPERM; + os_subcommand_fprintf(osc, stderr, + "subcommand requires root: %s", + cmdname); + xit = EX_NOPERM; goto __out; } } - if (osci->osc_flags & OS_SUBCOMMAND_FLAG_TTYONLY) { + if (osc->osc_flags & OS_SUBCOMMAND_FLAG_TTYONLY) { if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) { - crfprintf_np(stderr, "subcommand requires a tty: %s", cmdname); - exitcode = EX_UNAVAILABLE; + os_subcommand_fprintf(osc, stderr, + "subcommand requires a tty: %s", + cmdname); + xit = EX_UNAVAILABLE; goto __out; } } - exitcode = osci->osc_invoke(osci, argc, argv); - if (exitcode == EX_USAGE) { - _os_subcommand_print_usage(osci, stderr); - } + xit = osc->osc_invoke(osc, argc, argv); } else { - crfprintf_np(stderr, "unrecognized subcommand: %s", cmdname); - exitcode = _usage(stderr); + os_subcommand_fprintf(NULL, stderr, "unknonwn subcommand: %s", cmdname); + xit = EX_USAGE; } __out: - return exitcode; + if (xit == EX_USAGE) { + if (!osc) { + osc = _main_cmd; + } + + _os_subcommand_print_usage(osc, stderr); + } + + return xit; +} + +void +os_subcommand_fprintf(const os_subcommand_t *osc, FILE *f, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vcrfprintf_np(f, fmt, ap); + va_end(ap); +} + +void +os_subcommand_vfprintf(const os_subcommand_t *osc, FILE *f, + const char *fmt, va_list ap) +{ + if (!osc || (osc->osc_flags & OS_SUBCOMMAND_FLAG_MAIN)) { + fprintf(f, "%s: ", getprogname()); + } else { + fprintf(f, "%s::%s: ", getprogname(), osc->osc_name); + } + + vcrfprintf_np(f, fmt, ap); } diff --git a/libdarwin/dirstat.c b/libdarwin/dirstat.c index 89b0a83..d62a521 100644 --- a/libdarwin/dirstat.c +++ b/libdarwin/dirstat.c @@ -335,8 +335,8 @@ fdirstat_fallback(int parent_fd, int flags, struct dirstat *ds) if (1 == attrs.link_count) { ds->total_size += object_size; } else { - bool new_fileid = _dirstat_fileid_set_add(fileid_seen, attrs.fileid); - if (new_fileid) { + bool seen_fileid = _dirstat_fileid_set_add(fileid_seen, attrs.fileid); + if (!seen_fileid) { ds->total_size += object_size; } else { DEBUGPRINT( "Skipping hardlinked file at %s/%s\n", path, name); diff --git a/libdarwin/h/bsd.h b/libdarwin/h/bsd.h index 6fc247e..a33a9e0 100644 --- a/libdarwin/h/bsd.h +++ b/libdarwin/h/bsd.h @@ -36,6 +36,10 @@ #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + __BEGIN_DECLS; /*! diff --git a/libdarwin/h/cleanup.h b/libdarwin/h/cleanup.h index 0b82c05..cbbd045 100644 --- a/libdarwin/h/cleanup.h +++ b/libdarwin/h/cleanup.h @@ -51,12 +51,17 @@ #include #include #include +#include #include #include #include #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + __BEGIN_DECLS; #if __has_attribute(cleanup) @@ -99,7 +104,7 @@ __os_cleanup_close(int *__fd) * @define __os_fclose * An attribute that may be applied to a variable's type. This attribute causes * the variable to be passed to fclose(3) when it goes out of scope. Applying - * this attribute to variables that do not reference a valid FILE* will result + * this attribute to variables that do not reference a valid FILE * will result * in undefined behavior. If the variable's value is NULL upon going out-of- * scope, no cleanup is performed. */ @@ -120,6 +125,26 @@ __os_cleanup_fclose(FILE **__fp) } } +/*! + * @define __os_closedir + * An attribute that may be applied to a variable's type. This attribute causes + * the variable to be passed to closedir(3) when it goes out of scope. Applying + * this attribute to variables that do not reference a valid DIR * will result + * in undefined behavior. If the variable's value is NULL upon going out-of- + * scope, no cleanup is performed. + */ +#define __os_closedir __attribute__((cleanup(__os_cleanup_closedir))) +static inline void +__os_cleanup_closedir(DIR **__dp) +{ + DIR *dp = *__dp; + + if (!dp) { + return; + } + posix_assert_zero(closedir(dp)); +} + /*! * @define __os_close_mach_recv * An attribute that may be applied to a variable's type. This attribute causes @@ -212,7 +237,7 @@ __os_cleanup_os_release(void *__p) } #endif -#if __COREFOUNDATION__ +#if DARWIN_CLEANUP_CF /*! * @define __os_cfrelease * An attribute that may be applied to a variable's type. This attribute causes @@ -220,6 +245,9 @@ __os_cleanup_os_release(void *__p) * this attribute to a variable which does not reference a valid CoreFoundation * object will result in undefined behavior. If the variable's value is NULL * upon going out-of-scope, no cleanup is performed. + * + * In order to use, you must define the DARWIN_CLEANUP_CF macro to 1 prior to + * including this header. */ #define __os_cfrelease __attribute__((cleanup(__os_cleanup_cfrelease))) static inline void @@ -232,7 +260,68 @@ __os_cleanup_cfrelease(void *__p) } CFRelease(cf); } -#endif // __COREFOUNDATION__ +#endif // DARWIN_CLEANUP_CF + +#if DARWIN_CLEANUP_IOKIT +/*! + * @define __os_iorelease + * An attribute that may be applied to a variable's type. This attribute causes + * the variable to be passed to IOObjectRelease() when it goes out of scope. + * Applying this attribute to a variable which does not reference a valid IOKit + * object will result in undefined behavior. If the variable's value is + * IO_OBJECT_NULL upon going out-of-scope, no cleanup is performed. + * + * + * In order to use, you must define the DARWIN_CLEANUP_IOKIT macro to 1 prior to + * including this header. + */ +#define __os_iorelease __attribute__((cleanup(__os_cleanup_iorelease))) +static inline void +__os_cleanup_iorelease(void *__p) +{ + kern_return_t kr = KERN_FAILURE; + io_object_t *iop = (io_object_t *)__p; + io_object_t io = *iop; + + if (io == IO_OBJECT_NULL) { + return; + } + + kr = IOObjectRelease(io); + if (kr) { + os_crash("IOObjectRetain: %{mach.errno}d", kr); + } +} + +/*! + * @define __os_ioclose + * An attribute that may be applied to a variable's type. This attribute causes + * the variable to be passed to IOServiceClose() when it goes out of scope. + * Applying this attribute to a variable which does not reference a valid IOKit + * connection will result in undefined behavior. If the variable's value is + * IO_OBJECT_NULL upon going out-of-scope, no cleanup is performed. + * + * In order to use, you must define the DARWIN_CLEANUP_IOKIT macro to 1 prior to + * including this header. + */ +#define __os_ioclose __attribute__((cleanup(__os_cleanup_ioclose))) +static inline void +__os_cleanup_ioclose(void *__p) +{ + kern_return_t kr = KERN_FAILURE; + io_connect_t *iop = (io_object_t *)__p; + io_connect_t io = *iop; + + if (io == IO_OBJECT_NULL) { + return; + } + + kr = IOServiceClose(io); + if (kr) { + os_crash("IOObjectRelease: %{mach.errno}d", kr); + } +} +#endif // DARWIN_CLEANUP_IOKIT /*! * @define __os_unfair_unlock @@ -275,11 +364,14 @@ __os_cleanup_unfair_unlock(void *__p) #define __os_free __os_cleanup_unsupported #define __os_close __os_cleanup_unsupported #define __os_fclose __os_cleanup_unsupported +#define __os_closedir __os_cleanup_unsupported #define __os_close_mach_recv __os_cleanup_unsupported #define __os_release_mach_send __os_cleanup_unsupported #define __os_preserve_errno __os_cleanup_unsupported #define __os_release __os_cleanup_unsupported #define __os_cfrelease __os_cleanup_unsupported +#define __os_iorelease __os_cleanup_unsupported +#define __os_ioclose __os_cleanup_unsupported #define __os_unfair_unlock __os_cleanup_unsupported #endif // __has_attribute(cleanup) diff --git a/libdarwin/h/ctl.h b/libdarwin/h/ctl.h index 450f097..2968b5a 100644 --- a/libdarwin/h/ctl.h +++ b/libdarwin/h/ctl.h @@ -32,118 +32,110 @@ * * The user may define each subcommand taken by the utility as: * - * static const os_subcommand_t _foo_cmd = { - * .osc_version = OS_SUBCOMMAND_VERSION, - * .osc_flags = 0, - * .osc_name = "foo", - * .osc_desc = "does a foo", - * .osc_optstring = NULL, - * .osc_options = NULL, - * .osc_info = NULL, - * .osc_invoke = &_foo_invoke, + * 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, + * }, * }; - * OS_SUBCOMMAND_REGISTER(_foo_cmd); * - * static const os_subcommand_t _bar_cmd = { - * .osc_version = OS_SUBCOMMAND_VERSION, - * .osc_flags = 0, - * .osc_name = "bar", - * .osc_desc = "bars a foo", - * .osc_optstring = "x:q", - * .osc_options = _bar_opts, - * .osc_info = &_bar_optinfo, - * .osc_invoke = &_bar_invoke, + * 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, * }; - * OS_SUBCOMMAND_REGISTER(_bar_cmd); - * }; * - * Where the "bar" subcommand's option information is returned by the routine: - * - * static void - * _bar_optinfo(const os_subcommand_t *osc, - * os_subcommand_optarg_format_t format, const struct option *opt, - * os_subcommand_option_t *scopt) - * { - * switch (format) { - * case OS_SUBCOMMAND_OPTARG_USAGE: - * switch (opt->val) { - * case 'x': - * scopt->osco_flags |= OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL; - * scopt->osco_argdesc = "x-argument"; - * break; - * case 'q': - * scopt->osco_argdesc = "q-argument"; - * break; - * default: - * __builtin_unreachable(); - * } - * break; - * case OS_SUBCOMMAND_OPTARG_HUMAN: - * switch (opt->val) { - * case 'x': - * scopt->osco_flags |= OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL; - * scopt->osco_argdesc = "argument describing x"; - * break; - * case 'q': - * scopt->osco_argdesc = "Lorem ipsum dolor sit amet, consectetur " - * "adipiscing elit. Nullam a maximus lectus. Curabitur ornare " - * "convallis turpis, in porttitor augue tempus laoreet. Maecenas " - * "auctor mauris tortor, et tempor libero maximus id. Donec ac " - * "nunc et elit sagittis commodo. Donec tincidunt libero vehicula " - * "ex eleifend sagittis. Suspendisse consectetur cursus elit. " - * "Proin neque metus, commodo id rhoncus eu, cursus hendrerit ex. " - * "Etiam in fringilla nulla, vitae mollis eros."; - * break; - * default: - * __builtin_unreachable(); - * } - * break; + * 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: playground [...] | help [subcommand] + * usage: tool * * subcommands: - * foo does a foo - * bar bars a foo + * foo foo a bar or maybe baz + * help Prints helpful information * * $ tool help foo - * usage: tool foo - * - * $ tool help bar - * usage: tool bar [--xarg] --qarg[=q-argument] - * --xarg argument describing x - * --qarg[=q-argument] Lorem ipsum dolor sit amet, consectetur - * adipiscing elit. Nullam a maximus lectus. - * Curabitur ornare convallis turpis, in porttitor - * augue tempus laoreet. Maecenas auctor mauris - * tortor, et tempor libero maximus id. Donec ac - * nunc et elit sagittis commodo. Donec tincidunt - * libero vehicula ex eleifend sagittis. Suspendisse - * consectetur cursus elit. Proin neque metus, - * commodo id rhoncus eu, cursus hendrerit ex. Etiam - * in fringilla nulla, vitae mollis eros. + * usage: tool foo [options] --bar= + * + * required options: + * --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 #include - -#if DARWIN_TAPI -#define LINKER_SET_ENTRY(_x, _y) -#else #include -#endif - #include #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + __BEGIN_DECLS; /*! @@ -169,31 +161,6 @@ typedef struct _os_subcommand os_subcommand_t; */ #define OS_SUBCOMMAND_OPTION_VERSION ((os_struct_version_t)0) -/*! - * @typedef os_subcommand_optarg_format_t - * A type describing a usage format for the argument taken by an option. - * - * @const OS_SUBCOMMAND_OPTION_USAGE - * The short-form name of the argument given to the option. For example, if the - * subcommand takes a "--file" option with a required argument, this might be - * "file-path" and will be displayed as - * - * --file= - * - * @const OS_SUBCOMMAND_OPTION_HUMAN - * The long-form description of the argument given to the option. Extending the - * above example, this might be "The path to a file to take as input. This path - * must be absolute; relative paths are not supported." and will be displayed as - * - * --file The path to a file to take as input. This path must be - * absolute; relative paths are not supported. - */ -DARWIN_API_AVAILABLE_20181020 -OS_CLOSED_ENUM(os_subcommand_optarg_format, uint64_t, - OS_SUBCOMMAND_OPTARG_USAGE, - OS_SUBCOMMAND_OPTARG_HUMAN, -); - /*! * @typedef os_subcommand_option_flags_t * Flags describing an option for a subcommand. @@ -201,66 +168,89 @@ OS_CLOSED_ENUM(os_subcommand_optarg_format, uint64_t, * @const OS_SUBCOMMAND_OPTION_FLAG_INIT * No flags set. This value is suitable for initialization purposes. * - * @const OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL - * The option does not need to be present in the subcommand invocation. By - * default, options are considered required. + * @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. */ DARWIN_API_AVAILABLE_20181020 OS_CLOSED_ENUM(os_subcommand_option_flags, uint64_t, OS_SUBCOMMAND_OPTION_FLAG_INIT = 0, - OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL = (1 << 0), + OS_SUBCOMMAND_OPTION_FLAG_TERMINATOR = (1 << 0), + OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS = (1 << 1), ); /*! * @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 queries about a command's options individually. This is done - * when the implementation is synthesizing a usage string. + * 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 - * On return from a {@link os_subcommand_option_info_t} function, a set of flags - * describing information about the option. + * 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_argdesc - * On return from a {@link os_subcommand_option_info_t} function, this should - * point to a constant string describing the argument to the 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= + * + * @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= The path to a file to take as input. This path must be + * absolute; relative paths are not supported. */ -DARWIN_API_AVAILABLE_20181020 +DARWIN_API_AVAILABLE_20191015 typedef struct _os_subcommand_option { const os_struct_version_t osco_version; os_subcommand_option_flags_t osco_flags; - const char *osco_argdesc; + const struct option *osco_option; + const char *osco_argument_usage; + const char *osco_argument_human; } os_subcommand_option_t; /*! - * @typedef os_subcommand_option_info_t - * A type describing a function which returns option information. - * - * @param osc - * The subcommand to which the option belongs. - * - * @param format - * The format of usage information required. - * - * @param opt - * A pointer to the option structure for which to retrieve information. - * - * @param scopt - * A pointer to a subcommand option structure to be populated with information - * pertaining to the option. When passed to the callee, this structure is zero- - * filled. + * @const OS_SUBCOMMAND_OPTION_TERMINATOR + * A convenience terminator for an array of {@link os_subcommand_option_t} + * structures. */ -DARWIN_API_AVAILABLE_20181020 -typedef void (*os_subcommand_option_info_t)( - const os_subcommand_t *osc, - os_subcommand_optarg_format_t format, - const struct option *opt, - os_subcommand_option_t *scopt); +#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 @@ -288,6 +278,21 @@ typedef void (*os_subcommand_option_info_t)( * * @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. */ DARWIN_API_AVAILABLE_20181020 OS_CLOSED_OPTIONS(os_subcommand_flags, uint64_t, @@ -295,6 +300,7 @@ OS_CLOSED_OPTIONS(os_subcommand_flags, uint64_t, 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), ); /*! @@ -312,17 +318,21 @@ OS_CLOSED_OPTIONS(os_subcommand_flags, uint64_t, * subcommand. * * @result - * An exit code, preferably from sysexits(3). Note that exit codes should not - * intersect with POSIX error codes from errno.h (cf. intro(2)). + * An exit code, preferably from sysexits(3). Do not return a POSIX error code + * directly from this routine. * * @discussion - * You may exit directly from within the routine if you wish. + * 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[]); + const os_subcommand_t *osc, + int argc, + const char *argv[] +); /*! * @struct os_subcommand_t @@ -361,7 +371,34 @@ typedef int (*os_subcommand_invoke_t)( * * @field osc_options * A pointer to an array of option structures describing the long options - * recognized by the subcommand (cf. getopt_long(3)). + * 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 @@ -370,7 +407,7 @@ typedef int (*os_subcommand_invoke_t)( * @field osc_invoke * The implementation for the subcommand. */ -DARWIN_API_AVAILABLE_20181020 +DARWIN_API_AVAILABLE_20191015 struct _os_subcommand { const os_struct_version_t osc_version; const os_subcommand_flags_t osc_flags; @@ -378,10 +415,23 @@ struct _os_subcommand { const char *const osc_desc; const char *osc_optstring; const struct option *osc_options; - const os_subcommand_option_info_t osc_info; + 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; }; +/*! + * @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 @@ -394,6 +444,9 @@ struct _os_subcommand { * @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 @@ -420,10 +473,70 @@ struct _os_subcommand { * * This routine implicitly implements a "help" subcommand. */ -DARWIN_API_AVAILABLE_20181020 +DARWIN_API_AVAILABLE_20191015 OS_EXPORT OS_WARN_RESULT OS_NONNULL2 int -os_subcommand_main(int argc, const char *argv[]); +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; diff --git a/libdarwin/h/err.h b/libdarwin/h/err.h index c5cd955..c912c53 100644 --- a/libdarwin/h/err.h +++ b/libdarwin/h/err.h @@ -55,6 +55,10 @@ #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + __BEGIN_DECLS; /*! diff --git a/libdarwin/h/errno.h b/libdarwin/h/errno.h index 4370b1e..1f21f85 100644 --- a/libdarwin/h/errno.h +++ b/libdarwin/h/errno.h @@ -35,6 +35,10 @@ #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + /*! * @enum * Additional POSIX-flavor error codes that are meaningful to Darwin. These diff --git a/libdarwin/h/mach_exception.h b/libdarwin/h/mach_exception.h index ef4a62e..76d1f7a 100644 --- a/libdarwin/h/mach_exception.h +++ b/libdarwin/h/mach_exception.h @@ -40,6 +40,10 @@ #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + __BEGIN_DECLS; /*! diff --git a/libdarwin/h/mach_utils.h b/libdarwin/h/mach_utils.h index 45ea439..8b95751 100644 --- a/libdarwin/h/mach_utils.h +++ b/libdarwin/h/mach_utils.h @@ -40,6 +40,10 @@ #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + /*! * @define OS_MIG_SUBSYSTEM_MAXSIZE * A macro that evaluates to the maximum size of a request or reply message in @@ -63,6 +67,42 @@ __BEGIN_DECLS; +/*! + * @function os_vm_address_from_ptr + * Converts the given pointer to a vm_address_t. + * + * @param p + * The pointer to convert. + * + * @result + * The pointer as a vm_address_t. + */ +DARWIN_API_AVAILABLE_20190830 +OS_ALWAYS_INLINE OS_WARN_RESULT +static inline vm_address_t +os_vm_address_from_ptr(const void *p) +{ + return (vm_address_t)(uintptr_t)p; +} + +/*! + * @function os_mach_vm_address_from_ptr + * Converts the given pointer to a mach_vm_address_t. + * + * @param p + * The pointer to convert. + * + * @result + * The pointer as a mach_vm_address_t. + */ +DARWIN_API_AVAILABLE_20190830 +OS_ALWAYS_INLINE OS_WARN_RESULT +static inline mach_vm_address_t +os_mach_vm_address_from_ptr(const void *p) +{ + return (mach_vm_address_t)(uintptr_t)p; +} + /*! * @function os_mach_msg_get_trailer * Obtains the trailer for the received Mach message. diff --git a/libdarwin/h/stdio.h b/libdarwin/h/stdio.h index 11e1c11..dd7f271 100644 --- a/libdarwin/h/stdio.h +++ b/libdarwin/h/stdio.h @@ -33,12 +33,18 @@ #include #include #include +#include #include +#include -// TAPI and the compiler don't agree about header search paths, so if TAPI found -// our header in the SDK, help it out. -#if DARWIN_TAPI && DARWIN_API_VERSION < 20180727 -#define DARWIN_API_AVAILABLE_20180727 +#if __has_include() +#include +#else +typedef uint64_t guardid_t; +#endif + +#if DARWIN_TAPI +#include "tapi.h" #endif __BEGIN_DECLS; @@ -68,6 +74,24 @@ os_fd_valid(os_fd_t fd) return (fd >= STDIN_FILENO); } +/*! + * @function os_guardid_from_ptr + * Converts the given pointer to a guardid_t. + * + * @param p + * The pointer to convert. + * + * @result + * The pointer as a guardid_t. + */ +DARWIN_API_AVAILABLE_20190830 +OS_ALWAYS_INLINE OS_WARN_RESULT +static inline guardid_t +os_guardid_from_ptr(const void *p) +{ + return (guardid_t)(uintptr_t)p; +} + /*! * @function fcheck_np * Checks the status of an fread(3) or fwrite(3) operation to a FILE. @@ -116,6 +140,97 @@ OS_EXPORT OS_WARN_RESULT os_fd_t dup_np(os_fd_t fd); +/*! + * @function claimfd_np + * Claims the given file descriptor for the caller's exclusive use by applying a + * guard and invalidating the given storage. + * + * @param fdp + * A pointer to the storage for the descriptor to claim. Upon return, a known- + * invalid value is written into this memory. + * + * @param gdid + * The optional guard value to enforce the caller's claim on the descriptor. + * + * @param gdflags + * The guard flags to enforce the caller's claim on the descriptor. + * + * @result + * The given descriptor with the guard applied. + */ +DARWIN_API_AVAILABLE_20190830 +OS_EXPORT OS_WARN_RESULT OS_NONNULL1 +os_fd_t +claimfd_np(os_fd_t *fdp, const guardid_t *gdid, u_int gdflags); + +/*! + * @function xferfd_np + * Transfers ownership from the given file descriptor back to the general public + * by clearing the guard associated with it. + * + * @param fdp + * A pointer to the storage for the descriptor to claim. Upon return, a known- + * invalid value is written into this memory. + * + * @param gdid + * The optional guard value to reliquish ownership on the descriptor. + * + * @param gdflags + * The guard flags to relinquish. + * + * @result + * The given descriptor with the guard cleared. This descriptor is suitable for + * claiming with {@link claimfd_np}. + */ +DARWIN_API_AVAILABLE_20190830 +OS_EXPORT OS_WARN_RESULT OS_NONNULL1 +os_fd_t +xferfd_np(os_fd_t *fdp, const guardid_t *gdid, u_int gdflags); + +/*! + * @function close_drop_np + * Variant of close(2) which transfers ownership from the caller and performs + * the close(2) operation. These semantics are useful for ensuring that a + * descriptor is not erroneously re-used after it has been closed. To achieve + * these semantics, this variant will clear the memory in which the descriptor + * resides and replace it with a known-invalid value before returning. + * + * @param fdp + * A pointer to the storage for the descriptor to close. Upon return, a known- + * invalid value is written into this memory. + * + * @param gdid + * The optional guard. If the descriptor is not guarded, pass NULL. + * + * @discussion + * If the implementation encounters a failure to close a valid descriptor + * number, the caller will be terminated. + */ +OS_EXPORT OS_NONNULL1 +void +close_drop_np(os_fd_t *fdp, const guardid_t *gdid); + +/*! + * @function close_drop_optional_np + * Variant of {@link close_drop} which will not attempt to close an invalid + * descriptor. Otherwise all semantics are the same. + * + * @param fdp + * A pointer to the storage for the descriptor to close. Upon return, a known- + * invalid value is written into this memory. + * + * @param gdid + * The optional guard. If the descriptor is not guarded, pass NULL. + * + * @discussion + * If the implementation encounters a failure to close a valid descriptor + * number, the caller will be terminated. The implementation will not attempt to + * close the descriptor if its value is -1. + */ +OS_EXPORT OS_NONNULL1 +void +close_drop_optional_np(os_fd_t *fdp, const guardid_t *gdid); + /*! * @function zsnprintf_np * snprintf(3) variant which returns the numnber of bytes written less the null diff --git a/libdarwin/h/stdlib.h b/libdarwin/h/stdlib.h index 25d524e..2d59ba3 100644 --- a/libdarwin/h/stdlib.h +++ b/libdarwin/h/stdlib.h @@ -51,10 +51,15 @@ #include #include +#include #include #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + __BEGIN_DECLS; /*! @@ -237,6 +242,122 @@ _os_strdup_known(const char *str) #define os_strdup(__size) __os_requires_experimental_libtrace #endif +/*! + * @function os_setflag + * Sets the given flag in a manner which is compatible with strongly-typed + * enumerations. + * + * @param _bf + * The bitfield in which to set the flag. + * + * @param _f + * The flag to set. + * + * @result + * The result of setting {@link _f} in {@link _bf}. + */ +#define os_setflag(_bf, _f) (typeof(_bf))((_bf) & (_f)) + +/*! + * @function os_clrflag + * Clears the given flag in a manner which is compatible with strongly-typed + * enumerations. + * + * @param _bf + * The bitfield in which to clear the flag. + * + * @param _f + * The flag to clear. + * + * @result + * The result of clearing {@link _f} from {@link _bf}. + * + * @discussion + * clrbit() will produce errors when given types smaller than a pointer such as + * int because it casts to char *; thus this implementation is required to deal + * properly with flags defined via {@link OS_OPTIONS} or similar. + */ +#define os_clrflag(_bf, _f) (typeof(_bf))((_bf) & (typeof(_bf))(~(_f))) + +/*! + * @function switch_posix + * Macro which expands to a switch() statement for handling both the success + * case as well as errno values set by POSIX and POSIX-y APIs that return -1 and + * set errno. + * + * @example + * + * int ret = dup(fd); + * switch_posix (ret) { + * case 0: + * // success + * break; + * case EINTR: + * // interrupted system call + * break; + * case EBADF: + * // bad file descriptor + * break; + * } + * + * @discussion + * This statement cannot be used with APIs that return positive values on + * failure. + */ +#define switch_posix(_ret) if ((_ret) < 0 || (errno = 0, 1)) switch (errno) + +/*! + * @function size_unsigned + * Converts a signed size quantity into an unsigned size quantity after + * verifying the former can be represented as the latter. + * + * @param ss + * The signed size quantity. + * + * @result + * The unsigned representation of {@link ss}. + * + * @discussion + * This routine is useful for passing a signed value (such as the size of a file + * from a stat(2) structure) into a routine which accepts unsigned input + * (e.g. write(2)). + */ +OS_ALWAYS_INLINE OS_WARN_RESULT +static inline size_t +size_unsigned(ssize_t ss) +{ + if (ss < 0) { + os_crash("value not representable as size_t"); + } + return (size_t)ss; +} + +/*! + * @function size_signed + * Converts an unsigned size quantity into a signed size quantity after + * verifying the former can be represented as the latter. + * + * @param un + * The unsigned size quantity. + * + * @result + * The signed representation of {@link un}. + * + * @discussion + * This routine is useful for comparing an unsigned value (such as a number of + * bytes) to the result of a routine which returns a signed type but only ever + * returns a negative number in the event of an error (e.g. read(2)). + */ +OS_ALWAYS_INLINE OS_WARN_RESULT +static inline ssize_t +size_signed(size_t un) +{ + if (un > SSIZE_MAX) { + os_crash("value not representable as ssize_t"); + } + return (ssize_t)un; +} + /*! * @function os_localtime_file * A routine to generate a time stamp that is suitable for embedding in a file @@ -372,6 +493,51 @@ OS_EXPORT OS_WARN_RESULT errno_t realpath_np(os_fd_t fd, char buff[static PATH_MAX]); +/*! + * @function memdup_np + * Copies the given range of bytes into a new allocation. + * + * @param _new + * Upon successful return, a pointer to a new allocation which has had the given + * source bytes copied into it. The caller is responsible for calling free(3) on + * this object when it is no longer needed. + * + * @param src + * The bytes to copy. + * + * @param len + * The number of bytes to copy. + * + * @result + * On success, zero is returned. Otherwise, the implementation may return any + * error that can be returned by malloc(3). + */ +DARWIN_API_AVAILABLE_20190830 +OS_EXPORT OS_WARN_RESULT OS_NONNULL1 OS_NONNULL2 +errno_t +memdup_np(void **_new, const void *src, size_t len); + +/*! + * @function memdup2_np + * Variant of {@link memdup_np} which guarantees that memory duplication will + * either succeed or not return (terminating the caller). + * + * @param src + * The bytes to copy. + * + * @param len + * The number of bytes to copy. + * + * @result + * On success, a pointer to the new allocation which has had the given source + * bytes copied into it. The caller is responsible for calling free(3) on this + * object when it is no longer needed. + */ +DARWIN_API_AVAILABLE_20190830 +OS_EXPORT OS_WARN_RESULT OS_MALLOC OS_NONNULL1 +void * +memdup2_np(const void *src, size_t len); + __END_DECLS; #endif // __DARWIN_STDLIB_H diff --git a/libdarwin/h/string.h b/libdarwin/h/string.h index 23c0098..b4db82e 100644 --- a/libdarwin/h/string.h +++ b/libdarwin/h/string.h @@ -32,6 +32,10 @@ #include #include +#if DARWIN_TAPI +#include "tapi.h" +#endif + __BEGIN_DECLS; /*! @@ -93,6 +97,21 @@ OS_EXPORT OS_COLD OS_WARN_RESULT OS_PURE const char * strerror_np(int code); +/*! + * @function strexit_np + * Returns a human-readable string for the given sysexits(3) code. + * + * @param code + * The exit code for which to obtain the string. + * + * @result + * A human-readable string describing the exit condition. + */ +DARWIN_API_AVAILABLE_20190830 +OS_EXPORT OS_COLD OS_WARN_RESULT OS_PURE +const char * +strexit_np(int code); + /*! * @function symerror_np * Returns the token name of the given {@link errno_t} or POSIX error diff --git a/libdarwin/internal.h b/libdarwin/internal.h index a16d866..25a29ba 100644 --- a/libdarwin/internal.h +++ b/libdarwin/internal.h @@ -90,22 +90,4 @@ #include "h/stdlib.h" #include "h/string.h" -#if DARWIN_TAPI -#undef os_assert_mach -#undef os_assert_mach_port_status - -// Duplicate declarations to make TAPI happy. This header is included in the -// TAPI build as an extra public header. -API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) -OS_EXPORT OS_NONNULL1 -void -(os_assert_mach)(const char *op, kern_return_t kr); - -API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) -OS_EXPORT -void -os_assert_mach_port_status(const char *desc, mach_port_t p, - mach_port_status_t *expected); -#endif - #endif //__DARWIN_INTERNAL_H diff --git a/libdarwin/stdio.c b/libdarwin/stdio.c index c1d1585..d7c4407 100644 --- a/libdarwin/stdio.c +++ b/libdarwin/stdio.c @@ -66,6 +66,63 @@ dup_np(os_fd_t fd) return dfd; } +os_fd_t +claimfd_np(os_fd_t *fdp, const guardid_t *gdid, u_int gdflags) +{ + int ret = -1; + int fd = *fdp; + + if (gdid) { + ret = change_fdguard_np(fd, NULL, 0, gdid, gdflags, NULL); + if (ret) { + os_crash("change_fdguard_np: %{darwin.errno}d", errno); + } + } + + *fdp = -1; + return fd; +} + +os_fd_t +xferfd_np(os_fd_t *fdp, const guardid_t *gdid, u_int gdflags) +{ + int ret = -1; + int fd = *fdp; + + ret = change_fdguard_np(fd, gdid, gdflags, NULL, 0, NULL); + if (ret) { + os_crash("change_fdguard_np: %{darwin.errno}d", errno); + } + + *fdp = -1; + return fd; +} + +void +close_drop_np(os_fd_t *fdp, const guardid_t *gdid) +{ + int ret = -1; + int fd = *fdp; + + if (gdid) { + ret = guarded_close_np(fd, gdid); + } else { + ret = close(fd); + } + + posix_assert_zero(ret); + *fdp = -1; +} + +void +close_drop_optional_np(os_fd_t *fdp, const guardid_t *gdid) +{ + if (!os_fd_valid(*fdp)) { + return; + } + close_drop_np(fdp, gdid); +} + size_t zsnprintf_np(char *buff, size_t len, const char *fmt, ...) { diff --git a/libdarwin/stdlib.c b/libdarwin/stdlib.c index d5ba786..1c22fb3 100644 --- a/libdarwin/stdlib.c +++ b/libdarwin/stdlib.c @@ -158,3 +158,26 @@ realpath_np(os_fd_t fd, char buff[static PATH_MAX]) return error; } + +errno_t +memdup_np(void **_new, const void *src, size_t len) +{ + void *mynew = NULL; + + mynew = malloc(len); + if (!mynew) { + return errno; + } + + memcpy(mynew, src, len); + *_new = mynew; + return 0; +} + +void * +memdup2_np(const void *src, size_t len) +{ + void *_new = os_malloc(len); + memcpy(_new, src, len); + return _new; +} diff --git a/libdarwin/string.c b/libdarwin/string.c index 7dbf105..79208e4 100644 --- a/libdarwin/string.c +++ b/libdarwin/string.c @@ -71,6 +71,14 @@ typedef struct _errno_desc { } #pragma mark Top-Level Statics +static const errno_desc_t _zero = { + .ed_error = 0, + .ed_sysexit = 0, + .ed_flags = 0, + .ed_sym = "0", + .ed_str = "successful termination", +}; + static const errno_desc_t _negative_one = { .ed_error = __ENEG_ONE, .ed_sysexit = EXIT_FAILURE, @@ -306,6 +314,10 @@ _find_error(int code) static const errno_desc_t * _find_sysexit(int code) { + if (code == 0) { + return &_zero; + } + if (code == EX_BADRECEIPT_NP) { return &_badreceipt; } @@ -352,10 +364,17 @@ strerror_np(int code) return _find_error(code)->ed_str; } +const char * +strexit_np(int code) +{ + const errno_desc_t *de = _find_sysexit(code); + return de->ed_str; +} + const char * symerror_np(int code) { - const errno_desc_t *de = _find_error(code);; + const errno_desc_t *de = _find_error(code); return de->ed_sym; } diff --git a/libdarwin/tapi.h b/libdarwin/tapi.h new file mode 100644 index 0000000..1779311 --- /dev/null +++ b/libdarwin/tapi.h @@ -0,0 +1,59 @@ +/*! + * @header + * TAPI-specific header to ensure project builds properly in installapi. + */ +#ifndef __DARWIN_TAPI_H +#define __DARWIN_TAPI_H + +#if !DARWIN_TAPI +#error "This header is for the installapi action only" +#endif + +#include +#include +#include +#include +#include + +#undef os_assert_mach +#undef os_assert_mach_port_status + +// Duplicate declarations to make TAPI happy. +API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) +OS_EXPORT OS_NONNULL1 +void +os_assert_mach(const char *op, kern_return_t kr); + +API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) +OS_EXPORT +void +os_assert_mach_port_status(const char *desc, mach_port_t p, + mach_port_status_t *expected); + +// TAPI and the compiler don't agree about header search paths, so if TAPI found +// our header in the SDK, and we've increased the API version, help it out. +#if DARWIN_API_VERSION < 20170407 +#define DARWIN_API_AVAILABLE_20170407 +#endif + +#if DARWIN_API_VERSION < 20180727 +#define DARWIN_API_AVAILABLE_20180727 +#endif + +#if DARWIN_API_VERSION < 20181020 +#define DARWIN_API_AVAILABLE_20181020 +#endif + +#if DARWIN_API_VERSION < 20190830 +#define DARWIN_API_AVAILABLE_20190830 +#endif + +#if DARWIN_API_VERSION < 20191015 +#define DARWIN_API_AVAILABLE_20191015 +#endif + +#if !defined(LINKER_SET_ENTRY) +#define LINKER_SET_ENTRY(_x, _y) +#endif + +#endif // __DARWIN_TAPI_H diff --git a/os/api.h b/os/api.h index d68f175..43a37d0 100644 --- a/os/api.h +++ b/os/api.h @@ -71,7 +71,7 @@ * individual preprocessor macros in this header that declare new behavior as * required. */ -#define DARWIN_API_VERSION 20181020u +#define DARWIN_API_VERSION 20191015u #if !DARWIN_BUILDING_LIBSYSTEM_DARWIN #define DARWIN_API_AVAILABLE_20170407 \ @@ -80,10 +80,18 @@ API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)) #define DARWIN_API_AVAILABLE_20181020 \ API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)) +#define DARWIN_API_AVAILABLE_20181020 \ + API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)) +#define DARWIN_API_AVAILABLE_20190830 \ + API_AVAILABLE(macos(10.15.2), ios(13.3), tvos(13.3), watchos(6.1.1)) +#define DARWIN_API_AVAILABLE_20191015 \ + API_AVAILABLE(macos(10.15.2), ios(13.3), tvos(13.3), watchos(6.1.1)) #else #define DARWIN_API_AVAILABLE_20170407 #define DARWIN_API_AVAILABLE_20180727 #define DARWIN_API_AVAILABLE_20181020 +#define DARWIN_API_AVAILABLE_20190830 +#define DARWIN_API_AVAILABLE_20191015 #endif /*! diff --git a/os/assumes.h b/os/assumes.h index ac76db5..c5bca69 100644 --- a/os/assumes.h +++ b/os/assumes.h @@ -133,6 +133,28 @@ os_assert_sprintf(int ret, size_t buff_size) } } +/*! + * @function os_assert_asprintf + * A routine to assert the result of a call to {v}asprintf(3). + * + * @param ret + * The return value from {v}asnprintf(3). + * + * @discussion + * If ret is less than zero, the routine will abort the caller with a message + * indicating the nature of the failure in the Application Specific Information + * section of the resulting crash log. + */ +API_AVAILABLE(macos(10.15.2), ios(13.3), tvos(13.3), watchos(6.1.1)) +OS_ALWAYS_INLINE OS_COLD +static inline void +os_assert_asprintf(int ret) +{ + if (ret < 0) { + os_crash("error printing buffer: %s", strerror(errno)); + } +} + /*! * @function os_assert_malloc * A routine to assert the result of allocations which may fail. diff --git a/tests/Makefile b/tests/Makefile index e6cbbed..3f6b561 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -9,7 +9,7 @@ endif include $(DEVELOPER_DIR)/AppleInternal/Makefiles/darwintest/Makefile.common -ifeq ($(PLATFORM),BridgeOS) +ifneq ($(PLATFORM),MacOSX) EXCLUDED_SOURCES += locale.c endif @@ -20,7 +20,7 @@ WARNING_CFLAGS := -Weverything \ -Wno-partial-availability -Wno-used-but-marked-unused \ -Wno-reserved-id-macro -fmacro-backtrace-limit=0 \ -Wno-c++98-compat -Wno-extra-semi -Wno-language-extension-token -OTHER_CFLAGS := -DDARWINTEST --std=gnu11 $(FRAMEWORK_CFLAGS) $(WARNING_CFLAGS) +OTHER_CFLAGS := -DDARWINTEST --std=gnu11 $(FRAMEWORK_CFLAGS) $(WARNING_CFLAGS) -I$(SDK_SYSTEM_FRAMEWORK_HEADERS) DT_LDFLAGS += -ldarwintest_utils ASAN_DYLIB_PATH := /usr/local/lib/sanitizers/ diff --git a/tests/abort_tests.c b/tests/abort_tests.c index f4306fa..cae43f3 100644 --- a/tests/abort_tests.c +++ b/tests/abort_tests.c @@ -6,30 +6,46 @@ #include #include +typedef enum { PTHREAD, WORKQUEUE } thread_type_t; + +typedef enum { + NO_CORRUPTION, + SIG_CORRUPTION, + FULL_CORRUPTION, +} corrupt_type_t; + static void * -body(void *corrupt) +body(void *ctx) { - T_LOG("Helper thread running: %d", (bool)corrupt); - if (corrupt) { - // The pthread_t is stored at the top of the stack and could be - // corrupted because of a stack overflow. To make the test more - // reliable, we will manually smash the pthread struct directly. - pthread_t self = pthread_self(); + corrupt_type_t corrupt_type = (corrupt_type_t)ctx; + pthread_t self = pthread_self(); + + T_LOG("Helper thread running: %d", corrupt_type); + + // The pthread_t is stored at the top of the stack and could be + // corrupted because of a stack overflow. To make the test more + // reliable, we will manually smash the pthread struct directly. + switch (corrupt_type) { + case NO_CORRUPTION: + break; + case SIG_CORRUPTION: + memset(self, 0x41, 128); + break; + case FULL_CORRUPTION: /* includes TSD */ memset(self, 0x41, 4096); + break; } + // Expected behavior is that if a thread calls abort, the process should // abort promptly. abort(); T_FAIL("Abort didn't?"); } -typedef enum { PTHREAD, WORKQUEUE } thread_type_t; - static void -abort_test(thread_type_t type, int expected_signal) +abort_test(thread_type_t type, corrupt_type_t corrupt_type) { pid_t child = fork(); - bool corrupt = expected_signal == SIGSEGV; if (child == 0) { T_LOG("Child running"); @@ -38,41 +54,54 @@ abort_test(thread_type_t type, int expected_signal) pthread_t tid; T_QUIET; T_ASSERT_POSIX_ZERO( - pthread_create(&tid, NULL, body, (void *)corrupt), NULL); + pthread_create(&tid, NULL, body, (void *)corrupt_type), NULL); break; } case WORKQUEUE: { dispatch_async_f(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - (void *)corrupt, &body); + (void *)corrupt_type, &body); break; } } sleep(5); T_FAIL("Child didn't abort"); exit(-1); - } else { - // Wait and check the exit status of the child - int status = 0; - pid_t pid = wait(&status); - T_QUIET; - T_ASSERT_EQ(pid, child, NULL); - T_QUIET; - T_EXPECT_FALSE(WIFEXITED(status), "WIFEXITED Status: %x", status); - T_QUIET; - T_EXPECT_TRUE(WIFSIGNALED(status), "WIFSIGNALED Status: %x", status); - T_QUIET; - T_EXPECT_FALSE(WIFSTOPPED(status), "WIFSTOPPED Status: %x", status); - // This test is successful if we trigger a SIGSEGV|SIGBUS or SIGABRT - // since both will promptly terminate the program - int signal = WTERMSIG(status); + } + + // Wait and check the exit status of the child + int status = 0; + pid_t pid = wait(&status); + T_QUIET; + T_ASSERT_EQ(pid, child, NULL); + T_QUIET; + T_EXPECT_FALSE(WIFEXITED(status), "WIFEXITED Status: %x", status); + T_QUIET; + T_EXPECT_TRUE(WIFSIGNALED(status), "WIFSIGNALED Status: %x", status); + T_QUIET; + T_EXPECT_FALSE(WIFSTOPPED(status), "WIFSTOPPED Status: %x", status); + + // This test is successful if we trigger a SIGSEGV|SIGBUS or SIGABRT + // since both will promptly terminate the program + int signal = WTERMSIG(status); + +#if defined(__i386__) || defined(__x86_64__) + // on intel pthread_self() reads a TSD so FULL corruption results + // in SIGSEGV/SIGBUS + if (corrupt_type == FULL_CORRUPTION) { + // any of these signals may happen depending on which libpthread + // you're running on. if (signal == SIGBUS) { - // rdar://53269061 T_LOG("Converting %d to SIGSEGV", signal); signal = SIGSEGV; } - T_EXPECT_EQ(signal, expected_signal, NULL); + T_EXPECT_EQ(signal, SIGSEGV, NULL); + T_END; } +#endif + + /* pthread calls abort_with_reason if only the signature is corrupt */ + T_EXPECT_EQ(signal, SIGABRT, NULL); } static void @@ -82,38 +111,48 @@ signal_handler(int signo) T_FAIL("Unexpected signal: %d\n", signo); } -T_DECL(abort_pthread_corrupt_test, "Tests abort") +T_DECL(abort_pthread_corrupt_test_full, "Tests abort") { - abort_test(PTHREAD, SIGSEGV); + abort_test(PTHREAD, FULL_CORRUPTION); } -T_DECL(abort_workqueue_corrupt_test, "Tests abort") +T_DECL(abort_workqueue_corrupt_test_full, "Tests abort") { - abort_test(WORKQUEUE, SIGSEGV); + abort_test(WORKQUEUE, FULL_CORRUPTION); } -T_DECL(abort_pthread_handler_test, "Tests abort") +T_DECL(abort_pthread_handler_test_full, "Tests abort") { // rdar://52892057 T_SKIP("Abort hangs if the user registers their own SIGSEGV handler"); signal(SIGSEGV, signal_handler); - abort_test(PTHREAD, SIGSEGV); + abort_test(PTHREAD, FULL_CORRUPTION); } -T_DECL(abort_workqueue_handler_test, "Tests abort") +T_DECL(abort_workqueue_handler_test_full, "Tests abort") { // rdar://52892057 T_SKIP("Abort hangs if the user registers their own SIGSEGV handler"); signal(SIGSEGV, signal_handler); - abort_test(WORKQUEUE, SIGSEGV); + abort_test(WORKQUEUE, FULL_CORRUPTION); +} + +T_DECL(abort_pthread_corrupt_test_sig, "Tests abort") +{ + abort_test(PTHREAD, SIG_CORRUPTION); +} + +T_DECL(abort_workqueue_corrupt_test_sig, "Tests abort") +{ + abort_test(WORKQUEUE, SIG_CORRUPTION); } T_DECL(abort_pthread_test, "Tests abort") { - abort_test(PTHREAD, SIGABRT); + abort_test(PTHREAD, NO_CORRUPTION); } T_DECL(abort_workqueue_test, "Tests abort") { - abort_test(WORKQUEUE, SIGABRT); + abort_test(WORKQUEUE, NO_CORRUPTION); } diff --git a/tests/darwin_bsd.c b/tests/darwin_bsd.c index 56efa8e..10d4d13 100644 --- a/tests/darwin_bsd.c +++ b/tests/darwin_bsd.c @@ -1,5 +1,9 @@ #include +#if !defined(DARWIN_API_AVAILABLE_20190830) +#define DARWIN_API_AVAILABLE_20190830 +#endif + #include "../libdarwin/bsd.c" static struct test_case { diff --git a/tests/locale.c b/tests/locale.c index d3aa4bf..8d188e9 100644 --- a/tests/locale.c +++ b/tests/locale.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,6 +8,7 @@ #include +#if TARGET_OS_OSX T_DECL(locale_PR_23679075, "converts a cyrillic a to uppercase") { locale_t loc = newlocale(LC_COLLATE_MASK|LC_CTYPE_MASK, "ru_RU", 0); @@ -32,3 +34,4 @@ T_DECL(locale_PR_28774201, "return code on bad locale") T_EXPECT_NULL(newlocale(LC_COLLATE_MASK | LC_CTYPE_MASK, "foobar", NULL), NULL); T_EXPECT_EQ(errno, ENOENT, NULL); } +#endif -- 2.45.2