commit: 37883f2036135bd68cebd5ed1d31d3c4b834f88c
parent 10b80638bb1cf28b1375aa2e2add71d76b636313
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Mon, 11 Mar 2024 12:09:19 +0100
lib/mode.c: new
Diffstat:
7 files changed, 659 insertions(+), 4 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -13,8 +13,10 @@
/build/
-# Binary
-/test-lib/strtodur
+/test-lib/*
+!/test-lib/Kuafile
+!/test-lib/*.c
+!/test-lib/*.h
# Kyua
/html/
diff --git a/Makefile b/Makefile
@@ -13,11 +13,16 @@ all: $(EXE) $(MAN1SO)
rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
$(CC) -std=c99 $(CFLAGS) -c -o $@ $<
+TEST_LIBS = test-lib/mode test-lib/strtodur
.PHONY: check
-check: all test-lib/strtodur
+check: all $(TEST_LIBS)
MALLOC_CHECK_=3 POSIX_ME_HARDER=1 POSIXLY_CORRECT=1 LDSTATIC=$(LDSTATIC) kyua test || (kyua report --verbose --results-filter=broken,failed; false)
MALLOC_CHECK_=3 POSIX_ME_HARDER=1 POSIXLY_CORRECT=1 $(CRAM) test-cmd/*.t
+.PHONY: check-libs
+check-libs: $(TEST_LIBS)
+ MALLOC_CHECK_=3 POSIX_ME_HARDER=1 POSIXLY_CORRECT=1 LDSTATIC=$(LDSTATIC) kyua test test-lib/ || (kyua report --verbose --results-filter=broken,failed; false)
+
.PHONY: lint
lint: $(MAN1SO)
$(SHELLCHECK) ./configure ./test_functions.sh
@@ -27,7 +32,7 @@ lint: $(MAN1SO)
.PHONY: clean
clean:
- rm -fr $(EXE) $(MAN1SO) test-lib/strtodur
+ rm -fr $(EXE) $(MAN1SO) $(TEST_LIBS)
rm -fr ${EXE:=.c.gcov} ${EXE:=.gcda} ${EXE:=.gcno}
install: all
@@ -62,6 +67,9 @@ cmd/sleep: cmd/sleep.c lib/strtodur.c Makefile
rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
$(CC) -std=c99 $(CFLAGS) -o $@ cmd/sleep.c lib/strtodur.c $(LDFLAGS) $(LDSTATIC)
+test-lib/mode: test-lib/mode.c lib/mode.c Makefile
+ $(CC) -std=c99 $(CFLAGS) $(ATF_CFLAGS) -o $@ test-lib/mode.c lib/mode.c $(LDFLAGS) $(ATF_LIBS)
+
test-lib/strtodur: test-lib/strtodur.c lib/strtodur.c Makefile
$(CC) -std=c99 $(CFLAGS) $(ATF_CFLAGS) -o $@ test-lib/strtodur.c lib/strtodur.c $(LDFLAGS) $(ATF_LIBS)
diff --git a/lib/bitmasks.h b/lib/bitmasks.h
@@ -7,3 +7,5 @@
#define FIELD_TGL(field, val) field ^= (val)
#define FIELD_GET(field, val) (field & (val))
+
+#define FIELD_MATCH(field, val) ((field & (val)) == (val))
diff --git a/lib/mode.c b/lib/mode.c
@@ -0,0 +1,280 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include "mode.h"
+
+#include "bitmasks.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h> // int8_t
+#include <stdio.h> // strtol
+#include <stdlib.h> // abort
+#include <string.h> // strlen
+#include <sys/stat.h> // umask
+
+enum perm_who_e
+{
+ WHO_RST = 0, // 0b000
+ WHO_USER = 1, // 0b001
+ WHO_GROUP = 2, // 0b010
+ WHO_OTHER = 4, // 0b100
+ WHO_ALL = 7, // 0b111
+};
+
+enum operation_e
+{
+ OP_RST,
+ OP_ADD,
+ OP_DEL,
+ OP_SET,
+};
+
+struct modes_t
+{
+ int8_t set;
+ int8_t user;
+ int8_t group;
+ int8_t other;
+};
+
+struct meta_t
+{
+ enum perm_who_e dest;
+ enum operation_e op;
+ struct modes_t new_modes;
+};
+
+static void
+apply(struct meta_t *meta, int8_t mode)
+{
+ // get old, then reset to old. Yay to POSIX nonsense
+ mode_t mask = umask(0);
+ umask(mask);
+
+ // negate to get a normal bitmask
+ mask ^= 0777;
+
+ switch(meta->op)
+ {
+ case OP_ADD:
+ if(meta->dest == WHO_RST)
+ {
+ FIELD_SET(meta->new_modes.user, mode & ((mask >> 6) & 07));
+ FIELD_SET(meta->new_modes.group, mode & ((mask >> 3) & 07));
+ FIELD_SET(meta->new_modes.other, mode & ((mask >> 0) & 07));
+ }
+ if(FIELD_MATCH(meta->dest, WHO_USER)) FIELD_SET(meta->new_modes.user, mode);
+ if(FIELD_MATCH(meta->dest, WHO_GROUP)) FIELD_SET(meta->new_modes.group, mode);
+ if(FIELD_MATCH(meta->dest, WHO_OTHER)) FIELD_SET(meta->new_modes.other, mode);
+ break;
+ case OP_DEL:
+ if(meta->dest == WHO_RST)
+ {
+ FIELD_CLR(meta->new_modes.user, mode & ((mask >> 6) & 07));
+ FIELD_CLR(meta->new_modes.group, mode & ((mask >> 3) & 07));
+ FIELD_CLR(meta->new_modes.other, mode & ((mask >> 0) & 07));
+ }
+ if(FIELD_MATCH(meta->dest, WHO_USER)) FIELD_CLR(meta->new_modes.user, mode);
+ if(FIELD_MATCH(meta->dest, WHO_GROUP)) FIELD_CLR(meta->new_modes.group, mode);
+ if(FIELD_MATCH(meta->dest, WHO_OTHER)) FIELD_CLR(meta->new_modes.other, mode);
+ break;
+ case OP_SET:
+ if(meta->dest == WHO_RST)
+ {
+ meta->new_modes.user = mode & ((mask >> 6) & 07);
+ meta->new_modes.group = mode & ((mask >> 3) & 07);
+ meta->new_modes.other = mode & ((mask >> 0) & 07);
+ }
+ if(FIELD_MATCH(meta->dest, WHO_USER)) meta->new_modes.user = mode;
+ if(FIELD_MATCH(meta->dest, WHO_GROUP)) meta->new_modes.group = mode;
+ if(FIELD_MATCH(meta->dest, WHO_OTHER)) meta->new_modes.other = mode;
+ break;
+ default:
+ abort();
+ }
+}
+
+mode_t
+new_mode(const char *mode, mode_t old, const char **errstr)
+{
+ // NULL? Then nothing to change :)
+ if(mode == NULL) return old;
+
+ size_t mode_len = strlen(mode);
+
+ if(mode_len == 0) return old;
+
+ bool symbolic = false;
+ for(size_t i = 0; i < mode_len; i++)
+ {
+ if(mode[i] < '0' || mode[i] > '7')
+ {
+ symbolic = true;
+ break;
+ }
+ }
+
+ if(!symbolic)
+ {
+ errno = 0;
+ char *endptr = NULL;
+
+ long new = strtol(mode, &endptr, 8);
+
+ if(errno != 0)
+ {
+ *errstr = strerror(errno);
+ return old;
+ }
+
+ if(new < 0)
+ {
+ *errstr = "mode can't be negative";
+ return old;
+ }
+
+ if(new > 07777)
+ {
+ *errstr = "mode can't be higher than 0o7777";
+ return old;
+ }
+
+ return (old & 0770000) | new;
+ }
+
+ // ((^|,)[ugoa]*([+-=]|[ugo]|[rwxXst]+)+)+
+ struct meta_t meta = {.dest = WHO_RST,
+ .op = OP_RST,
+ .new_modes = {
+ .set = (old & 07000) >> 9,
+ .user = (old & 00700) >> 6,
+ .group = (old & 00070) >> 3,
+ .other = (old & 00007) >> 0,
+ }};
+
+ for(size_t i = 0; i < mode_len; i++)
+ {
+ char m = mode[i];
+
+ switch(m)
+ {
+ // separator
+ case ',':
+ meta.dest = WHO_RST;
+ meta.op = OP_RST;
+ break;
+
+ // who
+ case 'u':
+ if(meta.op == OP_RST)
+ FIELD_SET(meta.dest, WHO_USER);
+ else
+ apply(&meta, meta.new_modes.user);
+ break;
+ case 'g':
+ if(meta.op == OP_RST)
+ FIELD_SET(meta.dest, WHO_GROUP);
+ else
+ apply(&meta, meta.new_modes.group);
+ break;
+ case 'o':
+ if(meta.op == OP_RST)
+ FIELD_SET(meta.dest, WHO_OTHER);
+ else
+ apply(&meta, meta.new_modes.other);
+ break;
+ case 'a':
+ if(meta.op == OP_RST) FIELD_SET(meta.dest, WHO_ALL);
+ // ignore =a, POSIX doesn't allows "a" in permcopy (would be a noop anyway)
+ break;
+
+ // op
+ // Implementation-note: When another operator was set, override it
+ case '+':
+ meta.op = OP_ADD;
+ break;
+ case '-':
+ meta.op = OP_DEL;
+ break;
+ case '=':
+ meta.op = OP_SET;
+ apply(&meta, 00);
+ break;
+
+ // perm
+ case 'r':
+ if(meta.op == OP_RST)
+ {
+ *errstr = "perm 'r' given without operator";
+ return 0;
+ }
+ apply(&meta, 04);
+ break;
+ case 'w':
+ if(meta.op == OP_RST)
+ {
+ *errstr = "perm 'w' given without operator";
+ return 0;
+ }
+ apply(&meta, 02);
+ break;
+ case 'x':
+ if(meta.op == OP_RST)
+ {
+ *errstr = "perm 'x' given without operator";
+ return 0;
+ }
+ apply(&meta, 01);
+ break;
+ case 'X':
+ if(meta.op == OP_RST)
+ {
+ *errstr = "perm 'X' given without operator";
+ return 0;
+ }
+ if(S_ISDIR(old)) apply(&meta, 01);
+ break;
+ case 's':
+ if(meta.op == OP_RST)
+ {
+ *errstr = "perm 's' given without operator";
+ return 0;
+ }
+ if(FIELD_MATCH(meta.dest, WHO_USER))
+ meta.new_modes.set = S_ISUID >> 9;
+ else if(FIELD_MATCH(meta.dest, WHO_GROUP))
+ meta.new_modes.set = S_ISGID >> 9;
+ else
+ meta.new_modes.set = S_ISUID >> 9;
+ break;
+ case 't':
+ // Implementation defined behavior outside of directories and !(WHO_ALL|WHO_RST)
+ if(meta.op == OP_RST)
+ {
+ *errstr = "perm 't' given without operator";
+ return 0;
+ }
+ meta.new_modes.set = S_ISVTX >> 9;
+ break;
+
+ default:
+ // Would need to either be non-thread-safe with a shared read-write buffer
+ // or only allocate in this situation, meaning a memory leak.
+ *errstr = "syntax error, got invalid character";
+
+ return 0;
+ }
+ }
+
+ // clang-format off
+ return (old & 0770000)
+ | ((meta.new_modes.set & 07) << 9)
+ | ((meta.new_modes.user & 07) << 6)
+ | ((meta.new_modes.group & 07) << 3)
+ | ((meta.new_modes.other & 07) << 0);
+ // clang-format on
+}
diff --git a/lib/mode.h b/lib/mode.h
@@ -0,0 +1,8 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#include <sys/stat.h> // mode_t
+
+// Returns 0 on error and sets errstr
+mode_t new_mode(const char *mode, mode_t old, const char **errstr);
diff --git a/test-lib/Kyuafile b/test-lib/Kyuafile
@@ -5,4 +5,5 @@ syntax(2)
test_suite("utils-std libs")
-- 7,$|LC_ALL=C.UTF-8 sort
+atf_test_program{name="mode"}
atf_test_program{name="strtodur"}
diff --git a/test-lib/mode.c b/test-lib/mode.c
@@ -0,0 +1,354 @@
+// utils-std: Collection of commonly available Unix tools
+// SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <atf-c.h>
+#include <assert.h>
+#include <signal.h> // SIGABRT
+#include <sys/stat.h> // umask
+
+#include "../lib/mode.h"
+
+#define CHECK_EQ_MODE(expect, got) ATF_CHECK_EQ_MSG(expect, got, "Expected %04o; Got 0%06o\n", expect, got)
+
+#define TEST_MODE(str, old, expect) \
+ res = new_mode(str, old, &errstr); \
+ CHECK_EQ_MODE(expect, res); \
+ ATF_CHECK(errstr == NULL)
+
+#define TEST_ERRSTR(str, old, error) \
+ res = new_mode(str, old, &errstr); \
+ CHECK_EQ_MODE(0, res); \
+ ATF_CHECK(errstr != NULL); \
+ ATF_CHECK_STREQ(error, errstr)
+
+ATF_TC(empty);
+ATF_TC_HEAD(empty, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Empty string");
+}
+ATF_TC_BODY(empty, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ TEST_MODE("", 0, 0);
+ TEST_MODE("", 00777, 00777);
+}
+
+ATF_TC(null_str);
+ATF_TC_HEAD(null_str, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "NULL string");
+}
+ATF_TC_BODY(null_str, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ TEST_MODE(NULL, 0, 0);
+ TEST_MODE(NULL, 00777, 00777);
+}
+
+ATF_TC(sole_comma);
+ATF_TC_HEAD(sole_comma, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "string containing only a comma");
+}
+ATF_TC_BODY(sole_comma, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ TEST_MODE(",", 0, 0);
+ TEST_MODE(",", 00777, 00777);
+}
+
+ATF_TC(add_read);
+ATF_TC_HEAD(add_read, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "chmod +r");
+}
+ATF_TC_BODY(add_read, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ umask(0044);
+
+ TEST_MODE( "+r", 0, 00400);
+ TEST_MODE("a+r", 0, 00444);
+ TEST_MODE("u+r", 0, 00400);
+ TEST_MODE("g+r", 0, 00040);
+ TEST_MODE("o+r", 0, 00004);
+ TEST_MODE("ug+r", 0, 00440);
+ TEST_MODE("go+r", 0, 00044);
+ TEST_MODE("uo+r", 0, 00404);
+ TEST_MODE("u+r,g+r", 0, 00440);
+ TEST_MODE("g+r,o+r", 0, 00044);
+ TEST_MODE("u+r,o+r", 0, 00404);
+
+ TEST_MODE( "+r", 00777, 00777);
+ TEST_MODE("a+r", 00777, 00777);
+ TEST_MODE("u+r", 00777, 00777);
+ TEST_MODE("g+r", 00777, 00777);
+ TEST_MODE("o+r", 00777, 00777);
+ TEST_MODE("ug+r", 00777, 00777);
+ TEST_MODE("go+r", 00777, 00777);
+ TEST_MODE("uo+r", 00777, 00777);
+ TEST_MODE("u+r,g+r", 00777, 00777);
+ TEST_MODE("g+r,o+r", 00777, 00777);
+ TEST_MODE("u+r,o+r", 00777, 00777);
+}
+
+ATF_TC(set_read);
+ATF_TC_HEAD(set_read, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "chmod =r");
+}
+ATF_TC_BODY(set_read, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ umask(0044);
+
+ TEST_MODE( "=r", 0, 00400);
+ TEST_MODE("a=r", 0, 00444);
+ TEST_MODE("u=r", 0, 00400);
+ TEST_MODE("g=r", 0, 00040);
+ TEST_MODE("o=r", 0, 00004);
+ TEST_MODE("ug=r", 0, 00440);
+ TEST_MODE("go=r", 0, 00044);
+ TEST_MODE("uo=r", 0, 00404);
+ TEST_MODE("u=r,g=r", 0, 00440);
+ TEST_MODE("g=r,o=r", 0, 00044);
+ TEST_MODE("u=r,o=r", 0, 00404);
+
+ TEST_MODE( "=r", 00777, 00400);
+ TEST_MODE("a=r", 00777, 00444);
+ TEST_MODE("u=r", 00777, 00477);
+ TEST_MODE("g=r", 00777, 00747);
+ TEST_MODE("o=r", 00777, 00774);
+ TEST_MODE("ug=r", 00777, 00447);
+ TEST_MODE("go=r", 00777, 00744);
+ TEST_MODE("uo=r", 00777, 00474);
+ TEST_MODE("u=r,g=r", 00777, 00447);
+ TEST_MODE("g=r,o=r", 00777, 00744);
+ TEST_MODE("u=r,o=r", 00777, 00474);
+}
+
+ATF_TC(del_read);
+ATF_TC_HEAD(del_read, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "chmod -r");
+}
+ATF_TC_BODY(del_read, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ umask(0044);
+
+ TEST_MODE( "-r", 0, 0);
+ TEST_MODE("a-r", 0, 0);
+ TEST_MODE("u-r", 0, 0);
+ TEST_MODE("g-r", 0, 0);
+ TEST_MODE("o-r", 0, 0);
+ TEST_MODE("ug-r", 0, 0);
+ TEST_MODE("go-r", 0, 0);
+ TEST_MODE("uo-r", 0, 0);
+ TEST_MODE("u-r,g-r", 0, 0);
+ TEST_MODE("g-r,o-r", 0, 0);
+ TEST_MODE("u-r,o-r", 0, 0);
+
+ TEST_MODE( "-r", 00777, 00377);
+ TEST_MODE("a-r", 00777, 00333);
+ TEST_MODE("u-r", 00777, 00377);
+ TEST_MODE("g-r", 00777, 00737);
+ TEST_MODE("o-r", 00777, 00773);
+ TEST_MODE("ug-r", 00777, 00337);
+ TEST_MODE("go-r", 00777, 00733);
+ TEST_MODE("uo-r", 00777, 00373);
+ TEST_MODE("u-r,g-r", 00777, 00337);
+ TEST_MODE("g-r,o-r", 00777, 00733);
+ TEST_MODE("u-r,o-r", 00777, 00373);
+}
+
+ATF_TC(search);
+ATF_TC_HEAD(search, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "chmod [-+=]X");
+}
+ATF_TC_BODY(search, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ TEST_MODE( "-X", 0, 0);
+ TEST_MODE("a-X", 0, 0);
+ TEST_MODE("u-X", 0, 0);
+ TEST_MODE("g-X", 0, 0);
+ TEST_MODE("o-X", 0, 0);
+
+ TEST_MODE( "+X", 0, 0);
+ TEST_MODE("a+X", 0, 0);
+ TEST_MODE("u+X", 0, 0);
+ TEST_MODE("g+X", 0, 0);
+ TEST_MODE("o+X", 0, 0);
+
+ TEST_MODE( "=X", 0, 0);
+ TEST_MODE("a=X", 0, 0);
+ TEST_MODE("u=X", 0, 0);
+ TEST_MODE("g=X", 0, 0);
+ TEST_MODE("o=X", 0, 0);
+
+ // S_IFDIR = 0040000
+ TEST_MODE( "-X", 0040777, 0040666);
+ TEST_MODE("a-X", 0040777, 0040666);
+ TEST_MODE("u-X", 0040777, 0040677);
+ TEST_MODE("g-X", 0040777, 0040767);
+ TEST_MODE("o-X", 0040777, 0040776);
+
+ TEST_MODE( "+X", 0040777, 0040777);
+ TEST_MODE("a+X", 0040777, 0040777);
+ TEST_MODE("u+X", 0040777, 0040777);
+ TEST_MODE("g+X", 0040777, 0040777);
+ TEST_MODE("o+X", 0040777, 0040777);
+
+ TEST_MODE( "=X", 0040777, 0040111);
+ TEST_MODE("a=X", 0040777, 0040111);
+ TEST_MODE("u=X", 0040777, 0040177);
+ TEST_MODE("g=X", 0040777, 0040717);
+ TEST_MODE("o=X", 0040777, 0040771);
+
+ TEST_MODE( "-X", 0040000, 0040000);
+ TEST_MODE("a-X", 0040000, 0040000);
+ TEST_MODE("u-X", 0040000, 0040000);
+ TEST_MODE("g-X", 0040000, 0040000);
+ TEST_MODE("o-X", 0040000, 0040000);
+
+ TEST_MODE( "+X", 0040000, 0040111);
+ TEST_MODE("a+X", 0040000, 0040111);
+ TEST_MODE("u+X", 0040000, 0040100);
+ TEST_MODE("g+X", 0040000, 0040010);
+ TEST_MODE("o+X", 0040000, 0040001);
+
+ TEST_MODE( "=X", 0040000, 0040111);
+ TEST_MODE("a=X", 0040000, 0040111);
+ TEST_MODE("u=X", 0040000, 0040100);
+ TEST_MODE("g=X", 0040000, 0040010);
+ TEST_MODE("o=X", 0040000, 0040001);
+}
+
+ATF_TC(syntax);
+ATF_TC_HEAD(syntax, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Weird syntax and syntax errors");
+}
+ATF_TC_BODY(syntax, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ TEST_MODE("=", 0, 0);
+ TEST_MODE("=", 0040777, 0040000);
+
+ TEST_MODE("-", 0, 0);
+ TEST_MODE("-", 0040777, 0040777);
+
+ TEST_MODE("+", 0, 0);
+ TEST_MODE("+", 0040777, 0040777);
+
+ TEST_ERRSTR("/", 0, "syntax error, got invalid character");
+
+ TEST_ERRSTR("foo", 0, "syntax error, got invalid character");
+
+ TEST_ERRSTR("77777", 0, "mode can't be higher than 0o7777");
+
+ TEST_ERRSTR("-0", 0, "syntax error, got invalid character");
+}
+
+ATF_TC(posix_examples);
+ATF_TC_HEAD(posix_examples, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Examples as given by POSIX in chmod(1p)");
+}
+ATF_TC_BODY(posix_examples, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ // clears all file mode bits.
+ TEST_MODE("a+=", 0040777, 0040000);
+ TEST_MODE("a+=", 0040000, 0040000);
+
+ // clears group and other write bits
+ TEST_MODE("go+-w", 0040777, 0040755);
+ TEST_MODE("go+-w", 0040000, 0040000);
+
+ // sets group bit to match other bits and then clears group write bit.
+ TEST_MODE("g=o-w", 0040777, 0040757);
+ TEST_MODE("g=o-w", 0040642, 0040602);
+ TEST_MODE("g=o-w", 0040246, 0040246);
+ TEST_MODE("g=o-w", 0040000, 0040000);
+
+ // clears group read bit and sets group write bit.
+ TEST_MODE("g-r+w", 0040777, 0040737);
+ TEST_MODE("g-r+w", 0040000, 0040020);
+
+ // Sets owner bits to match group bits and sets other bits to match group bits.
+ TEST_MODE("uo=g", 0040777, 0040777);
+ TEST_MODE("uo=g", 0040642, 0040444);
+ TEST_MODE("uo=g", 0040000, 0040000);
+}
+
+ATF_TC(non_symbolic);
+ATF_TC_HEAD(non_symbolic, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "chmod /[0-7]{1,4}/");
+}
+ATF_TC_BODY(non_symbolic, tc)
+{
+ const char *errstr = NULL;
+ mode_t res;
+
+ TEST_MODE("0", 0, 0);
+ TEST_MODE("0", 0040777, 0040000);
+
+ TEST_MODE("7", 0, 07);
+ TEST_MODE("7", 0040777, 0040007);
+
+ TEST_MODE("77", 0, 077);
+ TEST_MODE("77", 0040777, 0040077);
+
+ TEST_MODE("777", 0, 0777);
+ TEST_MODE("777", 0040777, 0040777);
+
+ TEST_MODE("700", 0, 0700);
+ TEST_MODE("700", 0040777, 0040700);
+
+ TEST_MODE("7777", 0, 07777);
+ TEST_MODE("7777", 0040777, 0047777);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, empty);
+ ATF_TP_ADD_TC(tp, null_str);
+ ATF_TP_ADD_TC(tp, sole_comma);
+
+ ATF_TP_ADD_TC(tp, add_read);
+ ATF_TP_ADD_TC(tp, set_read);
+ ATF_TP_ADD_TC(tp, del_read);
+
+ ATF_TP_ADD_TC(tp, search);
+
+ ATF_TP_ADD_TC(tp, syntax);
+ ATF_TP_ADD_TC(tp, posix_examples);
+
+ ATF_TP_ADD_TC(tp, non_symbolic);
+
+ return atf_no_error();
+}