diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | package/deps.mak | 3 | ||||
-rw-r--r-- | package/modes | 1 | ||||
-rw-r--r-- | package/targets.mak | 1 | ||||
-rw-r--r-- | src/shh-portable-utils/chmod.c | 373 | ||||
-rw-r--r-- | src/shh-portable-utils/deps-exe/chmod | 1 |
6 files changed, 380 insertions, 0 deletions
@@ -7,6 +7,7 @@ config.mak src/include/shh-portable-utils/config.h basename cat +chmod cut false true diff --git a/package/deps.mak b/package/deps.mak index 4703292..a90da7a 100644 --- a/package/deps.mak +++ b/package/deps.mak @@ -5,6 +5,7 @@ src/shh-portable-utils/basename.o src/shh-portable-utils/basename.lo: src/shh-portable-utils/basename.c src/shh-portable-utils/byte_notin.o src/shh-portable-utils/byte_notin.lo: src/shh-portable-utils/byte_notin.c src/shh-portable-utils/shhfuncs.h src/shh-portable-utils/cat.o src/shh-portable-utils/cat.lo: src/shh-portable-utils/cat.c +src/shh-portable-utils/chmod.o src/shh-portable-utils/chmod.lo: src/shh-portable-utils/chmod.c src/shh-portable-utils/cut.o src/shh-portable-utils/cut.lo: src/shh-portable-utils/cut.c src/shh-portable-utils/shhfuncs.h src/shh-portable-utils/false.o src/shh-portable-utils/false.lo: src/shh-portable-utils/false.c src/shh-portable-utils/shhgetln.o src/shh-portable-utils/shhgetln.lo: src/shh-portable-utils/shhgetln.c src/shh-portable-utils/shhfuncs.h @@ -15,6 +16,8 @@ basename: EXTRA_LIBS := -lskarnet basename: src/shh-portable-utils/basename.o cat: EXTRA_LIBS := -lskarnet cat: src/shh-portable-utils/cat.o +chmod: EXTRA_LIBS := -lskarnet +chmod: src/shh-portable-utils/chmod.o cut: EXTRA_LIBS := -lskarnet cut: src/shh-portable-utils/cut.o src/shh-portable-utils/shhgetln.o false: EXTRA_LIBS := diff --git a/package/modes b/package/modes index ab9464c..4c8a005 100644 --- a/package/modes +++ b/package/modes @@ -1,5 +1,6 @@ basename 0755 cat 0755 +chmod 0755 cut 0755 false 0755 true 0755 diff --git a/package/targets.mak b/package/targets.mak index 754091e..f18f34b 100644 --- a/package/targets.mak +++ b/package/targets.mak @@ -1,6 +1,7 @@ BIN_TARGETS := \ basename \ cat \ +chmod \ cut \ false \ true \ diff --git a/src/shh-portable-utils/chmod.c b/src/shh-portable-utils/chmod.c new file mode 100644 index 0000000..cce6844 --- /dev/null +++ b/src/shh-portable-utils/chmod.c @@ -0,0 +1,373 @@ +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> + +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/types.h> +#include <skalibs/skamisc.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> + +#define USAGE "chmod [-R] mode file..." + +#define S_ISALL (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX) + +struct chmod_directive_s { + mode_t who; + int action; + mode_t perm; + int permcopy; + int dir_x; +}; + +typedef struct chmod_directive_s chmod_directive; + +#define CHMOD_DIRECTIVE_ZERO { 0, 0, 0, 0, 0 } + +mode_t parse_octal(char const*); + +void parse_symbolic(char const*, genalloc*); + +/* + * Algorithm for chmod mode parsing was inspired by sbase's chmod + * implementation (https://core.suckless.org/sbase), so let's credit them here. + */ + +int change_mode(char const*, mode_t, genalloc*, mode_t); + +int traverse_dir(stralloc*, mode_t, genalloc*, mode_t); + +int main(int argc, char const *const *argv) +{ + struct stat st; + mode_t mask; + mode_t mode = 0; + int recurse = 0; + int failure = 0; + genalloc directives = GENALLOC_ZERO; + subgetopt l = SUBGETOPT_ZERO; + PROG = "chmod"; + for (;;) { + int opt = subgetopt_r(argc, argv, "R", &l); + if (opt == -1) + break; + switch (opt) { + case 'R': + recurse = 1; + break; + default: + strerr_dieusage(100, USAGE); + } + } + argc -= l.ind; + argv += l.ind; + + if (argc < 2) + strerr_dieusage(100, USAGE); + + mask = umask(0); + umask(mask); + + if ('0' <= argv[0][0] && argv[0][0] <= '7') + mode = parse_octal(argv[0]); + else { + parse_symbolic(argv[0], &directives); + if (!genalloc_len(chmod_directive, &directives)) + strerr_dieusage(100, USAGE); + } + + for (char const *const *filename = argv + 1; *filename; filename++) { + if (stat(*filename, &st)) { + strerr_warnwu2sys("stat ", *filename); + failure = 1; + continue; + } + if (S_ISDIR(st.st_mode) && recurse) { + satmp.len = 0; + stralloc_catb(&satmp, *filename, strlen(*filename) + 1); + failure = failure || traverse_dir(&satmp, mode, &directives, + mask); + } + else + failure = failure || change_mode(*filename, mode, &directives, + mask); + } + return failure ? 111 : 0; +} + + +mode_t parse_octal(char const *raw_mode) +{ + mode_t mode = 0; + unsigned int m; + if (!uint0_oscan(raw_mode, &m)) + strerr_dief2x(100, "invalid mode: ", raw_mode); + + if (m & 0001) + mode |= S_IXOTH; + if (m & 0002) + mode |= S_IWOTH; + if (m & 0004) + mode |= S_IROTH; + if (m & 0010) + mode |= S_IXGRP; + if (m & 0020) + mode |= S_IWGRP; + if (m & 0040) + mode |= S_IRGRP; + if (m & 0100) + mode |= S_IXUSR; + if (m & 0200) + mode |= S_IWUSR; + if (m & 0400) + mode |= S_IRUSR; + if (m & 01000) + mode |= S_ISVTX; + if (m & 02000) + mode |= S_ISGID; + if (m & 04000) + mode |= S_ISUID; + + return mode; +} + +void parse_symbolic(char const *raw, genalloc *directives) +{ + char const *p = raw; + for (;;) { + chmod_directive d = CHMOD_DIRECTIVE_ZERO; + /* stage 1: parse 'who' list */ + for (;;) { + if (*p == 'u') + d.who |= S_IRWXU | S_ISUID; + else if (*p == 'g') + d.who |= S_IRWXG | S_ISGID; + else if (*p == 'o') + d.who |= S_IRWXO; + else if (*p == 'a') + d.who |= S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID; + else + break; + p++; + } + /* stage 2: actions. First cycle is out of the loop. */ + switch(*p) { + case '+': + case '-': + case '=': + d.action = *p; + break; + default: + strerr_dief2x(100, "invalid mode: ", raw); + } + p++; + for (;;) { + if (*p == 'u') + d.permcopy |= 1; + else if (*p == 'g') + d.permcopy |= 2; + else if (*p == 'o') + d.permcopy |= 4; + else if (*p == 'r') + d.perm |= S_IRUSR | S_IRGRP | S_IROTH; + else if (*p == 'w') + d.perm |= S_IWUSR | S_IWGRP | S_IWOTH; + else if (*p == 'x') + d.perm |= S_IXUSR | S_IXGRP | S_IXOTH; + else if (*p == 'X') + d.dir_x = 1; + else if (*p == 's') + d.perm |= S_ISUID | S_ISGID; + else if (*p == 't') + d.perm |= S_ISVTX; + else if (*p == ',') { + p++; + break; + } + else if (*p == '\0') { + if (d.perm && d.permcopy) + strerr_dief2x(100, "invalid mode: ", raw); + if (!genalloc_append(chmod_directive, directives, &d)) + strerr_diefu1sys(111, "parse mode"); + return; + } + p++; + } + + if (d.perm && d.permcopy) + strerr_dief2x(100, "invalid mode: ", raw); + if (!genalloc_append(chmod_directive, directives, &d)) + strerr_diefu1sys(111, "parse mode"); + + for (;;) { + d.action = d.perm = d.permcopy = d.dir_x = 0; + if (*p == '+' || *p == '-' || *p == '=') + d.action = *p; + else if (*p == ',') + break; + else if (*p == '\0') + return; + else + strerr_dief2x(100, "invalid mode: ", raw); + + p++; + + for (;;) { + if (*p == 'u') + d.permcopy |= 1; + else if (*p == 'g') + d.permcopy |= 2; + else if (*p == 'o') + d.permcopy |= 4; + else if (*p == 'r') + d.perm |= S_IRUSR | S_IRGRP | S_IROTH; + else if (*p == 'w') + d.perm |= S_IWUSR | S_IWGRP | S_IWOTH; + else if (*p == 'x') + d.perm |= S_IXUSR | S_IXGRP | S_IXOTH; + else if (*p == 'X') + d.dir_x = 1; + else if (*p == 's') + d.perm |= S_ISUID | S_ISGID; + else if (*p == 't') + d.perm |= S_ISVTX; + else if (*p == ',') { + p++; + break; + } + else if (*p == '\0') { + if (d.perm && d.permcopy) + strerr_dief2x(100, "invalid mode: ", raw); + if (!genalloc_append(chmod_directive, directives, &d)) + strerr_diefu1sys(111, "parse mode"); + return; + } + p++; + } + + if (d.perm && d.permcopy) + strerr_dief2x(100, "invalid mode: ", raw); + if (!genalloc_append(chmod_directive, directives, &d)) + strerr_diefu1sys(111, "parse mode"); + } + } +} + +int change_mode(char const *file, mode_t mode, genalloc *directives, + mode_t mask) +{ + mode_t cur_mode; + if (!genalloc_len(chmod_directive, directives)) + cur_mode = mode; + else { + struct stat st; + mode_t who, perm, clear; + chmod_directive const *s = genalloc_s(chmod_directive, directives); + size_t len = genalloc_len(chmod_directive, directives); + + if (stat(file, &st) == -1) { + strerr_warnwu2sys("stat ", file); + return 1; + } + cur_mode = st.st_mode; + + for (size_t i = 0; i < len; i++) { + if (s[i].who) { + who = s[i].who; + clear = s[i].who; + } + else { + who = ~mask; + clear = S_ISALL; + } + perm = s[i].perm; + + if (s[i].permcopy & 1) { + if (cur_mode & S_IRUSR) + perm |= S_IRUSR | S_IRGRP | S_IROTH; + if (cur_mode & S_IWUSR) + perm |= S_IWUSR | S_IWGRP | S_IWOTH; + if (cur_mode & S_IXUSR) + perm |= S_IXUSR | S_IXGRP | S_IXOTH; + } + if (s[i].permcopy & 2) { + if (cur_mode & S_IRGRP) + perm |= S_IRUSR | S_IRGRP | S_IROTH; + if (cur_mode & S_IWGRP) + perm |= S_IWUSR | S_IWGRP | S_IWOTH; + if (cur_mode & S_IXGRP) + perm |= S_IXUSR | S_IXGRP | S_IXOTH; + } + if (s[i].permcopy & 4) { + if (cur_mode & S_IROTH) + perm |= S_IRUSR | S_IRGRP | S_IROTH; + if (cur_mode & S_IWOTH) + perm |= S_IWUSR | S_IWGRP | S_IWOTH; + if (cur_mode & S_IXOTH) + perm |= S_IXUSR | S_IXGRP | S_IXOTH; + } + + if (s[i].dir_x && (S_ISDIR(cur_mode) + || (cur_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))) + perm |= S_IXUSR | S_IXGRP | S_IXOTH; + + switch (s[i].action) { + case '=': + cur_mode &= ~clear; + case '+': + cur_mode |= (who & perm); + break; + case '-': + cur_mode &= ~(who & perm); + } + } + } + if (chmod(file, cur_mode) == -1) { + strerr_warnwu2sys("change mode of ", file); + return 1; + } + return 0; +} + +int traverse_dir(stralloc *dirname, mode_t mode, genalloc *directives, + mode_t mask) +{ + int failure = 0; + size_t filename_len; + struct dirent *entry; + struct stat st; + DIR *dir = opendir(dirname->s); + if (!dir) { + strerr_warnwu2sys("open dir: ", dirname->s); + return 1; + } + while ((entry = readdir(dir))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + dirname->s[dirname->len - 1] = '/'; + filename_len = strlen(entry->d_name); + if (!stralloc_catb(dirname, entry->d_name, filename_len + 1)) { + strerr_warnwu4sys("build file name: ", dirname->s, "/", + entry->d_name); + failure = 1; + continue; + } + if (stat(dirname->s, &st) == -1) { + strerr_warnwu2sys("stat ", dirname->s); + failure = 1; + } + if (S_ISDIR(st.st_mode)) + failure = failure || traverse_dir(dirname, mode, directives, mask); + else + failure = failure || change_mode(dirname->s, mode, directives, + mask); + dirname->len -= filename_len + 1; + dirname->s[dirname->len - 1] = '\0'; + } + failure = failure || change_mode(dirname->s, mode, directives, mask); + closedir(dir); + return failure; +} diff --git a/src/shh-portable-utils/deps-exe/chmod b/src/shh-portable-utils/deps-exe/chmod new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/shh-portable-utils/deps-exe/chmod @@ -0,0 +1 @@ +-lskarnet |