The branch main has been updated by des:

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

commit d94e034d504682be56fc2e9d20ac2c0fe15b70ec
Author:     Dag-Erling Smørgrav <[email protected]>
AuthorDate: 2026-07-13 15:32:41 +0000
Commit:     Dag-Erling Smørgrav <[email protected]>
CommitDate: 2026-07-13 15:32:41 +0000

    pkg: Add -j and -r options
    
    This allows pkg(7) to be used to bootstrap a jail or chroot, and to
    recognize the -j and -r options and pass them through to pkg(8) if
    already bootstrapped.
    
    Note that this does not address the issue of repository keys.  If using
    a signed package repository, you will still need to copy /usr/share/keys
    into the target environment before or after bootstrapping, or pkg will
    be unable to verify package signatures.
    
    MFC after:      1 week
    Reviewed by:    imp, bapt
    Differential Revision:  https://reviews.freebsd.org/D58165
---
 usr.sbin/pkg/Makefile |   2 +-
 usr.sbin/pkg/pkg.7    |  28 +++++++++---
 usr.sbin/pkg/pkg.c    | 116 +++++++++++++++++++++++++++++++++++++++-----------
 3 files changed, 115 insertions(+), 31 deletions(-)

diff --git a/usr.sbin/pkg/Makefile b/usr.sbin/pkg/Makefile
index 68b862826992..3b3249c538dd 100644
--- a/usr.sbin/pkg/Makefile
+++ b/usr.sbin/pkg/Makefile
@@ -27,7 +27,7 @@ MAN=  pkg.7
 
 CFLAGS+=-I${SRCTOP}/contrib/libucl/include
 .PATH: ${SRCTOP}/contrib/libucl/include
-LIBADD=        archive der fetch pkgecc ucl crypto ssl util md
+LIBADD=        archive der fetch jail pkgecc ucl crypto ssl util md
 
 CFLAGS+=-I${SRCTOP}/contrib/libder/libder
 CFLAGS+=-I${SRCTOP}/crypto/libecc/include
diff --git a/usr.sbin/pkg/pkg.7 b/usr.sbin/pkg/pkg.7
index d2246f74a3fc..9d6dc21c8472 100644
--- a/usr.sbin/pkg/pkg.7
+++ b/usr.sbin/pkg/pkg.7
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd April 29, 2025
+.Dd July 13, 2026
 .Dt PKG 7
 .Os
 .Sh NAME
@@ -31,17 +31,25 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl d
+.Op Fl j Ar jail
+.Op Fl r Ar rootdir
 .Ar command ...
 .Nm
 .Op Fl d
+.Op Fl j Ar jail
+.Op Fl r Ar rootdir
 .Cm add
 .Op Fl fy
 .Op Fl r Ar reponame
 .Ar pkg.pkg
 .Nm
+.Op Fl j Ar jail
+.Op Fl r Ar rootdir
 .Fl N
 .Nm
 .Op Fl 46d
+.Op Fl j Ar jail
+.Op Fl r Ar rootdir
 .Cm bootstrap
 .Op Fl fy
 .Op Fl r Ar reponame
@@ -61,14 +69,14 @@ will bootstrap the real
 .Xr pkg 8
 from a remote repository.
 .Bl -tag
-.It Nm Ar command ...
+.It Nm Oo Fl j Ar jail Oc Oo Fl r Ar rootdir Oc Ar command ...
 If
 .Xr pkg 8
 is not installed yet, it will be fetched, have its signature verified,
 installed, and then have the original command forwarded to it.
 If already installed, the command requested will be forwarded to the real
 .Xr pkg 8 .
-.It Nm Cm add Oo Fl fy Oc Oo Fl r Ar reponame Oc Ar pkg.pkg
+.It Nm Oo Fl j Ar jail Oc Oo Fl r Ar rootdir Oc Cm add Oo Fl fy Oc Oo Fl r Ar 
reponame Oc Ar pkg.pkg
 Install
 .Xr pkg 8
 from a local package instead of fetching from remote.
@@ -88,13 +96,13 @@ If a
 .Ar reponame
 has been specified, then the signature configuration for that repository will 
be
 used.
-.It Nm Fl N
+.It Nm Oo Fl j Ar jail Oc Oo Fl r Ar rootdir Oc Fl N
 Do not bootstrap, just determine if
 .Xr pkg 8
 is actually installed or not.
 Returns 0 and the number of packages installed
 if it is, otherwise 1.
-.It Nm Oo Fl 46 Oc Cm bootstrap Oo Fl fy Oc \
+.It Nm Oo Fl 46d Oc Oo Fl j Ar jail Oc Oo Fl r Ar rootdir Oc Cm bootstrap Oo 
Fl fy Oc \
 Oo Fl r Ar reponame Oc
 Attempt to bootstrap and do not forward anything to
 .Xr pkg 8
@@ -131,6 +139,16 @@ May be specified more than once to increase the level of 
detail.
 When specified twice,
 .Xr fetch 3
 debug output is enabled.
+.It Fl j Ar jail , Fl -jail Ar jail
+Attach to the jail designated by the name or numerical identifier
+.Ar jail .
+Note that
+.Nm
+will attach to the jail immediately upon encountering this option, so
+any paths should be relative to the root of the jail.
+.It Fl r Ar rootdir , Fl -rootdir Ar rootdir
+Operate on the system rooted at
+.Ar rootdir .
 .El
 .Sh CONFIGURATION
 Configuration varies in whether it is in a repository configuration file
diff --git a/usr.sbin/pkg/pkg.c b/usr.sbin/pkg/pkg.c
index 33a404474cf2..3621081504cc 100644
--- a/usr.sbin/pkg/pkg.c
+++ b/usr.sbin/pkg/pkg.c
@@ -28,8 +28,7 @@
  */
 
 #include <sys/param.h>
-#include <sys/queue.h>
-#include <sys/types.h>
+#include <sys/jail.h>
 #include <sys/wait.h>
 
 #include <archive.h>
@@ -41,6 +40,7 @@
 #include <fcntl.h>
 #include <fetch.h>
 #include <getopt.h>
+#include <jail.h>
 #include <libutil.h>
 #include <paths.h>
 #include <stdbool.h>
@@ -92,6 +92,8 @@ struct fingerprint {
 };
 
 static const char *bootstrap_name = "pkg.pkg";
+static const char *rootdir = NULL;
+static int rootd = -1;
 
 STAILQ_HEAD(fingerprint_list, fingerprint);
 
@@ -168,7 +170,8 @@ extract_pkg_static(int fd, char *p, int sz)
 {
        struct archive *a;
        struct archive_entry *ae;
-       char *end;
+       const char *name, *end;
+       char *rname;
        int ret, r;
 
        ret = -1;
@@ -192,11 +195,22 @@ extract_pkg_static(int fd, char *p, int sz)
 
        ae = NULL;
        while ((r = archive_read_next_header(a, &ae)) == ARCHIVE_OK) {
-               end = strrchr(archive_entry_pathname(ae), '/');
+               name = archive_entry_pathname(ae);
+               end = strrchr(name, '/');
                if (end == NULL)
                        continue;
 
                if (strcmp(end, "/pkg-static") == 0) {
+                       if (rootdir) {
+                               if (asprintf(&rname, "%s%s", rootdir, name) < 0)
+                                       err(1, NULL);
+                               archive_entry_copy_pathname(ae, rname);
+                               free(rname);
+                       }
+                       if (debug) {
+                               fprintf(stderr, "extracting %s...\n",
+                                   archive_entry_pathname(ae));
+                       }
                        r = archive_read_extract(a, ae,
                            ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM |
                            ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL |
@@ -221,28 +235,56 @@ cleanup:
 static int
 install_pkg_static(const char *path, const char *pkgpath, bool force)
 {
+       const char *argv[16];
+       int argc = 0;
        int pstat;
        pid_t pid;
 
-       switch ((pid = fork())) {
-       case -1:
+       /* assemble the command line */
+       argv[argc++] = "pkg-static";
+       if (rootdir) {
+               argv[argc++] = "-r";
+               argv[argc++] = rootdir;
+       }
+       argv[argc++] = "add";
+       if (rootdir) {
+               /*
+                * Installing into an empty directory will normally fail
+                * because none of the shared libraries required by pkg
+                * are present, but pkg-static will still work and can be
+                * used to install a packaged base, so tell pkg-static to
+                * ignore missing dependencies.
+                */
+               argv[argc++] = "-M";
+       }
+       if (force)
+               argv[argc++] = "-f";
+       argv[argc++] = pkgpath;
+       argv[argc] = NULL;
+       assert((size_t)argc < sizeof(argv) / sizeof(*argv));
+
+       if (debug) {
+               fprintf(stderr, "%s", path);
+               for (int i = 1; i < argc; i++)
+                       fprintf(stderr, " %s", argv[i]);
+               fprintf(stderr, "\n");
+       }
+
+       /* fork and exec */
+       if ((pid = fork()) < 0)
                return (-1);
-       case 0:
-               if (force)
-                       execl(path, "pkg-static", "add", "-f", pkgpath,
-                           (char *)NULL);
-               else
-                       execl(path, "pkg-static", "add", pkgpath,
-                           (char *)NULL);
+       if (pid == 0) {
+               /* child */
+               execv(path, __DECONST(char **, argv));
                _exit(1);
-       default:
-               break;
        }
 
+       /* wait for pkg-static to complete */
        while (waitpid(pid, &pstat, 0) == -1)
                if (errno != EINTR)
                        return (-1);
 
+       /* check the result */
        if (WEXITSTATUS(pstat))
                return (WEXITSTATUS(pstat));
        else if (WIFSIGNALED(pstat))
@@ -946,7 +988,7 @@ static const char non_interactive_message[] =
 
 static const char args_bootstrap_message[] =
 "Too many arguments\n"
-"Usage: pkg [-4|-6] bootstrap [-f] [-y]\n";
+"Usage: pkg [-46d] [-r rootdir] bootstrap [-f] [-y]\n";
 
 static int
 pkg_query_yes_no(void)
@@ -978,7 +1020,7 @@ bootstrap_pkg_local(const char *pkgpath, bool force)
        fd_sig = -1;
        ret = -1;
 
-       fd_pkg = open(pkgpath, O_RDONLY);
+       fd_pkg = open(pkgpath, O_RDONLY | O_CLOEXEC);
        if (fd_pkg == -1)
                err(EXIT_FAILURE, "Unable to open %s", pkgpath);
 
@@ -992,7 +1034,8 @@ bootstrap_pkg_local(const char *pkgpath, bool force)
 
                        snprintf(path, sizeof(path), "%s.sig", pkgpath);
 
-                       if ((fd_sig = open(path, O_RDONLY)) == -1) {
+                       fd_sig = open(path, O_RDONLY | O_CLOEXEC);
+                       if (fd_sig == -1) {
                                fprintf(stderr, "Signature for pkg not "
                                    "available.\n");
                                goto cleanup;
@@ -1005,7 +1048,8 @@ bootstrap_pkg_local(const char *pkgpath, bool force)
 
                        snprintf(path, sizeof(path), "%s.pubkeysig", pkgpath);
 
-                       if ((fd_sig = open(path, O_RDONLY)) == -1) {
+                       fd_sig = open(path, O_RDONLY | O_CLOEXEC);
+                       if (fd_sig == -1) {
                                fprintf(stderr, "Signature for pkg not "
                                    "available.\n");
                                goto cleanup;
@@ -1075,9 +1119,9 @@ main(int argc, char *argv[])
 {
        char pkgpath[MAXPATHLEN];
        char **original_argv;
-       const char *pkgarg, *repo_name;
+       const char *localbase, *pkgarg, *repo_name;
        bool activation_test, add_pkg, bootstrap_only, force, yes;
-       signed char ch;
+       int ch, jid;
        const char *fetchOpts;
        struct repositories *repositories;
 
@@ -1093,21 +1137,35 @@ main(int argc, char *argv[])
 
        struct option longopts[] = {
                { "debug",              no_argument,            NULL,   'd' },
+               { "jail",               required_argument,      NULL,   'j' },
                { "only-ipv4",          no_argument,            NULL,   '4' },
                { "only-ipv6",          no_argument,            NULL,   '6' },
+               { "rootdir",            required_argument,      NULL,   'r' },
                { NULL,                 0,                      NULL,   0   },
        };
 
-       snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getlocalbase());
+       localbase = getlocalbase();
+       while (localbase[0] == '/' && localbase[1] == '/')
+               localbase++;
+       snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", localbase);
 
-       while ((ch = getopt_long(argc, argv, "+:dN46", longopts, NULL)) != -1) {
+       while ((ch = getopt_long(argc, argv, "+:djNr:46", longopts, NULL)) != 
-1) {
                switch (ch) {
                case 'd':
                        debug++;
                        break;
+               case 'j':
+                       if ((jid = jail_getid(optarg)) == -1)
+                               err(1, "%s", jail_errmsg);
+                       if (jail_attach(jid) != 0)
+                               err(1, "jail_attach(%s)", optarg);
+                       break;
                case 'N':
                        activation_test = true;
                        break;
+               case 'r':
+                       rootdir = optarg;
+                       break;
                case '4':
                        fetchOpts = "4";
                        break;
@@ -1207,7 +1265,14 @@ main(int argc, char *argv[])
                }
        }
 
-       if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) {
+       if (rootdir) {
+               rootd = open(rootdir, O_DIRECTORY | O_SEARCH | O_CLOEXEC);
+               if (rootd < 0)
+                       err(1, "%s", rootdir);
+       }
+
+       if ((bootstrap_only && force) ||
+           faccessat(rootd, pkgpath + 1, X_OK, 0) == -1) {
                struct repository *repo;
                int ret = 0;
                /*
@@ -1259,7 +1324,8 @@ main(int argc, char *argv[])
                if (bootstrap_only)
                        exit(EXIT_SUCCESS);
        } else if (bootstrap_only) {
-               printf("pkg already bootstrapped at %s\n", pkgpath);
+               printf("pkg already bootstrapped at %s%s\n",
+                   rootdir ? rootdir : "", pkgpath);
                exit(EXIT_SUCCESS);
        }
 

Reply via email to