logo

utils-std

Collection of commonly available Unix tools
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:

M.gitignore6++++--
MMakefile12++++++++++--
Mlib/bitmasks.h2++
Alib/mode.c280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/mode.h8++++++++
Mtest-lib/Kyuafile1+
Atest-lib/mode.c354+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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(); +}