chown.c (7010B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- // NetBSD <10 hides fdopendir behind _NETBSD_SOURCE
- #if __NetBSD_Version__ < 1000000000
- #define _NETBSD_SOURCE
- #endif
- #include "../lib/fs.h"
- #include "../lib/user_group_parse.h"
- #include <dirent.h> // fdopendir, readdir, closedir
- #include <errno.h>
- #include <fcntl.h> // AT_FDCWD, fchownat
- #include <limits.h> // PATH_MAX
- #include <stdbool.h>
- #include <stdio.h> // fprintf
- #include <stdlib.h> // abort, exit
- #include <string.h> // strerror, strcmp
- #include <sys/stat.h> // fstatat, S_ISDIR
- #include <unistd.h> // getopt
- #ifdef HAS_GETOPT_LONG
- #include <getopt.h>
- #endif
- const char *argv0 = NULL;
- static uid_t user = (uid_t)-1;
- static uid_t group = (uid_t)-1;
- static bool opt_v = false, opt_R = false;
- enum chown_follow_symlinks
- {
- CHOWN_FOLLOW_UNK_SYMLINKS,
- CHOWN_FOLLOW_ALL_SYMLINKS,
- CHOWN_FOLLOW_NO_SYMLINK,
- CHOWN_FOLLOW_ONE_SYMLINK,
- };
- static int
- do_fchownat(int fd, char *name, char *acc_path, enum chown_follow_symlinks follow_symlinks)
- {
- struct stat stats;
- int err = 0;
- int stat_opts = (follow_symlinks == CHOWN_FOLLOW_NO_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
- int chown_opts = (follow_symlinks == CHOWN_FOLLOW_NO_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
- if(fstatat(fd, name, &stats, stat_opts) != 0)
- {
- fprintf(stderr,
- "%s: error: Failed getting status for '%s': %s\n",
- argv0,
- acc_path,
- strerror(errno));
- errno = 0;
- return 1;
- }
- bool change = false;
- if(user != (uid_t)-1 && stats.st_uid != user) change = true;
- if(group != (uid_t)-1 && stats.st_gid != group) change = true;
- if(change)
- {
- if(fchownat(fd, name, user, group, chown_opts) != 0)
- {
- fprintf(stderr,
- "%s: error: Failed setting ownership to '%d:%d' for '%s': %s\n",
- argv0,
- user,
- group,
- acc_path,
- strerror(errno));
- errno = 0;
- return 1;
- }
- if(opt_v)
- printf("%s: Ownership changed from %d:%d to %d:%d for '%s'\n",
- argv0,
- stats.st_uid,
- stats.st_gid,
- user,
- group,
- acc_path);
- }
- else
- {
- if(opt_v)
- printf("%s: Ownership already set to %d:%d for '%s'\n",
- argv0,
- stats.st_uid,
- stats.st_gid,
- acc_path);
- }
- if(opt_R && 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",
- argv0,
- name,
- acc_path,
- strerror(errno));
- err++;
- errno = 0;
- continue;
- }
- enum chown_follow_symlinks child_follow_symlinks =
- (follow_symlinks == CHOWN_FOLLOW_ONE_SYMLINK) ? CHOWN_FOLLOW_NO_SYMLINK : follow_symlinks;
- // No depth counter for now, unlikely to be a problem as symlinks aren't followed
- int ret = do_fchownat(dir, dp->d_name, new_path, child_follow_symlinks);
- 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)
- {
- if(strcmp(argv0, "chown") == 0)
- fprintf(stderr, "Usage: %s [-h | -R [-H|-L|-P]] owner[:group] file...\n", argv0);
- else if(strcmp(argv0, "chgrp") == 0)
- fprintf(stderr, "Usage: %s [-h | -R [-H|-L|-P]] group file...\n", argv0);
- else
- {
- fprintf(
- stderr, "%s: error: Unknown utility '%s' expected either chown or chgrp\n", argv0, argv0);
- exit(1);
- }
- }
- int
- main(int argc, char *argv[])
- {
- argv0 = static_basename(argv[0]);
- enum chown_follow_symlinks follow_symlinks = CHOWN_FOLLOW_UNK_SYMLINKS;
- int c = -1;
- #ifdef HAS_GETOPT_LONG
- // Strictly for GNUisms compatibility so no long-only options
- // clang-format off
- static struct option opts[] = {
- {"no-dereference", no_argument, 0, 'h'},
- {"recursive", no_argument, 0, 'R'},
- {"verbose", no_argument, 0, 'v'},
- {0, 0, 0, 0},
- };
- // clang-format on
- // Need + as first character to get POSIX-style option parsing
- while((c = getopt_long(argc, argv, "+:hRHLPv", opts, NULL)) != -1)
- #else
- while((c = getopt(argc, argv, ":hRHLPv")) != -1)
- #endif
- {
- switch(c)
- {
- case 'h':
- follow_symlinks = CHOWN_FOLLOW_NO_SYMLINK;
- break;
- case 'R':
- opt_R = true;
- break;
- case 'H':
- follow_symlinks = CHOWN_FOLLOW_ONE_SYMLINK;
- break;
- case 'L':
- follow_symlinks = CHOWN_FOLLOW_ALL_SYMLINKS;
- break;
- case 'P':
- follow_symlinks = CHOWN_FOLLOW_NO_SYMLINK;
- break;
- case 'v':
- opt_v = true;
- break;
- case ':':
- fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
- usage();
- return 1;
- case '?':
- fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
- usage();
- return 1;
- default:
- abort();
- }
- }
- argc -= optind;
- argv += optind;
- if(follow_symlinks == CHOWN_FOLLOW_UNK_SYMLINKS)
- follow_symlinks = opt_R ? CHOWN_FOLLOW_NO_SYMLINK : CHOWN_FOLLOW_ALL_SYMLINKS;
- if(argc < 2)
- {
- fprintf(stderr, "%s: error: Expects >=2 arguments, %d given\n", argv0, argc);
- usage();
- return 1;
- }
- if(strcmp(argv0, "chown") == 0)
- {
- char *gname = strchr(argv[0], ':');
- if(gname != NULL)
- {
- gname[0] = 0;
- gname++;
- if(parse_group(gname, &group) < 0) return 1;
- }
- if(parse_user(argv[0], &user) < 0) return 1;
- }
- else if(strcmp(argv0, "chgrp") == 0)
- {
- if(parse_group(argv[0], &group) < 0) return 1;
- }
- else
- {
- fprintf(
- stderr, "%s: error: Unknown utility '%s' expected either chown or chgrp\n", argv0, argv0);
- return 1;
- }
- for(int i = 1; i < argc; i++)
- {
- int ret = do_fchownat(AT_FDCWD, argv[i], argv[i], follow_symlinks);
- if(ret != 0) return ret;
- }
- return 0;
- }