summaryrefslogtreecommitdiff
path: root/corebinutils/chmod/mode.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/mode.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/mode.c')
-rw-r--r--corebinutils/chmod/mode.c424
1 files changed, 424 insertions, 0 deletions
diff --git a/corebinutils/chmod/mode.c b/corebinutils/chmod/mode.c
new file mode 100644
index 0000000000..03f3538f0b
--- /dev/null
+++ b/corebinutils/chmod/mode.c
@@ -0,0 +1,424 @@
+#include "mode.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+typedef struct bitcmd {
+ char cmd;
+ char cmd2;
+ mode_t bits;
+} bitcmd_t;
+
+#define SET_LEN 6
+#define SET_LEN_INCR 4
+
+#define CMD2_CLR 0x01
+#define CMD2_SET 0x02
+#define CMD2_GBITS 0x04
+#define CMD2_OBITS 0x08
+#define CMD2_UBITS 0x10
+
+#define STANDARD_BITS (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO)
+
+static mode_t get_current_umask(void);
+static bitcmd_t *addcmd(bitcmd_t *set, mode_t op, mode_t who, mode_t oparg,
+ mode_t mask);
+static void compress_mode(bitcmd_t *set);
+
+mode_t
+mode_apply(const void *compiled, mode_t old_mode)
+{
+ const bitcmd_t *set;
+ mode_t clear_value, new_mode, value;
+
+ set = compiled;
+ new_mode = old_mode;
+ for (value = 0;; set++) {
+ switch (set->cmd) {
+ case 'u':
+ value = (new_mode & S_IRWXU) >> 6;
+ goto common;
+ case 'g':
+ value = (new_mode & S_IRWXG) >> 3;
+ goto common;
+ case 'o':
+ value = new_mode & S_IRWXO;
+common:
+ if (set->cmd2 & CMD2_CLR) {
+ clear_value = (set->cmd2 & CMD2_SET) ? S_IRWXO : value;
+ if (set->cmd2 & CMD2_UBITS)
+ new_mode &= ~((clear_value << 6) & set->bits);
+ if (set->cmd2 & CMD2_GBITS)
+ new_mode &= ~((clear_value << 3) & set->bits);
+ if (set->cmd2 & CMD2_OBITS)
+ new_mode &= ~(clear_value & set->bits);
+ }
+ if (set->cmd2 & CMD2_SET) {
+ if (set->cmd2 & CMD2_UBITS)
+ new_mode |= (value << 6) & set->bits;
+ if (set->cmd2 & CMD2_GBITS)
+ new_mode |= (value << 3) & set->bits;
+ if (set->cmd2 & CMD2_OBITS)
+ new_mode |= value & set->bits;
+ }
+ break;
+ case '+':
+ new_mode |= set->bits;
+ break;
+ case '-':
+ new_mode &= ~set->bits;
+ break;
+ case 'X':
+ if (old_mode & (S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH))
+ new_mode |= set->bits;
+ break;
+ case '\0':
+ default:
+ return (new_mode);
+ }
+ }
+}
+
+void *
+mode_compile(const char *mode_string)
+{
+ char op, *endp;
+ bitcmd_t *endset, *saveset, *set;
+ mode_t mask, perm, perm_xbits, who;
+ long perm_value;
+ unsigned int setlen;
+ int saved_errno;
+ int equal_done;
+
+ if (*mode_string == '\0') {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ mask = ~get_current_umask();
+ setlen = SET_LEN + 2;
+ set = malloc(setlen * sizeof(*set));
+ if (set == NULL)
+ return (NULL);
+ saveset = set;
+ endset = set + (setlen - 2);
+
+ if (isdigit((unsigned char)*mode_string)) {
+ errno = 0;
+ perm_value = strtol(mode_string, &endp, 8);
+ if (*endp != '\0') {
+ errno = EINVAL;
+ goto fail;
+ }
+ if (errno == ERANGE &&
+ (perm_value == LONG_MAX || perm_value == LONG_MIN)) {
+ goto fail;
+ }
+ if (perm_value & ~(STANDARD_BITS | S_ISVTX)) {
+ errno = EINVAL;
+ goto fail;
+ }
+ perm = (mode_t)perm_value;
+ set = addcmd(set, '=', STANDARD_BITS | S_ISVTX, perm, mask);
+ set->cmd = '\0';
+ return (saveset);
+ }
+
+ equal_done = 0;
+ for (;;) {
+ for (who = 0;; mode_string++) {
+ switch (*mode_string) {
+ case 'a':
+ who |= STANDARD_BITS;
+ break;
+ case 'u':
+ who |= S_ISUID | S_IRWXU;
+ break;
+ case 'g':
+ who |= S_ISGID | S_IRWXG;
+ break;
+ case 'o':
+ who |= S_IRWXO;
+ break;
+ default:
+ goto getop;
+ }
+ }
+getop:
+ op = *mode_string++;
+ if (op != '+' && op != '-' && op != '=') {
+ errno = EINVAL;
+ goto fail;
+ }
+ if (op == '=')
+ equal_done = 0;
+
+ who &= ~S_ISVTX;
+ for (perm = 0, perm_xbits = 0;; mode_string++) {
+ switch (*mode_string) {
+ case 'r':
+ perm |= S_IRUSR | S_IRGRP | S_IROTH;
+ break;
+ case 'w':
+ perm |= S_IWUSR | S_IWGRP | S_IWOTH;
+ break;
+ case 'x':
+ perm |= S_IXUSR | S_IXGRP | S_IXOTH;
+ break;
+ case 'X':
+ perm_xbits = S_IXUSR | S_IXGRP | S_IXOTH;
+ break;
+ case 's':
+ if (who == 0 || (who & ~S_IRWXO) != 0)
+ perm |= S_ISUID | S_ISGID;
+ break;
+ case 't':
+ if (who == 0 || (who & ~S_IRWXO) != 0) {
+ who |= S_ISVTX;
+ perm |= S_ISVTX;
+ }
+ break;
+ case 'u':
+ case 'g':
+ case 'o':
+ if (perm != 0) {
+ set = addcmd(set, op, who, perm, mask);
+ perm = 0;
+ }
+ if (op == '=')
+ equal_done = 1;
+ if (op == '+' && perm_xbits != 0) {
+ set = addcmd(set, 'X', who, perm_xbits, mask);
+ perm_xbits = 0;
+ }
+ set = addcmd(set, *mode_string, who, op, mask);
+ break;
+ default:
+ if (perm != 0 || (op == '=' && !equal_done)) {
+ if (op == '=')
+ equal_done = 1;
+ set = addcmd(set, op, who, perm, mask);
+ perm = 0;
+ }
+ if (perm_xbits != 0) {
+ set = addcmd(set, 'X', who, perm_xbits, mask);
+ perm_xbits = 0;
+ }
+ goto apply;
+ }
+
+ if (set >= endset) {
+ ptrdiff_t offset;
+ bitcmd_t *newset;
+
+ offset = set - saveset;
+ setlen += SET_LEN_INCR;
+ newset = realloc(saveset, setlen * sizeof(*newset));
+ if (newset == NULL)
+ goto fail;
+ saveset = newset;
+ set = newset + offset;
+ endset = newset + (setlen - 2);
+ }
+ }
+apply:
+ if (*mode_string == '\0')
+ break;
+ if (*mode_string != ',') {
+ errno = EINVAL;
+ goto fail;
+ }
+ mode_string++;
+ }
+
+ set->cmd = '\0';
+ compress_mode(saveset);
+ return (saveset);
+
+fail:
+ saved_errno = errno;
+ free(saveset);
+ errno = saved_errno;
+ return (NULL);
+}
+
+void
+mode_free(void *compiled)
+{
+ free(compiled);
+}
+
+void
+strmode(mode_t mode, char *buf)
+{
+ switch (mode & S_IFMT) {
+ case S_IFDIR:
+ *buf++ = 'd';
+ break;
+ case S_IFCHR:
+ *buf++ = 'c';
+ break;
+ case S_IFBLK:
+ *buf++ = 'b';
+ break;
+ case S_IFREG:
+ *buf++ = '-';
+ break;
+ case S_IFLNK:
+ *buf++ = 'l';
+ break;
+ case S_IFSOCK:
+ *buf++ = 's';
+ break;
+#ifdef S_IFIFO
+ case S_IFIFO:
+ *buf++ = 'p';
+ break;
+#endif
+ default:
+ *buf++ = '?';
+ break;
+ }
+
+ *buf++ = (mode & S_IRUSR) ? 'r' : '-';
+ *buf++ = (mode & S_IWUSR) ? 'w' : '-';
+ switch (mode & (S_IXUSR | S_ISUID)) {
+ case 0: *buf++ = '-'; break;
+ case S_IXUSR: *buf++ = 'x'; break;
+ case S_ISUID: *buf++ = 'S'; break;
+ default: *buf++ = 's'; break;
+ }
+ *buf++ = (mode & S_IRGRP) ? 'r' : '-';
+ *buf++ = (mode & S_IWGRP) ? 'w' : '-';
+ switch (mode & (S_IXGRP | S_ISGID)) {
+ case 0: *buf++ = '-'; break;
+ case S_IXGRP: *buf++ = 'x'; break;
+ case S_ISGID: *buf++ = 'S'; break;
+ default: *buf++ = 's'; break;
+ }
+ *buf++ = (mode & S_IROTH) ? 'r' : '-';
+ *buf++ = (mode & S_IWOTH) ? 'w' : '-';
+ switch (mode & (S_IXOTH | S_ISVTX)) {
+ case 0: *buf++ = '-'; break;
+ case S_IXOTH: *buf++ = 'x'; break;
+ case S_ISVTX: *buf++ = 'T'; break;
+ default: *buf++ = 't'; break;
+ }
+ *buf++ = ' ';
+ *buf = '\0';
+}
+
+static mode_t
+get_current_umask(void)
+{
+ sigset_t all_signals, original_signals;
+ mode_t mask;
+
+ sigfillset(&all_signals);
+ (void)sigprocmask(SIG_BLOCK, &all_signals, &original_signals);
+ mask = umask(0);
+ (void)umask(mask);
+ (void)sigprocmask(SIG_SETMASK, &original_signals, NULL);
+ return (mask);
+}
+
+static bitcmd_t *
+addcmd(bitcmd_t *set, mode_t op, mode_t who, mode_t oparg, mode_t mask)
+{
+ switch (op) {
+ case '=':
+ set->cmd = '-';
+ set->bits = who ? who : STANDARD_BITS;
+ set++;
+ op = '+';
+ /* FALLTHROUGH */
+ case '+':
+ case '-':
+ case 'X':
+ set->cmd = op;
+ set->cmd2 = 0;
+ set->bits = (who ? who : mask) & oparg;
+ break;
+ case 'u':
+ case 'g':
+ case 'o':
+ set->cmd = op;
+ if (who != 0) {
+ set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
+ ((who & S_IRGRP) ? CMD2_GBITS : 0) |
+ ((who & S_IROTH) ? CMD2_OBITS : 0);
+ set->bits = (mode_t)~0;
+ } else {
+ set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
+ set->bits = mask;
+ }
+
+ if (oparg == '+')
+ set->cmd2 |= CMD2_SET;
+ else if (oparg == '-')
+ set->cmd2 |= CMD2_CLR;
+ else if (oparg == '=')
+ set->cmd2 |= CMD2_SET | CMD2_CLR;
+ break;
+ }
+
+ return (set + 1);
+}
+
+static void
+compress_mode(bitcmd_t *set)
+{
+ bitcmd_t *next_set;
+ int clear_bits, op, set_bits, x_bits;
+
+ for (next_set = set;;) {
+ while ((op = next_set->cmd) != '+' && op != '-' && op != 'X') {
+ *set++ = *next_set++;
+ if (op == '\0')
+ return;
+ }
+
+ for (set_bits = clear_bits = x_bits = 0;; next_set++) {
+ op = next_set->cmd;
+ if (op == '-') {
+ clear_bits |= next_set->bits;
+ set_bits &= ~next_set->bits;
+ x_bits &= ~next_set->bits;
+ } else if (op == '+') {
+ set_bits |= next_set->bits;
+ clear_bits &= ~next_set->bits;
+ x_bits &= ~next_set->bits;
+ } else if (op == 'X') {
+ x_bits |= next_set->bits & ~set_bits;
+ } else {
+ break;
+ }
+ }
+
+ if (clear_bits != 0) {
+ set->cmd = '-';
+ set->cmd2 = 0;
+ set->bits = clear_bits;
+ set++;
+ }
+ if (set_bits != 0) {
+ set->cmd = '+';
+ set->cmd2 = 0;
+ set->bits = set_bits;
+ set++;
+ }
+ if (x_bits != 0) {
+ set->cmd = 'X';
+ set->cmd2 = 0;
+ set->bits = x_bits;
+ set++;
+ }
+ }
+}