From nobody Thu Oct 08 16:29:12 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4j0wRx4rQ5z6vtTy for ; Thu, 08 Oct 2026 16:29:17 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4j0wRx2bTbz3J44 for ; Thu, 08 Oct 2026 16:29:17 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1791476957; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=/GYJ2hP/3n7PdsqB3p2KMn4ADGSRy2BlUpcvS7ynU4g=; b=smCMS7VmqmGlyn+HpGg/SUcX8kx75KySGOsYJHKlV7xsbuf8c75W72jxDpc+o+QDgN1bEs 8gzYjOSuyAa7IogyfO8egs0pgWAwRKGzq8GFVaMiWjjD9hatoZJOEgG+bJoNFX/2nx2LzN E9x+8xtbvx3fIK6CuRX2C17CK0zkwLX1THAaFVvg5VquyTgUr4VQol2DZa7ZHbfuuSGW5E H+WtBA0aBeY0myV4iYnpenRQBIJyHdLApV9vOEUlH9w8OZoB4wDkoeQZN3I6FB1yK0MVz3 km+YiRcCmILPKRvFMN95uKHL8kR1c74hBF8VWIT68lsC0oRtUdFnYF3dura6OQ== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1791476957; b=AmCjPbmjm/ME6DvJ76P6SWGsAkKYDz9JrkmmclIL8JReoD03qRNj/LsSUleXg6zE2f/pR4 /nt8UQN7XG/h+ovXMTIvLwzPE0JeCgaoZhxgpXojEl96Yuld3jf9DM/QST+G+zvGd9v+FG roAQm0/RGbJR7x1Tu8B5t8EGLuA8ekdjjRTzr757Iv5y04xLY+liQteeXhbqECjXObIVlC 0CP5RDlgjskkoubXirBse68BCuBcOLKeHZj+9qpaCilMWx78K4yXQE54QV9SVUcI34Zbcv CZAMiiJdUr7leXs78NmNxJGgrU4NNBTPBSJgDAV+kSEOwxBPFlrbEKSflMDf2A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1791476957; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=/GYJ2hP/3n7PdsqB3p2KMn4ADGSRy2BlUpcvS7ynU4g=; b=jB+r4JM96p3hNGek7OM9ddCx2KyDAuSQGEjEiFkyEGg/ZoBsJxFektJKHVhdm8wY7rEZok k0OtnR+0n9z8EfzCKw25O1HIMDpsukvpI0YHQnRcO4LlblqLcFUwE5Tze5yfRjV2HQQvG1 4MMyEWdwvMDE2z3Vu9rfa0tDOVqUX6y9w7TYhLm+oL4BPMVYiZZIx2aL5XnsaVFqv370c5 XM63lPZuh3bP77sXe9descR1WvrpMxkRq9clWpXZLHODb3ivI8YPO83Gjiz5LViJ8luis0 6g5zsmB8KmqQ+de2PNv7tXWUi/YB9TBISlawK7BKYF72JXwKjbzR6+2ZVfWE1w== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4j0wRx1N4Wznvf for ; Thu, 08 Oct 2026 16:29:17 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3f711 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 08 Oct 2026 16:29:12 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Devin Teske Subject: git: 439eb9c9771b - main - sysconf: accept a unique prefix of a target keyword List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: dteske X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 439eb9c9771b8a62b5bcc8d2f7337f26c4d717e2 Auto-Submitted: auto-generated Date: Thu, 08 Oct 2026 16:29:12 +0000 Message-Id: <6ac7c4d8.3f711.a8e1e26@gitrepo.freebsd.org> The branch main has been updated by dteske: URL: https://cgit.FreeBSD.org/src/commit/?id=439eb9c9771b8a62b5bcc8d2f7337f26c4d717e2 commit 439eb9c9771b8a62b5bcc8d2f7337f26c4d717e2 Author: Devin Teske AuthorDate: 2026-10-08 16:26:02 +0000 Commit: Devin Teske CommitDate: 2026-10-08 16:26:02 +0000 sysconf: accept a unique prefix of a target keyword Exact match still wins. An ambiguous prefix is rejected with the list of matches. Move keyword selection and the rc pass-through into sysconf-targets.c. Bump sysconf(8) to 2.1. Reviewed by: kfv Differential Revision: https://reviews.freebsd.org/D59924 --- usr.sbin/sysconf/Makefile | 1 + usr.sbin/sysconf/sysconf-targets.c | 179 +++++++++++++++++++++++++++++++++ usr.sbin/sysconf/sysconf.8 | 22 +++- usr.sbin/sysconf/sysconf.c | 91 ++--------------- usr.sbin/sysconf/sysconf_priv.h | 7 +- usr.sbin/sysconf/tests/sysconf_test.sh | 49 +++++++++ 6 files changed, 262 insertions(+), 87 deletions(-) diff --git a/usr.sbin/sysconf/Makefile b/usr.sbin/sysconf/Makefile index 84a12628cdc5..2c96997ff787 100644 --- a/usr.sbin/sysconf/Makefile +++ b/usr.sbin/sysconf/Makefile @@ -12,6 +12,7 @@ MAN= sysconf.8 \ sysconf-targets.8 SRCS= sysconf.c \ + sysconf-targets.c \ sysconf_edit_assign.c \ sysconf_edit_make.c \ sysconf_edit_write.c \ diff --git a/usr.sbin/sysconf/sysconf-targets.c b/usr.sbin/sysconf/sysconf-targets.c new file mode 100644 index 000000000000..940502d4ad02 --- /dev/null +++ b/usr.sbin/sysconf/sysconf-targets.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2013-2026 Devin Teske + * Copyright (c) 2021-2026 Faraz Vahedi + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Target keyword selection: unique-prefix match, and the rc pass-through. + */ + +#include "sysconf_priv.h" + +/* + * Unified target keywords: libbsdconf formats plus pass-throughs. Exact + * match wins; otherwise a unique prefix is accepted. Keep alphabetical so + * ambiguous-match lists read naturally (e.g. `s' -> src, sysctl). + */ +static const char *const target_keywords[] = { + "generic", + "loader", + "make", + "rc", + "src", + "sysctl", + NULL +}; + +/* + * Map `token' to a canonical keyword from `keywords'. Exact match wins; + * otherwise a unique prefix is accepted. Ambiguous prefixes print the + * matches and call usage(). Unknown tokens return NULL. + */ +const char * +resolve_keyword(const char *token, const char *const *keywords, + const char *what) +{ + char matches[128]; + const char *found; + size_t off; + size_t tlen; + unsigned int n; + unsigned int nmatch; + + if (token == NULL || *token == '\0') + return (NULL); + tlen = strlen(token); + for (n = 0; keywords[n] != NULL; n++) + if (strcmp(keywords[n], token) == 0) + return (keywords[n]); + found = NULL; + nmatch = 0; + off = 0; + matches[0] = '\0'; + for (n = 0; keywords[n] != NULL; n++) { + if (strncmp(keywords[n], token, tlen) != 0) + continue; + nmatch++; + found = keywords[n]; + if (off > 0 && off < sizeof(matches)) + off += (size_t)snprintf(matches + off, + sizeof(matches) - off, ", "); + if (off < sizeof(matches)) + off += (size_t)snprintf(matches + off, + sizeof(matches) - off, "%s", + keywords[n]); + } + if (nmatch == 1) + return (found); + if (nmatch > 1) { + warnx("ambiguous %s `%s'; matches %s", what, token, matches); + usage(); + } + return (NULL); +} + +static const char * +resolve_target_keyword(const char *token) +{ + + return (resolve_keyword(token, target_keywords, "target")); +} + +/* + * Locate the target keyword in `argv' without consuming or validating + * anything: the first argument that is neither an option cluster nor the + * argument of one (per OPTSTRING; an unknown option letter is assumed to + * take no argument). A `--' ends option scanning as usual. Returns the + * argv index of the target, or -1 when there is none. Used before any + * getopt(3) processing so that the `rc' pass-through target (see + * exec_sysrc() below) can be detected while the command line is still + * pristine. + * + * Attached arguments (`-fPATH', `-R/altroot') are self-contained: the rest + * of the cluster is the option's argument, so the following argv element is + * not consumed. Only a trailing option letter that takes an argument and + * has nothing after it in the cluster (`-f PATH') skips the next element. + */ +static int +find_target(int argc, char *argv[]) +{ + int n; + const char *o; + const char *p; + + for (n = 1; n < argc; n++) { + if (strcmp(argv[n], "--") == 0) + return (n + 1 < argc ? n + 1 : -1); + if (argv[n][0] == '-' && argv[n][1] != '\0') { + for (p = argv[n] + 1; *p != '\0'; p++) { + o = strchr(OPTSTRING, *p); + if (o != NULL && o[1] == ':') { + /* Takes an argument */ + if (p[1] != '\0') { + ; + /* attached: rest of argv[n] */ + } else if (n + 1 < argc) { + n++; + /* separate argv */ + } + break; + } + } + continue; + } + return (n); + } + + return (-1); +} + +/* + * The `rc' pass-through: rc.conf(5) is sysrc(8)'s domain, and its feature + * set is a superset of ours, so no option or argument is validated, + * interpreted, or reordered here. Every argument except the target + * keyword itself (at argv index `skip') is handed to sysrc(8) verbatim. + * Never returns. + */ +static void +exec_sysrc(int argc, char *argv[], int skip) +{ + int n; + int nargc = 0; + char **nargv; + + if ((nargv = calloc((size_t)argc + 1, sizeof(*nargv))) == NULL) + err(EXIT_FAILURE, NULL); + nargv[nargc++] = (char *)(uintptr_t)"sysrc"; + for (n = 1; n < argc; n++) { + if (n == skip) + continue; + nargv[nargc++] = argv[n]; + } + nargv[nargc] = NULL; + + execvp("sysrc", nargv); + err(EXIT_FAILURE, "sysrc"); +} + +/* + * Resolve the target keyword. The `rc' target execs sysrc(8) and does + * not return, so `--help' and `--version' are not intercepted on that + * path. Unique-prefix shorthands are expanded (`r', `m'). Returns the + * canonical keyword, or NULL when the token is unknown or absent. + */ +const char * +target_resolve(int argc, char *argv[]) +{ + const char *resolved; + int n; + + n = find_target(argc, argv); + if (n < 0) + return (NULL); + resolved = resolve_target_keyword(argv[n]); + if (resolved != NULL && strcmp(resolved, "rc") == 0) + exec_sysrc(argc, argv, n); /* never returns */ + return (resolved); +} diff --git a/usr.sbin/sysconf/sysconf.8 b/usr.sbin/sysconf/sysconf.8 index c026d40f68f9..b6147bd39088 100644 --- a/usr.sbin/sysconf/sysconf.8 +++ b/usr.sbin/sysconf/sysconf.8 @@ -3,7 +3,7 @@ .\" .\" SPDX-License-Identifier: BSD-2-Clause .\" -.Dd September 16, 2026 +.Dd September 22, 2026 .Dt SYSCONF 8 .Os .Sh NAME @@ -51,7 +51,8 @@ and .Pq Xr rc.conf 5 . The first positional argument selects a .Ar target -keyword; +keyword +.Pq or a unique prefix of one; see Sx TARGETS ; options are equally accepted before it. Each target is documented on its own page .Pq see Sx TARGETS ; @@ -266,6 +267,23 @@ See .Xr sysconf-generic 8 . .El .Pp +A unique prefix of a target keyword is accepted: +.Ql sy +selects +.Cm sysctl +and +.Ql sr +selects +.Cm src . +.Ql s +matches both and is rejected with a list of matches. +An exact keyword always wins. +Prefix matching is case-sensitive and applies to the start of the keyword +only, so +.Ql ctl +does not select +.Cm sysctl . +.Pp Cross-cutting rules for multi-file sourcing, defaults, and where writes versus removals land are in diff --git a/usr.sbin/sysconf/sysconf.c b/usr.sbin/sysconf/sysconf.c index 10b7ed302f0f..8e23dc7a8278 100644 --- a/usr.sbin/sysconf/sysconf.c +++ b/usr.sbin/sysconf/sysconf.c @@ -57,82 +57,6 @@ const char *jailname = NULL; /* `-j jail' */ const char *module = NULL; /* `-k module' */ const char *rootdir = ""; /* `-R dir' */ -/* - * Locate the target keyword in `argv' without consuming or validating - * anything: the first argument that is neither an option cluster nor the - * argument of one (per OPTSTRING; an unknown option letter is assumed to - * take no argument). A `--' ends option scanning as usual. Returns the - * argv index of the target, or -1 when there is none. Used before any - * getopt(3) processing so that the `rc' pass-through target (see - * exec_sysrc() below) can be detected while the command line is still - * pristine. - * - * Attached arguments (`-fPATH', `-R/altroot') are self-contained: the rest - * of the cluster is the option's argument, so the following argv element is - * not consumed. Only a trailing option letter that takes an argument and - * has nothing after it in the cluster (`-f PATH') skips the next element. - */ -static int -find_target(int argc, char *argv[]) -{ - int n; - const char *o; - const char *p; - - for (n = 1; n < argc; n++) { - if (strcmp(argv[n], "--") == 0) - return (n + 1 < argc ? n + 1 : -1); - if (argv[n][0] == '-' && argv[n][1] != '\0') { - for (p = argv[n] + 1; *p != '\0'; p++) { - o = strchr(OPTSTRING, *p); - if (o != NULL && o[1] == ':') { - /* Takes an argument */ - if (p[1] != '\0') { - ; - /* attached: rest of argv[n] */ - } else if (n + 1 < argc) { - n++; - /* separate argv */ - } - break; - } - } - continue; - } - return (n); - } - - return (-1); -} - -/* - * The `rc' pass-through: rc.conf(5) is sysrc(8)'s domain, and its feature - * set is a superset of ours, so no option or argument is validated, - * interpreted, or reordered here -- every argument except the target - * keyword itself (at argv index `skip') is handed to sysrc(8) verbatim. - * Never returns. - */ -static void -exec_sysrc(int argc, char *argv[], int skip) -{ - int n; - int nargc = 0; - char **nargv; - - if ((nargv = calloc((size_t)argc + 1, sizeof(*nargv))) == NULL) - err(EXIT_FAILURE, NULL); - nargv[nargc++] = (char *)(uintptr_t)"sysrc"; - for (n = 1; n < argc; n++) { - if (n == skip) - continue; - nargv[nargc++] = argv[n]; - } - nargv[nargc] = NULL; - - execvp("sysrc", nargv); - err(EXIT_FAILURE, "sysrc"); -} - /* * Run the getopt(3) loop over `argv', setting the option globals. Called * once for the options preceding the target keyword and once for those @@ -241,19 +165,16 @@ main(int argc, char *argv[]) int have_writes = 0; int n; int rv = EXIT_SUCCESS; + const char *resolved = NULL; const char *target = NULL; pgm = getprogname(); /* - * The `rc' target is a pure pass-through to sysrc(8), detected - * while the command line is still pristine so that nothing -- - * `--help' and `--version' included -- is intercepted on its way - * there (see exec_sysrc()). + * Target selection, including the rc pass-through, runs while + * argv is still pristine (see target_resolve()). */ - n = find_target(argc, argv); - if (n > 0 && strcmp(argv[n], "rc") == 0) - exec_sysrc(argc, argv, n); /* never returns */ + resolved = target_resolve(argc, argv); /* * Honor `--help' and `--version' wherever they appear (getopt(3) @@ -287,7 +208,7 @@ main(int argc, char *argv[]) * program name. */ if (argc > 0) { - target = argv[0]; + target = resolved != NULL ? resolved : argv[0]; argc--; argv++; #ifdef __FreeBSD__ @@ -679,6 +600,8 @@ help(void) "pass-through: all other arguments go to sysrc(8) verbatim"); fprintf(stderr, TGTFMT, "generic", "no default files; requires -f file"); + fprintf(stderr, + "A unique prefix of a target keyword is accepted.\n"); fprintf(stderr, "OPTIONS:\n"); #define OPTFMT "\t%-9s %s\n" fprintf(stderr, OPTFMT, "-A", diff --git a/usr.sbin/sysconf/sysconf_priv.h b/usr.sbin/sysconf/sysconf_priv.h index c72dd8f95c36..8add1fb3956e 100644 --- a/usr.sbin/sysconf/sysconf_priv.h +++ b/usr.sbin/sysconf/sysconf_priv.h @@ -37,7 +37,7 @@ #define __unused /* not all compilers support attributes */ #endif -#define SYSCONF_VERSION "2.0 2026-09-16" +#define SYSCONF_VERSION "2.1 2026-09-22" /* getopt(3) optstring; shared by parse_options() and find_target() */ #define OPTSTRING "AacdDEeFf:hij:k:lLnNqR:svVx" @@ -196,6 +196,11 @@ int sysctl_writable(const char *_name, const char *_value, int _quiet_unknown); #endif +/* sysconf-targets.c */ +const char *resolve_keyword(const char *_token, + const char *const *_keywords, const char *_what); +const char *target_resolve(int _argc, char **_argv); + /* sysconf.c */ void usage(void); void help(void); diff --git a/usr.sbin/sysconf/tests/sysconf_test.sh b/usr.sbin/sysconf/tests/sysconf_test.sh index b81379474cf7..0914e8727d2f 100644 --- a/usr.sbin/sysconf/tests/sysconf_test.sh +++ b/usr.sbin/sysconf/tests/sysconf_test.sh @@ -438,6 +438,54 @@ max_bytes_cap_cleanup() cleanup_root } +atf_test_case target_prefix cleanup +target_prefix_head() +{ + atf_set "descr" \ + "Unique target prefixes resolve; ambiguous and unknown fail" +} +target_prefix_body() +{ + find_sysconf + setup_root + :> "$ROOT/etc/make.conf" + :> "$ROOT/etc/src-env.conf" + :> "$ROOT/etc/src.conf" + printf 'foo=1\n' > "$ROOT/etc/make.conf" + + # Unique prefixes + atf_check -o inline:'foo: 1\n' "$SYSCONF" m -R "$ROOT" foo + atf_check -o inline:'foo: 1\n' "$SYSCONF" ma -R "$ROOT" foo + atf_check -o inline:'foo: 1\n' "$SYSCONF" make -R "$ROOT" foo + atf_check -o inline:'foo: 1\n' "$SYSCONF" sr -R "$ROOT" foo + printf 'kern.foo=1\n' > "$ROOT/etc/sysctl.conf" + atf_check -o inline:'kern.foo: 1\n' "$SYSCONF" sy -R "$ROOT" kern.foo + # generic requires -f + atf_check -s not-exit:0 -e match:'no default files' \ + "$SYSCONF" g -R "$ROOT" foo + atf_check -o inline:'foo: 1\n' \ + "$SYSCONF" g -R "$ROOT" -f /etc/make.conf foo + + # Ambiguous: src and sysctl + atf_check -s not-exit:0 \ + -e match:'ambiguous target' \ + -e match:'src' \ + -e match:'sysctl' \ + "$SYSCONF" s foo + # Exact still wins + atf_check -o inline:'foo: 1\n' "$SYSCONF" src -R "$ROOT" foo + # Infix is not a prefix + atf_check -s not-exit:0 -e match:'unknown target' \ + "$SYSCONF" ctl foo + # src-env is not a prefix of src + atf_check -s not-exit:0 -e match:'src-env' -e match:'src' \ + "$SYSCONF" src-env foo +} +target_prefix_cleanup() +{ + cleanup_root +} + atf_init_test_cases() { atf_add_test_case make_src_knobs @@ -451,4 +499,5 @@ atf_init_test_cases() atf_add_test_case equal_value_nocheck_mtime atf_add_test_case sysctl_oid_range atf_add_test_case max_bytes_cap + atf_add_test_case target_prefix }