From: Dan Nelson Date: Fri, 24 Jul 2026 05:02:05 +0000 (-0500) Subject: tar: add --mode option (compatible with gnutar) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e8154e0bfcf895a78c09d2ea32d30d116215e152;p=thirdparty%2Flibarchive.git tar: add --mode option (compatible with gnutar) --- diff --git a/CMakeLists.txt b/CMakeLists.txt index fa94a6c00..a29f3fe21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Makefile.am b/Makefile.am index 71aa15f6f..d8d52a355 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/configure.ac b/configure.ac index 2c0989732..071a1c522 100644 --- a/configure.ac +++ b/configure.ac @@ -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 index 000000000..1d4438555 --- /dev/null +++ b/libarchive_fe/lafe_setmode.c @@ -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 +#include +#ifdef HAVE_SYS_SYSCTL_H +#include +#endif + +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef SETMODE_DEBUG +#include +#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 index 000000000..efd2861d2 --- /dev/null +++ b/libarchive_fe/lafe_setmode.h @@ -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 diff --git a/tar/CMakeLists.txt b/tar/CMakeLists.txt index 396003607..7d677d3d9 100644 --- a/tar/CMakeLists.txt +++ b/tar/CMakeLists.txt @@ -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 diff --git a/tar/bsdtar.1 b/tar/bsdtar.1 index 0062789e2..414ff076b 100644 --- a/tar/bsdtar.1 +++ b/tar/bsdtar.1 @@ -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. diff --git a/tar/bsdtar.c b/tar/bsdtar.c index fbc0627a8..e91efa4be 100644 --- a/tar/bsdtar.c +++ b/tar/bsdtar.c @@ -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, diff --git a/tar/bsdtar.h b/tar/bsdtar.h index 29c67fc08..5dd8de9c3 100644 --- a/tar/bsdtar.h +++ b/tar/bsdtar.h @@ -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 *); diff --git a/tar/cmdline.c b/tar/cmdline.c index 309be312c..6495b1eea 100644 --- a/tar/cmdline.c +++ b/tar/cmdline.c @@ -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 }, diff --git a/tar/test/CMakeLists.txt b/tar/test/CMakeLists.txt index 5edb07074..eedf60ad2 100644 --- a/tar/test/CMakeLists.txt +++ b/tar/test/CMakeLists.txt @@ -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 index 000000000..bafc70343 --- /dev/null +++ b/tar/test/test_option_mode.c @@ -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 + +} diff --git a/tar/write.c b/tar/write.c index 13ff4eac7..81886a05c 100644 --- a/tar/write.c +++ b/tar/write.c @@ -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.