]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
tar: add --mode option (compatible with gnutar)
authorDan Nelson <dnelson_1901@yahoo.com>
Fri, 24 Jul 2026 05:02:05 +0000 (00:02 -0500)
committerDan Nelson <dnelson_1901@yahoo.com>
Sat, 1 Aug 2026 20:11:08 +0000 (15:11 -0500)
13 files changed:
CMakeLists.txt
Makefile.am
configure.ac
libarchive_fe/lafe_setmode.c [new file with mode: 0644]
libarchive_fe/lafe_setmode.h [new file with mode: 0644]
tar/CMakeLists.txt
tar/bsdtar.1
tar/bsdtar.c
tar/bsdtar.h
tar/cmdline.c
tar/test/CMakeLists.txt
tar/test/test_option_mode.c [new file with mode: 0644]
tar/write.c

index fa94a6c002e5d1e692374ac6da3f15468cc90ae0..a29f3fe21481468efe9d9dd8d6459c90fe7e30a9 100644 (file)
@@ -804,6 +804,7 @@ LA_CHECK_INCLUDE_FILE("sys/select.h" HAVE_SYS_SELECT_H)
 LA_CHECK_INCLUDE_FILE("sys/stat.h" HAVE_SYS_STAT_H)
 LA_CHECK_INCLUDE_FILE("sys/statfs.h" HAVE_SYS_STATFS_H)
 LA_CHECK_INCLUDE_FILE("sys/statvfs.h" HAVE_SYS_STATVFS_H)
+LA_CHECK_INCLUDE_FILE("sys/sysctl.h" HAVE_SYS_SYSCTL_H)
 LA_CHECK_INCLUDE_FILE("sys/sysmacros.h" HAVE_SYS_SYSMACROS_H)
 LA_CHECK_INCLUDE_FILE("sys/time.h" HAVE_SYS_TIME_H)
 LA_CHECK_INCLUDE_FILE("sys/utime.h" HAVE_SYS_UTIME_H)
index 71aa15f6fda5accc238a10cb9b332b88291f6785..d8d52a3552c7cd12bce313fdde23f3fad924f67a 100644 (file)
@@ -1215,12 +1215,14 @@ noinst_HEADERS+= \
        libarchive_fe/lafe_fnmatch.h \
        libarchive_fe/lafe_getline.h \
        libarchive_fe/lafe_platform.h \
+       libarchive_fe/lafe_setmode.h \
        libarchive_fe/line_reader.h \
        libarchive_fe/passphrase.h
 libarchive_fe_la_SOURCES= \
        libarchive_fe/lafe_err.c \
        libarchive_fe/lafe_fnmatch.c \
        libarchive_fe/lafe_getline.c \
+       libarchive_fe/lafe_setmode.c \
        libarchive_fe/line_reader.c \
        libarchive_fe/passphrase.c
 
@@ -1348,6 +1350,7 @@ bsdtar_test_SOURCES= \
        tar/test/test_option_lz4.c \
        tar/test/test_option_lzma.c \
        tar/test/test_option_lzop.c \
+       tar/test/test_option_mode.c \
        tar/test/test_option_mtime.c \
        tar/test/test_option_n.c \
        tar/test/test_option_newer_than.c \
index 2c09897325359f11169ce6e3e12a1debf22faf39..071a1c522db79c75433e0b0fd95821fdc36a72f5 100644 (file)
@@ -374,9 +374,10 @@ AC_CHECK_HEADERS([stdarg.h stdckdint.h stdint.h stdlib.h string.h])
 AC_CHECK_HEADERS([sys/acl.h sys/cdefs.h sys/ea.h sys/extattr.h])
 AC_CHECK_HEADERS([sys/ioctl.h sys/mkdev.h sys/mount.h])
 AC_CHECK_HEADERS([sys/param.h sys/poll.h sys/richacl.h])
-AC_CHECK_HEADERS([sys/select.h sys/statfs.h sys/statvfs.h sys/sysmacros.h])
-AC_CHECK_HEADERS([sys/time.h sys/utime.h sys/utsname.h sys/vfs.h sys/xattr.h])
-AC_CHECK_HEADERS([time.h unistd.h utime.h wchar.h wctype.h])
+AC_CHECK_HEADERS([sys/select.h sys/statfs.h sys/statvfs.h sys/sysctl.h])
+AC_CHECK_HEADERS([sys/sysmacros.h sys/time.h sys/utime.h sys/utsname.h])
+AC_CHECK_HEADERS([sys/vfs.h sys/xattr.h time.h unistd.h utime.h wchar.h])
+AC_CHECK_HEADERS([wctype.h])
 AC_CHECK_TYPE([suseconds_t])
 AC_CHECK_HEADERS([windows.h])
 # check windows.h first; the other headers require it.
diff --git a/libarchive_fe/lafe_setmode.c b/libarchive_fe/lafe_setmode.c
new file mode 100644 (file)
index 0000000..1d44385
--- /dev/null
@@ -0,0 +1,481 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Borman at Cray Research, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "lafe_platform.h"
+#include "archive_platform.h" /* for S_I* mode macros on windows */
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef SETMODE_DEBUG
+#include <stdio.h>
+#endif
+
+#include "lafe_setmode.h"
+
+#define        SET_LEN 6               /* initial # of bitcmd struct to malloc */
+#define        SET_LEN_INCR 4          /* # of bitcmd structs to add as needed */
+
+typedef struct bitcmd {
+       char    cmd;
+       char    cmd2;
+       mode_t  bits;
+} BITCMD;
+
+#define        CMD2_CLR        0x01
+#define        CMD2_SET        0x02
+#define        CMD2_GBITS      0x04
+#define        CMD2_OBITS      0x08
+#define        CMD2_UBITS      0x10
+
+static mode_t   get_current_umask(void);
+static BITCMD  *addcmd(BITCMD *, mode_t, mode_t, mode_t, mode_t);
+static void     compress_mode(BITCMD *);
+#ifdef SETMODE_DEBUG
+static void     dumpmode(BITCMD *);
+#endif
+
+/*
+ * Given the old mode and an array of bitcmd structures, apply the operations
+ * described in the bitcmd structures to the old mode, and return the new mode.
+ * Note that there is no '=' command; a strict assignment is just a '-' (clear
+ * bits) followed by a '+' (set bits).
+ */
+mode_t
+lafe_getmode(const void *bbox, mode_t omode)
+{
+       const BITCMD *set;
+       mode_t clrval, newmode, value;
+
+       set = (const BITCMD *)bbox;
+       newmode = omode;
+       for (value = 0;; set++)
+               switch(set->cmd) {
+               /*
+                * When copying the user, group or other bits around, we "know"
+                * where the bits are in the mode so that we can do shifts to
+                * copy them around.  If we don't use shifts, it gets real
+                * grundgy with lots of single bit checks and bit sets.
+                */
+               case 'u':
+                       value = (newmode & S_IRWXU) >> 6;
+                       goto common;
+
+               case 'g':
+                       value = (newmode & S_IRWXG) >> 3;
+                       goto common;
+
+               case 'o':
+                       value = newmode & S_IRWXO;
+common:                        if (set->cmd2 & CMD2_CLR) {
+                               clrval =
+                                   (set->cmd2 & CMD2_SET) ?  S_IRWXO : value;
+                               if (set->cmd2 & CMD2_UBITS)
+                                       newmode &= ~((clrval<<6) & set->bits);
+                               if (set->cmd2 & CMD2_GBITS)
+                                       newmode &= ~((clrval<<3) & set->bits);
+                               if (set->cmd2 & CMD2_OBITS)
+                                       newmode &= ~(clrval & set->bits);
+                       }
+                       if (set->cmd2 & CMD2_SET) {
+                               if (set->cmd2 & CMD2_UBITS)
+                                       newmode |= (value<<6) & set->bits;
+                               if (set->cmd2 & CMD2_GBITS)
+                                       newmode |= (value<<3) & set->bits;
+                               if (set->cmd2 & CMD2_OBITS)
+                                       newmode |= value & set->bits;
+                       }
+                       break;
+
+               case '+':
+                       newmode |= set->bits;
+                       break;
+
+               case '-':
+                       newmode &= ~set->bits;
+                       break;
+
+               case 'X':
+                       if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
+                               newmode |= set->bits;
+                       break;
+
+               case '\0':
+               default:
+#ifdef SETMODE_DEBUG
+                       (void)printf("getmode:%04o -> %04o\n", omode, newmode);
+#endif
+                       return (newmode);
+               }
+}
+
+#define        ADDCMD(a, b, c, d)                                              \
+       if (set >= endset) {                                            \
+               ptrdiff_t setdiff = set - saveset;                      \
+               BITCMD *newset;                                         \
+               setlen += SET_LEN_INCR;                                 \
+               newset = realloc(saveset, setlen * sizeof(BITCMD));     \
+               if (newset == NULL)                                     \
+                       goto out;                                       \
+               set = newset + setdiff;                                 \
+               saveset = newset;                                       \
+               endset = newset + (setlen - 2);                         \
+       }                                                               \
+       set = addcmd(set, (mode_t)(a), (mode_t)(b), (mode_t)(c), (d))
+
+#define        STANDARD_BITS   (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
+
+void *
+lafe_setmode(const char *p)
+{
+       int serrno;
+       char op, *ep;
+       BITCMD *set, *saveset, *endset;
+       mode_t mask, perm, permXbits, who;
+       long perml;
+       int equalopdone;
+       unsigned int setlen;
+
+       if (!*p) {
+               errno = EINVAL;
+               return (NULL);
+       }
+
+       /*
+        * Get a copy of the mask for the permissions that are mask relative.
+        * Flip the bits, we want what's not set.
+        */
+       mask = ~get_current_umask();
+
+       setlen = SET_LEN + 2;
+
+       if ((set = malloc(setlen * sizeof(BITCMD))) == NULL)
+               return (NULL);
+       saveset = set;
+       endset = set + (setlen - 2);
+
+       /*
+        * If an absolute number, get it and return; disallow non-octal digits
+        * or illegal bits.
+        */
+       if (isdigit((unsigned char)*p)) {
+               errno = 0;
+               perml = strtol(p, &ep, 8);
+               if (*ep) {
+                       errno = EINVAL;
+                       goto out;
+               }
+               if (errno == ERANGE && (perml == LONG_MAX || perml == LONG_MIN))
+                       goto out;
+               if (perml & ~(STANDARD_BITS|S_ISVTX)) {
+                       errno = EINVAL;
+                       goto out;
+               }
+               perm = (mode_t)perml;
+               ADDCMD('=', (STANDARD_BITS|S_ISVTX), perm, mask);
+               set->cmd = 0;
+               return (saveset);
+       }
+
+       /*
+        * Build list of structures to set/clear/copy bits as described by
+        * each clause of the symbolic mode.
+        */
+       equalopdone = 0;
+       for (;;) {
+               /* First, find out which bits might be modified. */
+               for (who = 0;; ++p) {
+                       switch (*p) {
+                       case 'a':
+                               who |= STANDARD_BITS;
+                               break;
+                       case 'u':
+                               who |= S_ISUID|S_IRWXU;
+                               break;
+                       case 'g':
+                               who |= S_ISGID|S_IRWXG;
+                               break;
+                       case 'o':
+                               who |= S_IRWXO;
+                               break;
+                       default:
+                               goto getop;
+                       }
+               }
+
+getop:         if ((op = *p++) != '+' && op != '-' && op != '=') {
+                       errno = EINVAL;
+                       goto out;
+               }
+               if (op == '=')
+                       equalopdone = 0;
+
+               who &= ~S_ISVTX;
+               for (perm = 0, permXbits = 0;; ++p) {
+                       switch (*p) {
+                       case 'r':
+                               perm |= S_IRUSR|S_IRGRP|S_IROTH;
+                               break;
+                       case 's':
+                               /* If only "other" bits ignore set-id. */
+                               if (!who || who & ~S_IRWXO)
+                                       perm |= S_ISUID|S_ISGID;
+                               break;
+                       case 't':
+                               /* If only "other" bits ignore sticky. */
+                               if (!who || who & ~S_IRWXO) {
+                                       who |= S_ISVTX;
+                                       perm |= S_ISVTX;
+                               }
+                               break;
+                       case 'w':
+                               perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+                               break;
+                       case 'X':
+                               permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
+                               break;
+                       case 'x':
+                               perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+                               break;
+                       case 'u':
+                       case 'g':
+                       case 'o':
+                               /*
+                                * When ever we hit 'u', 'g', or 'o', we have
+                                * to flush out any partial mode that we have,
+                                * and then do the copying of the mode bits.
+                                */
+                               if (perm) {
+                                       ADDCMD(op, who, perm, mask);
+                                       perm = 0;
+                               }
+                               if (op == '=')
+                                       equalopdone = 1;
+                               if (op == '+' && permXbits) {
+                                       ADDCMD('X', who, permXbits, mask);
+                                       permXbits = 0;
+                               }
+                               ADDCMD(*p, who, op, mask);
+                               break;
+
+                       default:
+                               /*
+                                * Add any permissions that we haven't already
+                                * done.
+                                */
+                               if (perm || (op == '=' && !equalopdone)) {
+                                       if (op == '=')
+                                               equalopdone = 1;
+                                       ADDCMD(op, who, perm, mask);
+                                       perm = 0;
+                               }
+                               if (permXbits) {
+                                       ADDCMD('X', who, permXbits, mask);
+                                       permXbits = 0;
+                               }
+                               goto apply;
+                       }
+               }
+
+apply:         if (!*p)
+                       break;
+               if (*p != ',')
+                       goto getop;
+               ++p;
+       }
+       set->cmd = 0;
+#ifdef SETMODE_DEBUG
+       (void)printf("Before compress_mode()\n");
+       dumpmode(saveset);
+#endif
+       compress_mode(saveset);
+#ifdef SETMODE_DEBUG
+       (void)printf("After compress_mode()\n");
+       dumpmode(saveset);
+#endif
+       return (saveset);
+out:
+       serrno = errno;
+       free(saveset);
+       errno = serrno;
+       return NULL;
+}
+
+static mode_t
+get_current_umask(void)
+{
+       mode_t mask;
+#ifdef KERN_PROC_UMASK
+       size_t len;
+       u_short smask;
+#endif
+
+#ifdef KERN_PROC_UMASK
+       /*
+        * First try requesting the umask without temporarily modifying it.
+        * Note that this does not work if the sysctl
+        * security.bsd.unprivileged_proc_debug is set to 0.
+        */
+       len = sizeof(smask);
+       if (sysctl((int[4]){ CTL_KERN, KERN_PROC, KERN_PROC_UMASK, 0 },
+           4, &smask, &len, NULL, 0) == 0)
+               return (smask);
+#endif
+       umask(mask = umask(0));
+       return (mask);
+}
+
+static BITCMD *
+addcmd(BITCMD *set, mode_t op, mode_t who, mode_t oparg, mode_t mask)
+{
+       switch (op) {
+       case '=':
+               set->cmd = '-';
+               set->bits = who ? who : STANDARD_BITS;
+               set++;
+
+               op = '+';
+               /* FALLTHROUGH */
+       case '+':
+       case '-':
+       case 'X':
+               set->cmd = op;
+               set->bits = (who ? who : mask) & oparg;
+               break;
+
+       case 'u':
+       case 'g':
+       case 'o':
+               set->cmd = op;
+               if (who) {
+                       set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
+                                   ((who & S_IRGRP) ? CMD2_GBITS : 0) |
+                                   ((who & S_IROTH) ? CMD2_OBITS : 0);
+                       set->bits = (mode_t)~0;
+               } else {
+                       set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
+                       set->bits = mask;
+               }
+
+               if (oparg == '+')
+                       set->cmd2 |= CMD2_SET;
+               else if (oparg == '-')
+                       set->cmd2 |= CMD2_CLR;
+               else if (oparg == '=')
+                       set->cmd2 |= CMD2_SET|CMD2_CLR;
+               break;
+       }
+       return (set + 1);
+}
+
+#ifdef SETMODE_DEBUG
+static void
+dumpmode(BITCMD *set)
+{
+       for (; set->cmd; ++set)
+               (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
+                   set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
+                   set->cmd2 & CMD2_CLR ? " CLR" : "",
+                   set->cmd2 & CMD2_SET ? " SET" : "",
+                   set->cmd2 & CMD2_UBITS ? " UBITS" : "",
+                   set->cmd2 & CMD2_GBITS ? " GBITS" : "",
+                   set->cmd2 & CMD2_OBITS ? " OBITS" : "");
+}
+#endif
+
+/*
+ * Given an array of bitcmd structures, compress by compacting consecutive
+ * '+', '-' and 'X' commands into at most 3 commands, one of each.  The 'u',
+ * 'g' and 'o' commands continue to be separate.  They could probably be
+ * compacted, but it's not worth the effort.
+ */
+static void
+compress_mode(BITCMD *set)
+{
+       BITCMD *nset;
+       int setbits, clrbits, Xbits, op;
+
+       for (nset = set;;) {
+               /* Copy over any 'u', 'g' and 'o' commands. */
+               while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
+                       *set++ = *nset++;
+                       if (!op)
+                               return;
+               }
+
+               for (setbits = clrbits = Xbits = 0;; nset++) {
+                       if ((op = nset->cmd) == '-') {
+                               clrbits |= nset->bits;
+                               setbits &= ~nset->bits;
+                               Xbits &= ~nset->bits;
+                       } else if (op == '+') {
+                               setbits |= nset->bits;
+                               clrbits &= ~nset->bits;
+                               Xbits &= ~nset->bits;
+                       } else if (op == 'X')
+                               Xbits |= nset->bits & ~setbits;
+                       else
+                               break;
+               }
+               if (clrbits) {
+                       set->cmd = '-';
+                       set->cmd2 = 0;
+                       set->bits = clrbits;
+                       set++;
+               }
+               if (setbits) {
+                       set->cmd = '+';
+                       set->cmd2 = 0;
+                       set->bits = setbits;
+                       set++;
+               }
+               if (Xbits) {
+                       set->cmd = 'X';
+                       set->cmd2 = 0;
+                       set->bits = Xbits;
+                       set++;
+               }
+       }
+}
diff --git a/libarchive_fe/lafe_setmode.h b/libarchive_fe/lafe_setmode.h
new file mode 100644 (file)
index 0000000..efd2861
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Borman at Cray Research, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef LAFE_SETMODE_H_INCLUDED
+#define LAFE_SETMODE_H_INCLUDED
+
+mode_t lafe_getmode(const void *set, mode_t mode);
+void *lafe_setmode(const char *mode_str);
+
+#endif
index 39600360776bbd31f9a481287646e117e08d7f7f..7d677d3d9a246288200df47daeb71eb780b78823 100644 (file)
@@ -21,6 +21,8 @@ IF(ENABLE_TAR)
     ../libarchive_fe/lafe_err.c
     ../libarchive_fe/lafe_err.h
     ../libarchive_fe/lafe_platform.h
+    ../libarchive_fe/lafe_setmode.c
+    ../libarchive_fe/lafe_setmode.h
     ../libarchive_fe/line_reader.c
     ../libarchive_fe/line_reader.h
     ../libarchive_fe/passphrase.c
index 0062789e27e961c89940cbe63608336840a97e3e..414ff076bd306acc011b712e6862f8efff5ba3d7 100644 (file)
@@ -421,6 +421,13 @@ is run in x mode as root.
 Currently supported only for pax formats
 .Po including "pax restricted", the default tar format for
 .Nm bsdtar Pc
+.It Fl Fl mode Ar permissions
+(c, r, u modes only)
+Modify the file mode bits stored in the archive.
+Permissions can be absolute (an octal number) or symbolic.
+See
+.Xr chmod 1
+for more information and examples of both types.
 .It Fl Fl mtime Ar date
 (c, r, u modes only)
 Set the modification times of added files to the specified date.
index fbc0627a83b9c657fda953faf5bfab47892ea208..e91efa4bec5234df770ad72e7a6e9d5433c69a32 100644 (file)
@@ -56,6 +56,7 @@
 
 #include "bsdtar.h"
 #include "lafe_err.h"
+#include "lafe_setmode.h"
 
 #if ARCHIVE_VERSION_NUMBER < 4000000 && !defined(_PATH_DEFTAPE)
 // Libarchive 4.0 and later will NOT define _PATH_DEFTAPE
@@ -512,6 +513,14 @@ main(int argc, char **argv)
                        bsdtar->extract_flags |= ARCHIVE_EXTRACT_MAC_METADATA;
                        bsdtar->flags |= OPTFLAG_MAC_METADATA;
                        break;
+               case OPTION_MODE: /* GNU tar */
+                       free(bsdtar->file_mode);
+                       bsdtar->file_mode = lafe_setmode(bsdtar->argument);
+                       if (bsdtar->file_mode == NULL)
+                               lafe_errc(1, 0,
+                                       "Invalid file mode: %s",
+                                       bsdtar->argument);
+                       break;
                case 'n': /* GNU tar */
                        bsdtar->flags |= OPTFLAG_NO_SUBDIRS;
                        break;
@@ -1023,6 +1032,7 @@ main(int argc, char **argv)
 #endif
        cset_free(bsdtar->cset);
        passphrase_free(bsdtar->ppbuff);
+       free(bsdtar->file_mode);
 
        if (bsdtar->return_value != 0)
                lafe_warnc(0,
index 29c67fc08295b38b3d037ddb6392eb9b4899173f..5dd8de9c3256e6ec9e472d9c405031355962728c 100644 (file)
@@ -47,6 +47,7 @@ struct bsdtar {
        const char       *gname; /* --gname */
        int               uid;  /* --uid */
        const char       *uname; /* --uname */
+       void             *file_mode; /* --mode */
        const char       *passphrase; /* --passphrase */
        int               mode; /* Program mode: 'c', 't', 'r', 'u', 'x' */
        char              symlink_mode; /* H or L, per BSD conventions */
@@ -186,6 +187,7 @@ enum {
        OPTION_ZSTD,
        OPTION_MTIME,
        OPTION_CLAMP_MTIME,
+       OPTION_MODE,
 };
 
 int    bsdtar_getopt(struct bsdtar *);
index 309be312c94818bd8801efe40f6c01198576edd6..6495b1eea675b611643c1b2b0b843f3bf44ef1be 100644 (file)
@@ -95,6 +95,7 @@ static const struct bsdtar_option {
        { "lzma",                 0, OPTION_LZMA },
        { "lzop",                 0, OPTION_LZOP },
        { "mac-metadata",         0, OPTION_MAC_METADATA },
+       { "mode",                 1, OPTION_MODE },
        { "modification-time",    0, 'm' },
        { "mtime",                1, OPTION_MTIME },
        { "newer",                1, OPTION_NEWER_CTIME },
index 5edb07074b92be93b08c85b72edb611cc9a35a8d..eedf60ad2c8e7ca68a3c734197f9e24de2d09d1a 100644 (file)
@@ -59,6 +59,7 @@ IF(ENABLE_TAR AND ENABLE_TEST)
     test_option_lz4.c
     test_option_lzma.c
     test_option_lzop.c
+    test_option_mode.c
     test_option_mtime.c
     test_option_n.c
     test_option_newer_than.c
diff --git a/tar/test/test_option_mode.c b/tar/test/test_option_mode.c
new file mode 100644 (file)
index 0000000..bafc703
--- /dev/null
@@ -0,0 +1,56 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Dan Nelson
+ * All rights reserved.
+ */
+#include "test.h"
+
+DEFINE_TEST(test_option_mode)
+{
+       int rv;
+       char *p;
+
+       assertMakeDir("in", 0755);
+
+       /* Test invalid modes */
+       rv = systemf("%s --mode 8 -cf archive.tar in > 1.out 2> 1.err",
+               testprog);
+       assert(rv != 0);
+
+       rv = systemf("%s --mode a+a -cf archive.tar in > 2.out 2> 2.err",
+               testprog);
+       assert(rv != 0);
+
+       /* Create some files with different modes */
+       assertMakeFile("in/all", 0777, "");
+       assertMakeFile("in/minimal", 0500, "");
+
+       /* Archive and override using an absolute mode */
+       assertEqualInt(0,
+               systemf("%s --mode 644 -cf archive1.tar "
+                       "in/all in/minimal", testprog));
+
+       /* Verify the modes */
+       p = slurpfile(NULL, "archive1.tar");
+       assertEqualString(p + 100,"000644 ");
+       assertEqualString(p + 612,"000644 ");
+       free(p);
+
+/* Skip relative symbolic mode checks on Windows; on-disk files always have
+ * mode 644.
+ */
+#if !defined(_WIN32) || defined(__CYGWIN__)
+       /* Archive and override using a symbolic mode */
+       assertEqualInt(0,
+               systemf("%s --mode u+rw-x,g+X,o-w -cf archive2.tar "
+                       "in/all in/minimal", testprog));
+
+       /* Verify the modes */
+       p = slurpfile(NULL, "archive2.tar");
+       assertEqualString(p + 100,"000675 ");
+       assertEqualString(p + 612,"000610 ");
+       free(p);
+#endif
+
+}
index 13ff4eac766e1a8efa6ba41b2a4e2fd5b1a0ad8f..81886a05c6a65a6a8825f8a6ecdbfbd09a2f4985 100644 (file)
@@ -59,6 +59,7 @@
 
 #include "bsdtar.h"
 #include "lafe_err.h"
+#include "lafe_setmode.h"
 #include "line_reader.h"
 
 #ifndef O_BINARY
@@ -942,6 +943,12 @@ write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path)
                if (bsdtar->gname)
                        archive_entry_set_gname(entry, bsdtar->gname);
 
+               if (bsdtar->file_mode) {
+                       mode_t m = archive_entry_mode(entry);
+                       m = lafe_getmode(bsdtar->file_mode, m);
+                       archive_entry_set_mode(entry, m);
+               }
+
                /*
                 * Rewrite the pathname to be archived.  If rewrite
                 * fails, skip the entry.