logo

utils-std

Collection of commonly available Unix tools
commit: 5ebd5d747ab67436708a92e87cbe1b2b42411c6f
parent 37883f2036135bd68cebd5ed1d31d3c4b834f88c
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Tue, 12 Mar 2024 00:56:50 +0100

cmd/chmod: new

Diffstat:

MMakefile4++++
Acmd/chmod.c170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; + } +}