logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 76fc87fe150c9d2c9aa2a4a3dea29891ceb32bd3
parent 5342b24f9fb82f1c77c50e5bd1a079d65ae4eec5
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Thu, 23 Jan 2025 11:24:02 +0100

cmd/chmod: add -F/--reference option

Diffstat:

Mcmd/chmod.120+++++++++++++++++++-
Mcmd/chmod.c181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtest-cmd/chmod.sh9++++++++-
3 files changed, 204 insertions(+), 6 deletions(-)

diff --git a/cmd/chmod.1 b/cmd/chmod.1 @@ -12,12 +12,18 @@ .Op Fl cRv .Ar mode .Ar file... +.Nm +.Op Fl cRv +.Fl F Ar ref_file +.Ar file... .Sh DESCRIPTION .Nm sets the permissions bits given by .Ar mode +or copying permissions from +.Ar ref_file on each given -.Ar file . +.Ar file , .Pp .Ar mode can be either an octal natural number between 0 and 7777, or a symbolic operation like @@ -40,6 +46,11 @@ like so: .Bl -tag -width Ds .It Fl c Print mode changes +.It Fl F Ar ref_file +Copy permission bits from +.Ar ref_file +into each +.Ar file . .It Fl R Recurse into directories passed to .It Fl v @@ -153,5 +164,12 @@ The and .Fl v options are present for compatibility with other modern systems such as BusyBox and GNU coreutils. +Similarly the +.Fl F +option was created for compatibility with +.Fl -reference +from GNU, BusyBox and +.Nx +while keeping the possibility of using short options. .Sh AUTHORS .An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me diff --git a/cmd/chmod.c b/cmd/chmod.c @@ -173,16 +173,156 @@ do_fchmodat(int fd, char *mode_arg, char *name, char *acc_path, bool recursive) return err; } +static int +copy_mode(int fd, mode_t mode, char *name, char *acc_path, bool recursive) +{ + struct stat stats; + int err = 0; + + if(fstatat(fd, name, &stats, AT_SYMLINK_NOFOLLOW) != 0) + { + fprintf(stderr, + "%s: error: Failed getting status for '%s': %s\n", + argv0, + acc_path, + strerror(errno)); + errno = 0; + return 1; + } + + char mode_from[11] = ""; + symbolize_mode(stats.st_mode, mode_from); + + if(mode != stats.st_mode) + { + if(fchmodat(fd, name, mode, 0) != 0) + { + fprintf(stderr, + "%s: error: Failed setting permissions to 0%04o for '%s': %s\n", + argv0, + mode, + acc_path, + strerror(errno)); + errno = 0; + return 1; + } + + if(opt_c || opt_v) + { + char mode_to[11] = ""; + symbolize_mode(mode, mode_to); + + printf("%s: Permissions changed from 0%04o/%s to 0%04o/%s for '%s'\n", + argv0, + stats.st_mode & 07777, + mode_from, + mode & 07777, + mode_to, + acc_path); + } + } + else if(opt_v) + printf("%s: Permissions already set to 0%04o/%s for '%s'\n", + argv0, + stats.st_mode & 07777, + mode_from, + acc_path); + + if(recursive && S_ISDIR(stats.st_mode)) + { + int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if(dir == -1) + { + fprintf(stderr, + "%s: error: Couldn't open '%s' as directory: %s\n", + argv0, + acc_path, + strerror(errno)); + errno = 0; + return 1; + } + + DIR *dirp = fdopendir(dir); + if(dirp == NULL) + { + fprintf(stderr, + "%s: error: Couldn't get DIR entry for opened '%s': %s\n", + argv0, + acc_path, + strerror(errno)); + errno = 0; + return 1; + } + + while(true) + { + struct dirent *dp = readdir(dirp); + if(dp == NULL) + { + if(errno == 0) break; + + fprintf(stderr, + "%s: error: Failed reading directory '%s': %s\n", + argv0, + acc_path, + strerror(errno)); + closedir(dirp); // FIXME: unhandled error + errno = 0; + return 1; + } + + if(strcmp(dp->d_name, ".") == 0) continue; + if(strcmp(dp->d_name, "..") == 0) continue; + + char new_path[PATH_MAX] = ""; + if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0) + { + fprintf( + stderr, + "%s: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s\n", + argv0, + name, + acc_path, + strerror(errno)); + err++; + errno = 0; + continue; + } + + // No depth counter for now, unlikely to be a problem + int ret = copy_mode(dir, mode, dp->d_name, new_path, true); + if(ret != 0) return ret; + } + + // fdopendir allocates memory for DIR, needs closedir + if(closedir(dirp) != 0) + { + fprintf(stderr, + "%s: error: Deallocating directory entry for '%s' failed: %s\n", + argv0, + acc_path, + strerror(errno)); + errno = 0; + return 1; + } + } + + return err; +} + static void usage(void) { - fprintf(stderr, "Usage: chmod [-cRv] <mode> <file ...>\n"); + fprintf(stderr, "\ +Usage: chmod [-cRv] <mode> <file...>\n\ + chmod [-cRv] -F <ref_file> <file...>\n"); } int main(int argc, char *argv[]) { bool opt_R = false; + char *ref_file = NULL; #ifdef HAS_GETOPT_LONG // Strictly for GNUisms compatibility so no long-only options @@ -190,6 +330,7 @@ main(int argc, char *argv[]) static struct option opts[] = { {"changes", no_argument, 0, 'c'}, {"recursive", no_argument, 0, 'R'}, + {"reference", required_argument, 0, 'F'}, {"verbose", no_argument, 0, 'v'}, {0, 0, 0, 0}, }; @@ -211,9 +352,9 @@ main(int argc, char *argv[]) #ifdef HAS_GETOPT_LONG // Need + as first character to get POSIX-style option parsing - c = getopt_long(argc, argv, "+:cRv", opts, NULL); + c = getopt_long(argc, argv, "+:cF:Rv", opts, NULL); #else - c = getopt(argc, argv, ":cRv"); + c = getopt(argc, argv, ":cF:Rv"); #endif if(c == -1) break; @@ -222,6 +363,9 @@ main(int argc, char *argv[]) case 'c': // GNU opt_c = true; break; + case 'F': // GNU & NetBSD for --reference, utils-std for -F + ref_file = optarg; + break; case 'R': // POSIX opt_R = true; break; @@ -242,9 +386,38 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; + if(ref_file != NULL) + { + if(argc < 1) + { + fprintf(stderr, "%s: error: Expected >=1 arguments, %d given\n", argv0, argc); + usage(); + return 1; + } + + struct stat ref_stat; + if(stat(ref_file, &ref_stat) != 0) + { + fprintf(stderr, + "%s: error: Failed to get status from reference file '%s': %s\n", + argv0, + ref_file, + strerror(errno)); + return 1; + } + + for(int i = 0; i < argc; i++) + { + int ret = copy_mode(AT_FDCWD, ref_stat.st_mode, argv[i], argv[i], opt_R); + if(ret != 0) return ret; + } + + return 0; + } + if(argc < 2) { - fprintf(stderr, "%s: error: Expects >=2 arguments, %d given\n", argv0, argc); + fprintf(stderr, "%s: error: Expected >=2 arguments, %d given\n", argv0, argc); usage(); return 1; } diff --git a/test-cmd/chmod.sh b/test-cmd/chmod.sh @@ -3,12 +3,14 @@ # SPDX-License-Identifier: MPL-2.0 target="$(dirname "$0")/../cmd/chmod" -plans=11 +plans=13 . "$(dirname "$0")/tap.sh" tmpfile="${TMPDIR-/tmp}/test_chmod_$(date +%s)" +ref_file="${TMPDIR-/tmp}/test_chmod_$(date +%s).ref" touch "$tmpfile" || exit 1 +touch "$ref_file" || exit 1 t '0' "-v 0 $tmpfile" "chmod: Permissions changed from 00644/-rw-r--r-- to 00000/---------- for '${tmpfile}' " @@ -42,4 +44,9 @@ t '__mode:-x,+w' "-v -- -x,+w $tmpfile" "chmod: Permissions changed from 00133/- t '__mode:-w' "-v -- -w $tmpfile" "chmod: Permissions changed from 00222/--w--w--w- to 00022/-----w--w- for '${tmpfile}' " +t 'ref_file' "-v -F $ref_file $tmpfile" "chmod: Permissions changed from 00022/-----w--w- to 00644/-rw-r--r-- for '${tmpfile}' +" +t 'ref_file:repeat' "-v -F $ref_file $tmpfile" "chmod: Permissions already set to 00644/-rw-r--r-- for '${tmpfile}' +" + rm -f "$tmpfile" || exit 1