commit: 5ebd5d747ab67436708a92e87cbe1b2b42411c6f
parent 37883f2036135bd68cebd5ed1d31d3c4b834f88c
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Tue, 12 Mar 2024 00:56:50 +0100
cmd/chmod: new
Diffstat:
M | Makefile | 4 | ++++ |
A | cmd/chmod.c | 170 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 174 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -76,3 +76,7 @@ test-lib/strtodur: test-lib/strtodur.c lib/strtodur.c Makefile
cmd/df: cmd/df.c lib/humanize.c Makefile
rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
$(CC) -std=c99 $(CFLAGS) -o $@ cmd/df.c lib/humanize.c $(LDFLAGS) $(LDSTATIC)
+
+cmd/chmod: cmd/chmod.c lib/mode.c Makefile
+ rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
+ $(CC) -std=c99 $(CFLAGS) -o $@ cmd/chmod.c lib/mode.c $(LDFLAGS) $(LDSTATIC)
diff --git a/cmd/chmod.c b/cmd/chmod.c
@@ -0,0 +1,170 @@
+// utils-std: Collection of commonly available Unix tools
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "../lib/mode.h"
+
+#include <dirent.h> // fdopendir, readdir, closedir
+#include <errno.h>
+#include <fcntl.h> // AT_FDCWD
+#include <limits.h> // PATH_MAX
+#include <stdbool.h>
+#include <stdio.h> // fprintf
+#include <string.h> // strerror
+#include <sys/stat.h> // chmod, fstatat, S_ISDIR
+#include <unistd.h> // getopt
+
+static int
+do_fchmodat(int fd, char *fdname, char *mode_arg, char *path, bool recursive)
+{
+ struct stat stats;
+ int err = 0;
+
+ if(fstatat(fd, path, &stats, AT_SYMLINK_NOFOLLOW) != 0)
+ {
+ fprintf(
+ stderr, "chmod: Failed getting status for '%s/%s': %s\n", fdname, path, strerror(errno));
+ return 1;
+ }
+
+ const char *errstr = NULL;
+ mode_t mode = new_mode(mode_arg, stats.st_mode, &errstr);
+ if(errstr != NULL)
+ {
+ fprintf(stderr, "chmod: Failed parsing '%s': %s\n", mode_arg, errstr);
+ return 1;
+ }
+
+ if(fchmodat(fd, path, mode, 0) != 0)
+ {
+ fprintf(stderr,
+ "chmod: Failed setting permissions to 0%6o for '%s/%s': %s\n",
+ mode,
+ fdname,
+ path,
+ strerror(errno));
+ return 1;
+ }
+
+ if(recursive && S_ISDIR(stats.st_mode))
+ {
+ int dir = openat(fd, path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if(dir == -1)
+ {
+ fprintf(
+ stderr, "chmod: Couldn't open '%s/%s' as directory: %s\n", fdname, path, strerror(errno));
+ return 1;
+ }
+
+ DIR *dirp = fdopendir(dir);
+ if(dirp == NULL)
+ {
+ fprintf(stderr, "chmod: Couldn't get DIR entry for opened '%s/%s': %s\n", fdname, path, strerror(errno));
+ return 1;
+ }
+
+ while(true)
+ {
+ struct dirent *dp = readdir(dirp);
+ if(dp == NULL)
+ {
+ if(errno == 0) break;
+
+ fprintf(stderr, "chmod: Failed reading directory '%s/%s': %s\n", fdname, path, strerror(errno));
+ closedir(dirp);
+ return 1;
+ }
+
+ if(strcmp(dp->d_name, ".") == 0) continue;
+ if(strcmp(dp->d_name, "..") == 0) continue;
+
+ char destdir[PATH_MAX] = "";
+ if(snprintf(destdir, PATH_MAX, "%s/%s", fdname, path) < 0)
+ {
+ fprintf(stderr, "chmod: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s", fdname, path, strerror(errno));
+ err++;
+ continue;
+ }
+
+ // No depth counter for now, unlikely to be a problem
+ int ret = do_fchmodat(dir, destdir, mode_arg, dp->d_name, true);
+ if(ret != 0) return ret;
+ }
+
+ // fdopendir allocates memory for DIR, needs closedir
+ if(closedir(dirp) != 0)
+ {
+ fprintf(stderr, "chmod: Deallocating directory entry for '%s/%s' failed: %s\n", fdname, path, strerror(errno));
+ return 1;
+ }
+ }
+
+ return err;
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: chmod [-R] <mode> <file ...>\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ bool opt_R = false;
+ int c = -1;
+
+ while((c = getopt(argc, argv, ":R")) != -1)
+ {
+ switch(c)
+ {
+ case 'R':
+ opt_R = true;
+ break;
+ case ':':
+ fprintf(stderr, "chmod: Error: Missing operand for option: '-%c'\n", optopt);
+ usage();
+ return 1;
+ case '?':
+ switch(optopt)
+ {
+ case 'r':
+ case 'w':
+ case 'x':
+ case 'u':
+ case 'g':
+ case 'o':
+ case 'a':
+ fprintf(stderr,
+ "chmod: Portability Warning: Pass -- before a mode with a leading dash(-) to "
+ "separate it from options");
+ optind--;
+ goto getopt_out;
+
+ default:
+ fprintf(stderr, "chmod: Error: Unrecognised option: '-%c'\n", optopt);
+ usage();
+ return 1;
+ }
+ }
+ }
+getopt_out:
+
+ argc -= optind;
+ argv += optind;
+
+ if(argc < 2)
+ {
+ fprintf(stderr, "chmod: Expects >=2 arguments, %d given\n", argc);
+ usage();
+ return 1;
+ }
+
+ for(int i = 1; i < argc; i++)
+ {
+ int ret = do_fchmodat(AT_FDCWD, ".", argv[0], argv[i], opt_R);
+ if(ret != 0) return ret;
+ }
+}