summaryrefslogtreecommitdiff
path: root/corebinutils/chmod/chmod.c
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:23:35 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:23:35 +0300
commit729788b336bbcf127179ac655ccf3b3e366f4ca6 (patch)
treeacf314b904cfc6231ac170b47229b7deeffd86bf /corebinutils/chmod/chmod.c
parentccef0af007fdeea4d0bcc3c182629d580f05892b (diff)
parentabd723dd0477f5009c8e77bc85412ca8917bf662 (diff)
downloadProject-Tick-729788b336bbcf127179ac655ccf3b3e366f4ca6.tar.gz
Project-Tick-729788b336bbcf127179ac655ccf3b3e366f4ca6.zip
Add 'corebinutils/chmod/' from commit 'abd723dd0477f5009c8e77bc85412ca8917bf662'
git-subtree-dir: corebinutils/chmod git-subtree-mainline: ccef0af007fdeea4d0bcc3c182629d580f05892b git-subtree-split: abd723dd0477f5009c8e77bc85412ca8917bf662
Diffstat (limited to 'corebinutils/chmod/chmod.c')
-rw-r--r--corebinutils/chmod/chmod.c438
1 files changed, 438 insertions, 0 deletions
diff --git a/corebinutils/chmod/chmod.c b/corebinutils/chmod/chmod.c
new file mode 100644
index 0000000000..faae1c6ca3
--- /dev/null
+++ b/corebinutils/chmod/chmod.c
@@ -0,0 +1,438 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Copyright (c) 2026
+ * Project Tick. All rights reserved.
+ *
+ * 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 <sys/param.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mode.h"
+
+#ifndef SIGINFO
+#define SIGINFO SIGUSR1
+#endif
+
+#ifndef NODEV
+#define NODEV ((dev_t)-1)
+#endif
+
+#ifndef ALLPERMS
+#define ALLPERMS (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
+#endif
+
+struct chmod_options {
+ int fflag;
+ int hflag;
+ int recursive;
+ int vflag;
+ bool root_follow;
+ bool follow_links;
+};
+
+struct visit {
+ dev_t dev;
+ ino_t ino;
+};
+
+struct visit_set {
+ struct visit *items;
+ size_t len;
+ size_t cap;
+};
+
+static volatile sig_atomic_t siginfo;
+
+static void usage(void) __attribute__((noreturn));
+static void siginfo_handler(int sig);
+static bool stat_path(const char *path, bool follow, struct stat *st);
+static bool should_skip_acl_check(const char *path, int hflag, dev_t dev);
+static int apply_mode(const char *display_path, const char *apply_path,
+ const struct stat *st, const struct chmod_options *opts, void *set,
+ bool dereference);
+static int walk_path(const char *path, const struct chmod_options *opts,
+ void *set, bool dereference, struct visit_set *visited);
+static int walk_dir(const char *path, const struct chmod_options *opts,
+ void *set, struct visit_set *visited);
+static bool visited_contains(const struct visit_set *visited, dev_t dev,
+ ino_t ino);
+static bool visited_add(struct visit_set *visited, dev_t dev, ino_t ino);
+static void visited_pop(struct visit_set *visited);
+static char *join_path(const char *parent, const char *name);
+
+static void
+siginfo_handler(int sig)
+{
+ (void)sig;
+ siginfo = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct chmod_options opts;
+ struct visit_set visited;
+ void *set;
+ int Hflag, Lflag, Rflag, ch, rval;
+ char *mode;
+ int i;
+
+ set = NULL;
+ memset(&opts, 0, sizeof(opts));
+ memset(&visited, 0, sizeof(visited));
+ Hflag = Lflag = Rflag = 0;
+
+ while ((ch = getopt(argc, argv, "HLPRXfghorstuvwx")) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'f':
+ opts.fflag = 1;
+ break;
+ case 'h':
+ opts.hflag = 1;
+ break;
+ case 'g': case 'o': case 'r': case 's':
+ case 't': case 'u': case 'w': case 'X': case 'x':
+ if (argv[optind - 1][0] == '-' &&
+ argv[optind - 1][1] == ch &&
+ argv[optind - 1][2] == '\0')
+ --optind;
+ goto done;
+ case 'v':
+ opts.vflag++;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+done:
+ argv += optind;
+ argc -= optind;
+
+ if (argc < 2)
+ usage();
+
+ (void)signal(SIGINFO, siginfo_handler);
+
+ opts.recursive = Rflag;
+ if (Rflag) {
+ if (opts.hflag)
+ errx(1, "the -R and -h options may not be specified together.");
+ if (Lflag) {
+ opts.root_follow = true;
+ opts.follow_links = true;
+ } else if (Hflag) {
+ opts.root_follow = true;
+ opts.follow_links = false;
+ } else {
+ opts.root_follow = false;
+ opts.follow_links = false;
+ }
+ } else {
+ opts.root_follow = (opts.hflag == 0);
+ opts.follow_links = false;
+ }
+
+ mode = *argv;
+ if ((set = mode_compile(mode)) == NULL)
+ errx(1, "invalid file mode: %s", mode);
+
+ rval = 0;
+ for (i = 1; i < argc; i++) {
+ if (walk_path(argv[i], &opts, set, opts.root_follow, &visited) != 0)
+ rval = 1;
+ }
+
+ mode_free(set);
+ free(visited.items);
+ return (rval);
+}
+
+static int
+walk_path(const char *path, const struct chmod_options *opts, void *set,
+ bool dereference, struct visit_set *visited)
+{
+ struct stat st;
+ int rval;
+
+ if (!stat_path(path, dereference, &st)) {
+ warn("%s", path);
+ return (1);
+ }
+
+ rval = apply_mode(path, path, &st, opts, set, dereference);
+ if (rval != 0 && opts->fflag)
+ rval = 0;
+
+ if (!opts->recursive || !S_ISDIR(st.st_mode))
+ return (rval);
+
+ if (visited_contains(visited, st.st_dev, st.st_ino)) {
+ warnx("%s: directory causes a cycle", path);
+ return (1);
+ }
+ if (!visited_add(visited, st.st_dev, st.st_ino)) {
+ if (!opts->fflag)
+ warnx("%s: insufficient memory", path);
+ return (1);
+ }
+
+ if (walk_dir(path, opts, set, visited) != 0)
+ rval = 1;
+ visited_pop(visited);
+ return (rval);
+}
+
+static int
+walk_dir(const char *path, const struct chmod_options *opts, void *set,
+ struct visit_set *visited)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ int rval;
+
+ dirp = opendir(path);
+ if (dirp == NULL) {
+ warn("%s", path);
+ return (1);
+ }
+
+ rval = 0;
+ while ((dp = readdir(dirp)) != NULL) {
+ char *child;
+ bool dereference;
+ struct stat st;
+
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue;
+
+ child = join_path(path, dp->d_name);
+ if (child == NULL) {
+ if (!opts->fflag)
+ warnx("%s: insufficient memory", path);
+ rval = 1;
+ break;
+ }
+
+ dereference = opts->follow_links;
+ if (opts->follow_links) {
+ if (!stat_path(child, true, &st)) {
+ warn("%s", child);
+ free(child);
+ rval = 1;
+ continue;
+ }
+ }
+
+ if (walk_path(child, opts, set, dereference, visited) != 0)
+ rval = 1;
+ free(child);
+ }
+
+ if (closedir(dirp) != 0) {
+ warn("%s", path);
+ rval = 1;
+ }
+
+ return (rval);
+}
+
+static int
+apply_mode(const char *display_path, const char *apply_path, const struct stat *st,
+ const struct chmod_options *opts, void *set, bool dereference)
+{
+ mode_t newmode;
+ int atflag;
+
+ newmode = mode_apply(set, st->st_mode);
+ if (should_skip_acl_check(apply_path, opts->hflag, st->st_dev) &&
+ (newmode & ALLPERMS) == (st->st_mode & ALLPERMS))
+ return (0);
+
+ atflag = dereference ? 0 : AT_SYMLINK_NOFOLLOW;
+ if (fchmodat(AT_FDCWD, apply_path, newmode, atflag) == -1) {
+ if (!opts->fflag)
+ warn("%s", display_path);
+ return (1);
+ }
+
+ if (opts->vflag || siginfo) {
+ (void)printf("%s", display_path);
+ if (opts->vflag > 1 || siginfo) {
+ char before[12], after[12];
+
+ strmode(st->st_mode, before);
+ strmode((st->st_mode & S_IFMT) | newmode, after);
+ (void)printf(": 0%o [%s] -> 0%o [%s]",
+ st->st_mode, before,
+ (st->st_mode & S_IFMT) | newmode, after);
+ }
+ (void)printf("\n");
+ siginfo = 0;
+ }
+
+ return (0);
+}
+
+static bool
+stat_path(const char *path, bool follow, struct stat *st)
+{
+ if (follow)
+ return (stat(path, st) == 0);
+ return (lstat(path, st) == 0);
+}
+
+static bool
+should_skip_acl_check(const char *path, int hflag, dev_t dev)
+{
+ static dev_t previous_dev = NODEV;
+ static int supports_acls = -1;
+
+ if (previous_dev != dev) {
+ previous_dev = dev;
+ supports_acls = 0;
+#ifdef _PC_ACL_NFS4
+ {
+ int ret;
+
+ if (hflag)
+ ret = lpathconf(path, _PC_ACL_NFS4);
+ else
+ ret = pathconf(path, _PC_ACL_NFS4);
+ if (ret > 0)
+ supports_acls = 1;
+ else if (ret < 0 && errno != EINVAL)
+ warn("%s", path);
+ }
+#else
+ (void)hflag;
+ (void)path;
+#endif
+ }
+
+ return (supports_acls == 0);
+}
+
+static bool
+visited_contains(const struct visit_set *visited, dev_t dev, ino_t ino)
+{
+ size_t i;
+
+ for (i = 0; i < visited->len; i++) {
+ if (visited->items[i].dev == dev && visited->items[i].ino == ino)
+ return (true);
+ }
+ return (false);
+}
+
+static bool
+visited_add(struct visit_set *visited, dev_t dev, ino_t ino)
+{
+ struct visit *new_items;
+
+ if (visited->len == visited->cap) {
+ size_t new_cap;
+
+ new_cap = visited->cap == 0 ? 16 : visited->cap * 2;
+ new_items = reallocarray(visited->items, new_cap,
+ sizeof(*visited->items));
+ if (new_items == NULL)
+ return (false);
+ visited->items = new_items;
+ visited->cap = new_cap;
+ }
+
+ visited->items[visited->len].dev = dev;
+ visited->items[visited->len].ino = ino;
+ visited->len++;
+ return (true);
+}
+
+static void
+visited_pop(struct visit_set *visited)
+{
+ if (visited->len > 0)
+ visited->len--;
+}
+
+static char *
+join_path(const char *parent, const char *name)
+{
+ char *path;
+ size_t parent_len, name_len;
+ bool need_slash;
+
+ parent_len = strlen(parent);
+ name_len = strlen(name);
+ need_slash = parent_len > 0 && parent[parent_len - 1] != '/';
+ path = malloc(parent_len + need_slash + name_len + 1);
+ if (path == NULL)
+ return (NULL);
+
+ memcpy(path, parent, parent_len);
+ if (need_slash)
+ path[parent_len++] = '/';
+ memcpy(path + parent_len, name, name_len + 1);
+ return (path);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: chmod [-fhv] [-R [-H | -L | -P]] mode file ...\n");
+ exit(1);
+}