git: 398b29f07f7d - main - sysconf: add a poudriere target

From: Devin Teske <dteske_at_FreeBSD.org>
Date: Thu, 08 Oct 2026 16:29:56 UTC
The branch main has been updated by dteske:

URL: https://cgit.FreeBSD.org/src/commit/?id=398b29f07f7d1fa6a045b3ccf242a55cb61207c0

commit 398b29f07f7d1fa6a045b3ccf242a55cb61207c0
Author:     Devin Teske <dteske@FreeBSD.org>
AuthorDate: 2026-10-08 16:26:43 +0000
Commit:     Devin Teske <dteske@FreeBSD.org>
CommitDate: 2026-10-08 16:26:43 +0000

    sysconf: add a poudriere target
    
    poudriere.conf is handed to sysrc(8), including the jail, tree, and
    set overlays poudriere(8) consults.  make, src, and src-env are
    sub-targets over the matching make(1) files under poudriere.d.  -l
    and -L list one path per line; -L includes overlays that do not exist
    yet.  A bare -- ends the sub-target so the next word is a variable.
    Bump sysconf(8) to 2.2.
    
    Reviewed by:    fuz, kfv, bcr
    Differential Revision:  https://reviews.freebsd.org/D59943
---
 usr.sbin/sysconf/Makefile              |   2 +
 usr.sbin/sysconf/sysconf-poudriere.8   | 364 ++++++++++++++++++
 usr.sbin/sysconf/sysconf-targets.8     |   4 +-
 usr.sbin/sysconf/sysconf-targets.c     |  70 +++-
 usr.sbin/sysconf/sysconf.8             |  57 ++-
 usr.sbin/sysconf/sysconf.c             |  18 +-
 usr.sbin/sysconf/sysconf_poudriere.c   | 677 +++++++++++++++++++++++++++++++++
 usr.sbin/sysconf/sysconf_priv.h        |  13 +-
 usr.sbin/sysconf/tests/sysconf_test.sh | 201 ++++++++++
 9 files changed, 1378 insertions(+), 28 deletions(-)

diff --git a/usr.sbin/sysconf/Makefile b/usr.sbin/sysconf/Makefile
index 2c96997ff787..cbe5440d6da7 100644
--- a/usr.sbin/sysconf/Makefile
+++ b/usr.sbin/sysconf/Makefile
@@ -6,6 +6,7 @@ MAN=		sysconf.8			\
 		sysconf-generic.8		\
 		sysconf-loader.8		\
 		sysconf-make.8			\
+		sysconf-poudriere.8		\
 		sysconf-rc.8			\
 		sysconf-src.8			\
 		sysconf-sysctl.8		\
@@ -16,6 +17,7 @@ SRCS=		sysconf.c			\
 		sysconf_edit_assign.c		\
 		sysconf_edit_make.c		\
 		sysconf_edit_write.c		\
+		sysconf_poudriere.c		\
 		sysconf_print.c			\
 		sysconf_query_desc.c		\
 		sysconf_query_sysctl.c		\
diff --git a/usr.sbin/sysconf/sysconf-poudriere.8 b/usr.sbin/sysconf/sysconf-poudriere.8
new file mode 100644
index 000000000000..a5a61fea4421
--- /dev/null
+++ b/usr.sbin/sysconf/sysconf-poudriere.8
@@ -0,0 +1,364 @@
+.\" Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
+.\" Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd September 23, 2026
+.Dt SYSCONF-POUDRIERE 8
+.Os
+.Sh NAME
+.Nm "sysconf poudriere"
+.Nd configure poudriere.conf and poudriere.d overlay files
+.Sh SYNOPSIS
+.Nm sysconf
+.Cm poudriere
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Ar sysrc_argument ...
+.Nm sysconf
+.Cm poudriere
+.Bro Cm make Ns | Ns Cm src Ns | Ns Cm src-env Brc
+.Op Fl ceFinNqsvVx
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Fl j Ar jail | Fl R Ar dir
+.Op Fl f Ar file
+.Ar name Ns Op Ns Oo +|- Oc Ns = Ns Ar value
+.Ar ...
+.Nm sysconf
+.Cm poudriere
+.Bro Cm make Ns | Ns Cm src Ns | Ns Cm src-env Brc
+.Op Fl eFnNqvV
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Fl f Ar file
+.Fl a
+.Nm sysconf
+.Cm poudriere
+.Bro Cm make Ns | Ns Cm src Ns | Ns Cm src-env Brc
+.Op Fl E
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Fl f Ar file
+.Fl l | L
+.Sh DESCRIPTION
+The
+.Cm poudriere
+target of
+.Xr sysconf 8
+reaches the same files
+.Xr poudriere 8
+sources.
+With no sub-target, it is the
+.Pa poudriere.conf
+collection, handed to
+.Xr sysrc 8
+as
+.Fl f
+files.
+The sub-targets are
+.Cm make ,
+.Cm src ,
+and
+.Cm src-env .
+Each is the matching
+.Xr make 1
+collection under
+.Pa poudriere.d ,
+handled by the same engine as
+.Xr sysconf-make 8 .
+.Pp
+A unique prefix of the
+.Cm poudriere
+keyword is accepted
+.Pq Ql p .
+A unique prefix of a sub-target is accepted the same way
+.Po
+.Ql m
+selects
+.Cm make ;
+.Ql s
+and
+.Ql sr
+are ambiguous between
+.Cm src
+and
+.Cm src-env
+.Pc .
+.Ss poudriere.conf
+.Pa poudriere.conf
+is
+.Xr rc.conf 5 Ns -style
+shell written by the administrator;
+poudriere never writes it.
+The default collection, sourced by every poudriere command, is in order:
+.Pa POUDRIERE_ETC/poudriere.conf
+if readable
+.Pq default Pa /usr/local/etc/poudriere.conf ,
+then
+.Pa POUDRIERED/poudriere.conf
+if readable and not the same file as the first
+.Pq default Pa POUDRIERE_ETC/poudriere.d/poudriere.conf .
+At least one of those two files must exist.
+Honor
+.Ev POUDRIERE_ETC
+and
+.Ev POUDRIERED
+when set.
+.Pp
+When a jail, ports tree, or set is given, the matching files are
+appended, in the order
+.Xr poudriere 8
+uses:
+.Bd -unfilled -offset indent
+.Pa POUDRIERED/<setname>-poudriere.conf
+.Pa POUDRIERED/<tree>-poudriere.conf
+.Pa POUDRIERED/<jailname>-poudriere.conf
+.Pa POUDRIERED/<tree>-<setname>-poudriere.conf
+.Pa POUDRIERED/<jailname>-<tree>-poudriere.conf
+.Pa POUDRIERED/<jailname>-<setname>-poudriere.conf
+.Pa POUDRIERED/<jailname>-<tree>-<setname>-poudriere.conf
+.Ed
+.Pp
+An explicit
+.Xr sysrc 8
+.Fl f
+replaces discovery.
+.Fl l
+and
+.Fl L
+are handled here and printed one path per line.
+For this
+.Pa poudriere.conf
+collection,
+.Fl l
+lists each readable file.
+.Fl L
+lists every location the given jail, tree, and set would consult,
+including overlays that do not exist yet.
+If no jail, tree, or set is given, the list is the two global
+.Pa poudriere.conf
+paths.
+.Fl E
+limits either list to files that exist.
+.Fl l
+and
+.Fl L
+take no variable names.
+Other arguments except the
+.Cm poudriere
+keyword itself are handed to
+.Xr sysrc 8
+verbatim.
+A bare
+.Ql --
+means the next word is a variable in this collection, even when that
+word is
+.Cm make ,
+.Cm src ,
+.Cm src-env ,
+or a unique prefix of one of those sub-targets.
+.Ss make.conf, src.conf, and src-env.conf
+These live only under
+.Pa POUDRIERED
+and use
+.Xr make 1
+syntax, so they are not mixed into the
+.Pa poudriere.conf
+sysrc collection.
+.Pp
+.Cm poudriere make
+consults, in the order shown by
+.Xr poudriere 8 :
+.Bd -unfilled -offset indent
+.Pa POUDRIERED/make.conf
+.Pa POUDRIERED/<setname>-make.conf
+.Pa POUDRIERED/<tree>-make.conf
+.Pa POUDRIERED/<jailname>-make.conf
+.Pa POUDRIERED/<tree>-<setname>-make.conf
+.Pa POUDRIERED/<jailname>-<tree>-make.conf
+.Pa POUDRIERED/<jailname>-<setname>-make.conf
+.Pa POUDRIERED/<jailname>-<tree>-<setname>-make.conf
+.Ed
+.Pp
+.Fl l
+always prints the global path, even when that file does not exist:
+.Pa POUDRIERED/make.conf
+for
+.Cm make ,
+.Pa POUDRIERED/src.conf
+for
+.Cm src ,
+and
+.Pa POUDRIERED/src-env.conf
+for
+.Cm src-env .
+An overlay is printed only when the file exists.
+.Fl L
+lists every location the given jail, tree, and set would consult,
+including files that do not exist yet.
+.Cm src
+and
+.Cm src-env
+have no per-tree file, so
+.Fl Fl tree
+does not add a path to those lists.
+.Fl E
+limits either list to files that exist, so a missing global is omitted.
+On a read, a missing global is skipped and an
+overlay is consulted only when the file exists.
+Hook-plugin
+.Pa make.conf
+files are not collected.
+.Pp
+.Cm poudriere src
+follows the shorter list
+.Xr poudriere 8
+uses when building jails
+.Pq global, then set, then jail; no tree :
+.Bd -unfilled -offset indent
+.Pa POUDRIERED/src.conf
+.Pa POUDRIERED/<setname>-src.conf
+.Pa POUDRIERED/<jailname>-src.conf
+.Ed
+.Pp
+.Cm poudriere src-env
+uses that same order, with
+.Pa src-env.conf
+in place of
+.Pa src.conf :
+.Bd -unfilled -offset indent
+.Pa POUDRIERED/src-env.conf
+.Pa POUDRIERED/<setname>-src-env.conf
+.Pa POUDRIERED/<jailname>-src-env.conf
+.Ed
+.Pp
+An explicit
+.Fl f
+replaces discovery, as on other
+.Nm sysconf
+targets.
+A name already defined is rewritten in the last file in the
+collection that defines it, so the change is not placed where a later
+overlay would mask it.
+A name defined nowhere is appended to the global file, and that write
+creates the global file when it is missing.
+A bare
+.Ar name
+reads, and
+.Ar name Ns = Ns Ar value
+writes, including
+.Ql +=
+and
+.Ql -= .
+Those rules are the
+.Dq Assignment syntax
+section of
+.Xr sysconf 8 .
+.Ss Context long options
+.Bl -tag -width indent
+.It Fl Fl jail Ar name
+Poudriere jail name, as in
+.Pa <jailname>-make.conf
+and the other jail files.
+.It Fl Fl ports Ar name , Fl Fl tree Ar name
+Poudriere ports tree.
+.Fl Fl tree
+is an alias of
+.Fl Fl ports ;
+when both are given, the last wins.
+A tree applies to
+.Pa poudriere.conf
+and the
+.Cm make
+sub-target.
+.Cm src
+and
+.Cm src-env
+ignore it; poudriere does not read a per-tree src file.
+.It Fl Fl set Ar name
+Poudriere set name.
+.El
+.Pp
+.Fl Fl jail Ns = Ns Ar name
+(and the same attached form for the others) is accepted.
+These long options are consumed here and never passed to
+.Xr sysrc 8
+or mistaken for
+.Nm sysconf
+operands.
+They are distinct from
+.Fl j
+on
+.Xr sysconf 8
+and
+.Xr sysrc 8 ,
+which name a running jail.
+.Ss Out of scope
+Poudriere maintains the following itself, or they are a different format:
+.Pp
+.Bl -tag -width indent -compact
+.It Pa poudriere.d/jails/ , poudriere.d/ports/
+One file per property; the whole file is the value.
+.It Pa poudriere.d/*-options/
+A
+.Ev PORT_DBDIR
+tree of per-port
+.Ql OPTIONS
+files, written by
+.Ql poudriere options
+.Pq Ql make config .
+.It Pa poudriere.d/*-blacklist
+Port origins, one per line
+.Pq with optional Ql @flavor ;
+.Ql #
+comments.
+Not
+.Ql name=value .
+.It Pa poudriere.d/hooks/
+Shell scripts, including plugin
+.Pa make.conf
+files under
+.Pa hooks/plugins/ .
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width POUDRIERE_ETC
+.It Ev POUDRIERE_ETC
+Directory containing
+.Pa poudriere.conf
+.Pq default Pa /usr/local/etc .
+.It Ev POUDRIERED
+Overlay directory
+.Pq default Pa POUDRIERE_ETC/poudriere.d .
+.El
+.Sh EXAMPLES
+.Dl sysconf poudriere ZPOOL=zroot
+.Dl sysconf poudriere --jail 131amd64 PARALLEL_JOBS=8
+.Dl sysconf p ZPOOL
+.Dl sysconf p -- src
+.Dl sysconf poudriere make --jail 131amd64 CFLAGS
+.Dl sysconf p m --jail 131amd64 CFLAGS+=-O2
+.Dl sysconf poudriere src --set dev WITHOUT_LLVM_BINUTILS=
+.Dl sysconf p src-env --jail 131amd64 SRC_ENV_VAR
+.Sh SEE ALSO
+.Xr poudriere 8 ,
+.Xr sysconf 8 ,
+.Xr sysconf-make 8 ,
+.Xr sysconf-rc 8 ,
+.Xr sysrc 8
+.Sh HISTORY
+The
+.Nm sysconf
+utility first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Devin Teske Aq Mt dteske@FreeBSD.org
+.An Faraz Vahedi Aq Mt kfv@FreeBSD.org
diff --git a/usr.sbin/sysconf/sysconf-targets.8 b/usr.sbin/sysconf/sysconf-targets.8
index 0b611bc5e0a7..e87992852677 100644
--- a/usr.sbin/sysconf/sysconf-targets.8
+++ b/usr.sbin/sysconf/sysconf-targets.8
@@ -3,7 +3,7 @@
 .\"
 .\" SPDX-License-Identifier: BSD-2-Clause
 .\"
-.Dd August 2, 2026
+.Dd September 22, 2026
 .Dt SYSCONF-TARGETS 8
 .Os
 .Sh NAME
@@ -24,6 +24,7 @@ Per-target file lists and syntax quirks are in
 .Xr sysconf-make 8 ,
 .Xr sysconf-src 8 ,
 .Xr sysconf-rc 8 ,
+.Xr sysconf-poudriere 8 ,
 and
 .Xr sysconf-generic 8 .
 Shared options remain in
@@ -160,6 +161,7 @@ but may differ when the value depends on conditionals or mk defaults.
 .Xr sysconf-generic 8 ,
 .Xr sysconf-loader 8 ,
 .Xr sysconf-make 8 ,
+.Xr sysconf-poudriere 8 ,
 .Xr sysconf-rc 8 ,
 .Xr sysconf-src 8 ,
 .Xr sysconf-sysctl 8
diff --git a/usr.sbin/sysconf/sysconf-targets.c b/usr.sbin/sysconf/sysconf-targets.c
index 940502d4ad02..9031b6c06748 100644
--- a/usr.sbin/sysconf/sysconf-targets.c
+++ b/usr.sbin/sysconf/sysconf-targets.c
@@ -6,7 +6,8 @@
  */
 
 /*
- * Target keyword selection: unique-prefix match, and the rc pass-through.
+ * Target keyword selection: unique-prefix match, the rc pass-through,
+ * and the poudriere target (sysrc collection or native make overlays).
  */
 
 #include "sysconf_priv.h"
@@ -20,6 +21,7 @@ static const char *const target_keywords[] = {
 	"generic",
 	"loader",
 	"make",
+	"poudriere",
 	"rc",
 	"src",
 	"sysctl",
@@ -82,31 +84,47 @@ resolve_target_keyword(const char *token)
 }
 
 /*
- * 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.
+ * Locate an operand 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). Used before any getopt(3) processing so that pass-through
+ * targets (`rc', `poudriere'; see exec_sysrc() / poudriere_dispatch())
+ * 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.
+ *
+ * Poudriere context long options (`--jail'/`--ports'/`--tree'/`--set')
+ * take an argument and must not be mistaken for a keyword.
+ *
+ * `past_dd' is for the top-level target: `--' ends options and the next
+ * word is the keyword (`sysconf -- loader'). The poudriere nested keyword
+ * passes zero: `--' ends the stem, and the following word is a name
+ * (`sysconf p -- src' reads the variable src from poudriere.conf).
  */
-static int
-find_target(int argc, char *argv[])
+int
+find_operand(int argc, char *argv[], int from, int past_dd)
 {
 	int n;
 	const char *o;
 	const char *p;
 
-	for (n = 1; n < argc; n++) {
-		if (strcmp(argv[n], "--") == 0)
+	for (n = from; n < argc; n++) {
+		if (strcmp(argv[n], "--") == 0) {
+			if (!past_dd)
+				return (-1);
 			return (n + 1 < argc ? n + 1 : -1);
+		}
 		if (argv[n][0] == '-' && argv[n][1] != '\0') {
+			if (argv[n][1] == '-') {
+				if (poudriere_longopt_p(argv[n]) &&
+				    strchr(argv[n], '=') == NULL &&
+				    n + 1 < argc)
+					n++;
+				continue;
+			}
 			for (p = argv[n] + 1; *p != '\0'; p++) {
 				o = strchr(OPTSTRING, *p);
 				if (o != NULL && o[1] == ':') {
@@ -129,6 +147,13 @@ find_target(int argc, char *argv[])
 	return (-1);
 }
 
+static int
+find_target(int argc, char *argv[])
+{
+
+	return (find_operand(argc, argv, 1, 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,
@@ -159,21 +184,28 @@ exec_sysrc(int argc, char *argv[], int skip)
 
 /*
  * 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.
+ * not return. The `poudriere' target either execs sysrc(8) or compacts
+ * argv for a native make/src/src-env overlay (`*argcp' is updated) and
+ * returns NULL so main does not treat it as a libbsdconf keyword.
+ * Unique-prefix shorthands are expanded (`r', `m', `p'). Returns the
+ * canonical keyword, or NULL when the token is unknown, absent, or
+ * handled as a poudriere overlay.
  */
 const char *
-target_resolve(int argc, char *argv[])
+target_resolve(int *argcp, char *argv[])
 {
 	const char *resolved;
 	int n;
 
-	n = find_target(argc, argv);
+	n = find_target(*argcp, 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 */
+		exec_sysrc(*argcp, argv, n); /* never returns */
+	if (resolved != NULL && strcmp(resolved, "poudriere") == 0) {
+		*argcp = poudriere_dispatch(*argcp, argv, n);
+		return (NULL);
+	}
 	return (resolved);
 }
diff --git a/usr.sbin/sysconf/sysconf.8 b/usr.sbin/sysconf/sysconf.8
index b6147bd39088..6d8428b5f72a 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 22, 2026
+.Dd September 23, 2026
 .Dt SYSCONF 8
 .Os
 .Sh NAME
@@ -38,6 +38,45 @@
 .Nm
 .Cm rc
 .Op Ar sysrc_argument ...
+.Nm
+.Cm poudriere
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Ar sysrc_argument ...
+.Nm
+.Cm poudriere
+.Bro Cm make Ns | Ns Cm src Ns | Ns Cm src-env Brc
+.Op Fl ceFinNqsvVx
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Fl j Ar jail | Fl R Ar dir
+.Op Fl f Ar file
+.Ar name Ns Op Ns Oo +|- Oc Ns = Ns Ar value
+.Ar ...
+.Nm
+.Cm poudriere
+.Bro Cm make Ns | Ns Cm src Ns | Ns Cm src-env Brc
+.Op Fl eFnNqvV
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Fl f Ar file
+.Fl a
+.Nm
+.Cm poudriere
+.Bro Cm make Ns | Ns Cm src Ns | Ns Cm src-env Brc
+.Op Fl E
+.Op Fl Fl jail Ar name
+.Op Fl Fl ports Ar name
+.Op Fl Fl tree Ar name
+.Op Fl Fl set Ar name
+.Op Fl f Ar file
+.Fl l | L
 .Sh DESCRIPTION
 The
 .Nm
@@ -259,6 +298,20 @@ Pass-through to
 .Xr sysrc 8 .
 See
 .Xr sysconf-rc 8 .
+.It Cm poudriere
+.Pa poudriere.conf
+collection via
+.Xr sysrc 8 ;
+.Cm poudriere make ,
+.Cm poudriere src ,
+and
+.Cm poudriere src-env
+for the
+.Xr make 1
+overlays under
+.Pa poudriere.d .
+See
+.Xr sysconf-poudriere 8 .
 .It Cm generic
 Foreign or unknown format;
 requires
@@ -682,6 +735,7 @@ Further examples for each target appear in
 .Xr sysconf-make 8 ,
 .Xr sysconf-src 8 ,
 .Xr sysconf-rc 8 ,
+.Xr sysconf-poudriere 8 ,
 and
 .Xr sysconf-generic 8 .
 .Sh SEE ALSO
@@ -695,6 +749,7 @@ and
 .Xr sysconf-generic 8 ,
 .Xr sysconf-loader 8 ,
 .Xr sysconf-make 8 ,
+.Xr sysconf-poudriere 8 ,
 .Xr sysconf-rc 8 ,
 .Xr sysconf-src 8 ,
 .Xr sysconf-sysctl 8 ,
diff --git a/usr.sbin/sysconf/sysconf.c b/usr.sbin/sysconf/sysconf.c
index 8e23dc7a8278..41091aa2ed58 100644
--- a/usr.sbin/sysconf/sysconf.c
+++ b/usr.sbin/sysconf/sysconf.c
@@ -171,10 +171,10 @@ main(int argc, char *argv[])
 	pgm = getprogname();
 
 	/*
-	 * Target selection, including the rc pass-through, runs while
-	 * argv is still pristine (see target_resolve()).
+	 * Target selection, including the rc and poudriere pass-throughs,
+	 * runs while argv is still pristine (see target_resolve()).
 	 */
-	resolved = target_resolve(argc, argv);
+	resolved = target_resolve(&argc, argv);
 
 	/*
 	 * Honor `--help' and `--version' wherever they appear (getopt(3)
@@ -206,8 +206,11 @@ main(int argc, char *argv[])
 	 * sysctl(8) (e.g., `sysconf sysctl -L'). getopt(3) skips over its
 	 * first argument, so hand it the target keyword's slot as the
 	 * program name.
+	 *
+	 * Nested `poudriere make|src|src-env' already stripped both keywords;
+	 * remaining argv is options plus names.
 	 */
-	if (argc > 0) {
+	if (argc > 0 && !poudriere_native()) {
 		target = resolved != NULL ? resolved : argv[0];
 		argc--;
 		argv++;
@@ -254,7 +257,10 @@ main(int argc, char *argv[])
 #endif
 
 	/* Resolve the target format and its backing files */
-	resolve_target(target);
+	if (poudriere_native())
+		poudriere_setup();
+	else
+		resolve_target(target);
 
 	/* `-k' requires a target with a module drop-in directory */
 	if (module != NULL) {
@@ -564,6 +570,7 @@ usage(void)
 	    " -l | -L\n", pgm);
 	fprintf(stderr,
 	    "       %s rc [sysrc(8) argument ...]\n", pgm);
+	poudriere_usage();
 	fprintf(stderr, "Try `%s --help' for more information.\n", pgm);
 	exit(EXIT_FAILURE);
 }
@@ -598,6 +605,7 @@ help(void)
 	    "__MAKE_CONF, SRCCONF)");
 	fprintf(stderr, TGTFMT, "rc",
 	    "pass-through: all other arguments go to sysrc(8) verbatim");
+	poudriere_help();
 	fprintf(stderr, TGTFMT, "generic",
 	    "no default files; requires -f file");
 	fprintf(stderr,
diff --git a/usr.sbin/sysconf/sysconf_poudriere.c b/usr.sbin/sysconf/sysconf_poudriere.c
new file mode 100644
index 000000000000..6e8b1e0e0295
--- /dev/null
+++ b/usr.sbin/sysconf/sysconf_poudriere.c
@@ -0,0 +1,677 @@
+/*
+ * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
+ * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * poudriere(8) target: poudriere.conf via sysrc(8), and the make(1)
+ * overlays (make.conf, src.conf, src-env.conf) under poudriere.d.
+ */
+
+#include "sysconf_priv.h"
+
+#define POUDRIERE_ETC_DEF	"/usr/local/etc"
+#define POUDRIERE_MAXFILES	16
+
+/*
+ * Nested poudriere targets (make(1) overlay files under poudriere.d).
+ * Exact match wins; otherwise a unique prefix. `s' and `sr' are
+ * ambiguous (src vs src-env); `m' uniquely selects make.
+ */
+static const char *const poudriere_subs[] = {
+	"make",
+	"src",
+	"src-env",
+	NULL
+};
+
+/* Poudriere context; filled when the poudriere target is selected. */
+static const char *poudriere_sub = NULL;	/* make, src, src-env */
+static const char *poudriere_jail = NULL;
+static const char *poudriere_tree = NULL;	/* --ports or --tree */
+static const char *poudriere_set = NULL;
+
+/*
+ * True when `arg' is `--name' or `--name=value'.
+ */
+static int
+longopt_eq(const char *arg, const char *name)
+{
+	size_t nlen;
+
+	if (arg[0] != '-' || arg[1] != '-')
+		return (0);
+	nlen = strlen(name);
+	if (strncmp(arg + 2, name, nlen) != 0)
+		return (0);
+	return (arg[2 + nlen] == '\0' || arg[2 + nlen] == '=');
+}
+
+int
+poudriere_longopt_p(const char *arg)
+{
+
+	return (longopt_eq(arg, "jail") || longopt_eq(arg, "ports") ||
+	    longopt_eq(arg, "tree") || longopt_eq(arg, "set"));
+}
+
+static void
+poudriere_add_path(char **files, unsigned int *nfiles, unsigned int max,
+    const char *path)
+{
+
+	if (*nfiles >= max)
+		errx(EXIT_FAILURE, "too many poudriere files");
+	if ((files[*nfiles] = strdup(path)) == NULL)
+		err(EXIT_FAILURE, NULL);
+	(*nfiles)++;
+}
+
+static void
+poudriere_add_if_readable(char **files, unsigned int *nfiles,
+    unsigned int max, const char *path)
+{
+
+	if (access(path, R_OK) != 0)
+		return;
+	poudriere_add_path(files, nfiles, max, path);
+}
+
+static int
+same_realpath(const char *a, const char *b)
+{
+	char ra[PATH_MAX];
+	char rb[PATH_MAX];
+
+	if (realpath(a, ra) == NULL || realpath(b, rb) == NULL)
+		return (0);
+	return (strcmp(ra, rb) == 0);
+}
+
+/*
+ * Join `dir'/`name', then prefix `-R dir' when set. `rootdir' is empty
+ * until parse_options(), so the sysrc pass-through is unprefixed.
+ */
+static void
+poudriere_join(char *dst, size_t dstsz, const char *dir, const char *name)
+{
+	char raw[PATH_MAX];
+	int n;
+
+	n = snprintf(raw, sizeof(raw), "%s/%s", dir, name);
+	if (n < 0 || (size_t)n >= sizeof(raw))
+		errx(EXIT_FAILURE, "%s/%s: %s", dir, name,
+		    strerror(ENAMETOOLONG));
+	n = snprintf(dst, dstsz, "%s%s", rootdir, raw);
+	if (n < 0 || (size_t)n >= dstsz)
+		errx(EXIT_FAILURE, "%s%s: %s", rootdir, raw,
+		    strerror(ENAMETOOLONG));
+}
+
+static void
+poudriere_etc_d(const char **etc, const char **d, char *dbuf, size_t dbufsz)
+{
+
+	*etc = getenv("POUDRIERE_ETC");
+	if (*etc == NULL || **etc == '\0')
+		*etc = POUDRIERE_ETC_DEF;
+	*d = getenv("POUDRIERED");
+	if (*d == NULL || **d == '\0') {
+		if (snprintf(dbuf, dbufsz, "%s/poudriere.d", *etc) >=
+		    (int)dbufsz)
+			errx(EXIT_FAILURE, "%s/poudriere.d: %s", *etc,
+			    strerror(ENAMETOOLONG));
+		*d = dbuf;
+	}
+}
+
+static void
+poudriere_try_named(char **files, unsigned int *nfiles, unsigned int max,
+    const char *dir, const char *stem, const char *suffix, int only_readable)
+{
+	char name[PATH_MAX];
+	char path[PATH_MAX];
+	int n;
+
+	if (stem == NULL || *stem == '\0')
+		return;
+	n = snprintf(name, sizeof(name), "%s-%s", stem, suffix);
+	if (n < 0 || (size_t)n >= sizeof(name))
+		errx(EXIT_FAILURE, "poudriere overlay name: %s",
+		    strerror(ENAMETOOLONG));
+	poudriere_join(path, sizeof(path), dir, name);
+	if (only_readable)
+		poudriere_add_if_readable(files, nfiles, max, path);
+	else
+		poudriere_add_path(files, nfiles, max, path);
+}
+
+/*
+ * Overlay stems in poudriere(8) construction order. `full' is the
+ * make.conf / poudriere.conf set (set, tree, jail, and the four combos).
+ * src.conf and src-env.conf are global plus set and jail only.
+ */
+static void
+poudriere_add_overlays(char **files, unsigned int *nfiles, unsigned int max,
+    const char *dir, const char *suffix, int full, int only_readable)
+{
+	char stem[PATH_MAX];
+	int n;
+
+	poudriere_try_named(files, nfiles, max, dir, poudriere_set, suffix,
+	    only_readable);
+	if (full)
+		poudriere_try_named(files, nfiles, max, dir, poudriere_tree,
+		    suffix, only_readable);
+	poudriere_try_named(files, nfiles, max, dir, poudriere_jail, suffix,
+	    only_readable);
+	if (!full)
+		return;
+	if (poudriere_tree != NULL && *poudriere_tree != '\0' &&
+	    poudriere_set != NULL && *poudriere_set != '\0') {
+		n = snprintf(stem, sizeof(stem), "%s-%s", poudriere_tree,
+		    poudriere_set);
+		if (n < 0 || (size_t)n >= sizeof(stem))
+			errx(EXIT_FAILURE, "poudriere overlay name: %s",
+			    strerror(ENAMETOOLONG));
+		poudriere_try_named(files, nfiles, max, dir, stem, suffix,
+		    only_readable);
+	}
+	if (poudriere_jail != NULL && *poudriere_jail != '\0' &&
+	    poudriere_tree != NULL && *poudriere_tree != '\0') {
+		n = snprintf(stem, sizeof(stem), "%s-%s", poudriere_jail,
+		    poudriere_tree);
+		if (n < 0 || (size_t)n >= sizeof(stem))
+			errx(EXIT_FAILURE, "poudriere overlay name: %s",
+			    strerror(ENAMETOOLONG));
+		poudriere_try_named(files, nfiles, max, dir, stem, suffix,
+		    only_readable);
+	}
+	if (poudriere_jail != NULL && *poudriere_jail != '\0' &&
+	    poudriere_set != NULL && *poudriere_set != '\0') {
+		n = snprintf(stem, sizeof(stem), "%s-%s", poudriere_jail,
+		    poudriere_set);
+		if (n < 0 || (size_t)n >= sizeof(stem))
+			errx(EXIT_FAILURE, "poudriere overlay name: %s",
+			    strerror(ENAMETOOLONG));
+		poudriere_try_named(files, nfiles, max, dir, stem, suffix,
+		    only_readable);
+	}
+	if (poudriere_jail != NULL && *poudriere_jail != '\0' &&
+	    poudriere_tree != NULL && *poudriere_tree != '\0' &&
+	    poudriere_set != NULL && *poudriere_set != '\0') {
+		n = snprintf(stem, sizeof(stem), "%s-%s-%s", poudriere_jail,
+		    poudriere_tree, poudriere_set);
+		if (n < 0 || (size_t)n >= sizeof(stem))
+			errx(EXIT_FAILURE, "poudriere overlay name: %s",
+			    strerror(ENAMETOOLONG));
+		poudriere_try_named(files, nfiles, max, dir, stem, suffix,
+		    only_readable);
+	}
+}
+
+/*
+ * Build the poudriere.conf collection in include_poudriere_confs() order:
+ * POUDRIERE_ETC/poudriere.conf, then POUDRIERED/poudriere.conf if distinct,
+ * then optional set/tree/jail overlays. At least one global file must exist.
+ */
+static void
+poudriere_collect_files(char **files, unsigned int *nfiles, unsigned int max,
+    int only_readable)
+{
+	char dbuf[PATH_MAX];
+	char path[PATH_MAX];
+	const char *d;
+	const char *etc;
+
+	poudriere_etc_d(&etc, &d, dbuf, sizeof(dbuf));
+
+	poudriere_join(path, sizeof(path), etc, "poudriere.conf");
+	if (only_readable)
+		poudriere_add_if_readable(files, nfiles, max, path);
+	else
+		poudriere_add_path(files, nfiles, max, path);
+
+	poudriere_join(path, sizeof(path), d, "poudriere.conf");
+	if (*nfiles == 0 || !same_realpath(files[0], path)) {
+		if (only_readable)
+			poudriere_add_if_readable(files, nfiles, max, path);
+		else
+			poudriere_add_path(files, nfiles, max, path);
+	}
*** 687 LINES SKIPPED ***