summaryrefslogtreecommitdiff
path: root/corebinutils/test/test.c
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:29:55 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:29:55 +0300
commit22343fc2bb8db94066d3e314c5a20a9b9278f4c9 (patch)
treed2a731892b60e63970796b567a6a2dd7eb209427 /corebinutils/test/test.c
parent4cf86e37e42f9f04ec4a41a28ffe466d2510438a (diff)
parentceefe27a76f3b2075abbf01b0c44375363967af6 (diff)
downloadProject-Tick-22343fc2bb8db94066d3e314c5a20a9b9278f4c9.tar.gz
Project-Tick-22343fc2bb8db94066d3e314c5a20a9b9278f4c9.zip
Add 'corebinutils/test/' from commit 'ceefe27a76f3b2075abbf01b0c44375363967af6'
git-subtree-dir: corebinutils/test git-subtree-mainline: 4cf86e37e42f9f04ec4a41a28ffe466d2510438a git-subtree-split: ceefe27a76f3b2075abbf01b0c44375363967af6
Diffstat (limited to 'corebinutils/test/test.c')
-rw-r--r--corebinutils/test/test.c679
1 files changed, 679 insertions, 0 deletions
diff --git a/corebinutils/test/test.c b/corebinutils/test/test.c
new file mode 100644
index 0000000000..8159a9f0fd
--- /dev/null
+++ b/corebinutils/test/test.c
@@ -0,0 +1,679 @@
+/*
+
+SPDX-License-Identifier: BSD-3-Clause
+
+Copyright (c) 2026
+ Project Tick. All rights reserved.
+
+This code is derived from software contributed to Berkeley by
+the Institute of Electrical and Electronics Engineers, Inc.
+
+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/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+enum token_type {
+ TOKEN_UNARY = 0x100,
+ TOKEN_BINARY = 0x200,
+ TOKEN_BOOLEAN_UNARY = 0x300,
+ TOKEN_BOOLEAN_BINARY = 0x400,
+ TOKEN_PAREN = 0x500
+};
+
+enum token {
+ TOKEN_EOI,
+ TOKEN_OPERAND,
+ TOKEN_FILRD = TOKEN_UNARY + 1,
+ TOKEN_FILWR,
+ TOKEN_FILEX,
+ TOKEN_FILEXIST,
+ TOKEN_FILREG,
+ TOKEN_FILDIR,
+ TOKEN_FILCDEV,
+ TOKEN_FILBDEV,
+ TOKEN_FILFIFO,
+ TOKEN_FILSOCK,
+ TOKEN_FILSYM,
+ TOKEN_FILGZ,
+ TOKEN_FILTT,
+ TOKEN_FILSUID,
+ TOKEN_FILSGID,
+ TOKEN_FILSTCK,
+ TOKEN_STREZ,
+ TOKEN_STRNZ,
+ TOKEN_FILUID,
+ TOKEN_FILGID,
+ TOKEN_FILNT = TOKEN_BINARY + 1,
+ TOKEN_FILOT,
+ TOKEN_FILEQ,
+ TOKEN_STREQ,
+ TOKEN_STRNE,
+ TOKEN_STRLT,
+ TOKEN_STRGT,
+ TOKEN_INTEQ,
+ TOKEN_INTNE,
+ TOKEN_INTGE,
+ TOKEN_INTGT,
+ TOKEN_INTLE,
+ TOKEN_INTLT,
+ TOKEN_UNOT = TOKEN_BOOLEAN_UNARY + 1,
+ TOKEN_BAND = TOKEN_BOOLEAN_BINARY + 1,
+ TOKEN_BOR,
+ TOKEN_LPAREN = TOKEN_PAREN + 1,
+ TOKEN_RPAREN
+};
+
+#define TOKEN_FAMILY(token) ((token) & 0xff00)
+
+struct operator {
+ const char *text;
+ enum token token;
+};
+
+struct parser {
+ char **argv;
+ int pos;
+ int remaining;
+ int paren_level;
+};
+
+static const struct operator ops_single[] = {
+ {"=", TOKEN_STREQ},
+ {"<", TOKEN_STRLT},
+ {">", TOKEN_STRGT},
+ {"!", TOKEN_UNOT},
+ {"(", TOKEN_LPAREN},
+ {")", TOKEN_RPAREN},
+};
+
+static const struct operator ops_dash_single[] = {
+ {"r", TOKEN_FILRD},
+ {"w", TOKEN_FILWR},
+ {"x", TOKEN_FILEX},
+ {"e", TOKEN_FILEXIST},
+ {"f", TOKEN_FILREG},
+ {"d", TOKEN_FILDIR},
+ {"c", TOKEN_FILCDEV},
+ {"b", TOKEN_FILBDEV},
+ {"p", TOKEN_FILFIFO},
+ {"u", TOKEN_FILSUID},
+ {"g", TOKEN_FILSGID},
+ {"k", TOKEN_FILSTCK},
+ {"s", TOKEN_FILGZ},
+ {"t", TOKEN_FILTT},
+ {"z", TOKEN_STREZ},
+ {"n", TOKEN_STRNZ},
+ {"h", TOKEN_FILSYM},
+ {"O", TOKEN_FILUID},
+ {"G", TOKEN_FILGID},
+ {"L", TOKEN_FILSYM},
+ {"S", TOKEN_FILSOCK},
+ {"a", TOKEN_BAND},
+ {"o", TOKEN_BOR},
+};
+
+static const struct operator ops_double[] = {
+ {"==", TOKEN_STREQ},
+ {"!=", TOKEN_STRNE},
+};
+
+static const struct operator ops_dash_double[] = {
+ {"eq", TOKEN_INTEQ},
+ {"ne", TOKEN_INTNE},
+ {"ge", TOKEN_INTGE},
+ {"gt", TOKEN_INTGT},
+ {"le", TOKEN_INTLE},
+ {"lt", TOKEN_INTLT},
+ {"nt", TOKEN_FILNT},
+ {"ot", TOKEN_FILOT},
+ {"ef", TOKEN_FILEQ},
+};
+
+static const char *program_name = "test";
+
+__attribute__((format(printf, 1, 2), noreturn))
+static void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ fprintf(stderr, "%s: ", program_name);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+ va_end(ap);
+ exit(2);
+}
+
+static void
+syntax_error(const char *op, const char *msg)
+{
+ if (op != NULL && *op != '\0')
+ die("%s: %s", op, msg);
+ die("%s", msg);
+}
+
+static const char *
+base_name(const char *path)
+{
+ const char *slash;
+
+ slash = strrchr(path, '/');
+ return slash == NULL ? path : slash + 1;
+}
+
+static const char *
+current_arg(const struct parser *parser)
+{
+ if (parser->remaining <= 0)
+ return NULL;
+ return parser->argv[parser->pos];
+}
+
+static const char *
+peek_arg(const struct parser *parser, int offset)
+{
+ if (offset < 0 || offset >= parser->remaining)
+ return NULL;
+ return parser->argv[parser->pos + offset];
+}
+
+static const char *
+advance_arg(struct parser *parser)
+{
+ if (parser->remaining > 0) {
+ parser->pos++;
+ parser->remaining--;
+ }
+ return current_arg(parser);
+}
+
+static void
+rewind_arg(struct parser *parser)
+{
+ if (parser->pos > 0) {
+ parser->pos--;
+ parser->remaining++;
+ }
+}
+
+static enum token
+lookup_operator(const struct operator *ops, size_t count, const char *text)
+{
+ size_t i;
+
+ for (i = 0; i < count; i++) {
+ if (strcmp(text, ops[i].text) == 0)
+ return ops[i].token;
+ }
+ return TOKEN_OPERAND;
+}
+
+static enum token
+find_operator(const char *text)
+{
+ size_t length;
+
+ if (text == NULL || text[0] == '\0')
+ return TOKEN_OPERAND;
+
+ length = strlen(text);
+ if (length == 1)
+ return lookup_operator(ops_single,
+ sizeof(ops_single) / sizeof(ops_single[0]), text);
+ if (length == 2) {
+ if (text[0] == '-')
+ return lookup_operator(ops_dash_single,
+ sizeof(ops_dash_single) / sizeof(ops_dash_single[0]),
+ text + 1);
+ return lookup_operator(ops_double,
+ sizeof(ops_double) / sizeof(ops_double[0]), text);
+ }
+ if (length == 3 && text[0] == '-') {
+ return lookup_operator(ops_dash_double,
+ sizeof(ops_dash_double) / sizeof(ops_dash_double[0]),
+ text + 1);
+ }
+ return TOKEN_OPERAND;
+}
+
+static bool
+is_unary_operand(const struct parser *parser)
+{
+ const char *next;
+ const char *after_next;
+ enum token next_token;
+
+ if (parser->remaining == 1)
+ return true;
+
+ next = peek_arg(parser, 1);
+ if (parser->remaining == 2)
+ return parser->paren_level == 1 && strcmp(next, ")") == 0;
+
+ after_next = peek_arg(parser, 2);
+ next_token = find_operator(next);
+ return TOKEN_FAMILY(next_token) == TOKEN_BINARY &&
+ (parser->paren_level == 0 || strcmp(after_next, ")") != 0);
+}
+
+static bool
+is_left_paren_operand(const struct parser *parser)
+{
+ const char *next;
+ enum token next_token;
+
+ if (parser->remaining == 1)
+ return true;
+
+ next = peek_arg(parser, 1);
+ if (parser->remaining == 2)
+ return parser->paren_level == 1 && strcmp(next, ")") == 0;
+ if (parser->remaining != 3)
+ return false;
+
+ next_token = find_operator(next);
+ return TOKEN_FAMILY(next_token) == TOKEN_BINARY;
+}
+
+static bool
+is_right_paren_operand(const struct parser *parser)
+{
+ const char *next;
+
+ if (parser->remaining == 1)
+ return false;
+
+ next = peek_arg(parser, 1);
+ if (parser->remaining == 2)
+ return parser->paren_level == 1 && strcmp(next, ")") == 0;
+ return false;
+}
+
+static enum token
+lex_token(struct parser *parser, const char *text)
+{
+ enum token token;
+
+ if (text == NULL)
+ return TOKEN_EOI;
+
+ token = find_operator(text);
+ if (((TOKEN_FAMILY(token) == TOKEN_UNARY ||
+ TOKEN_FAMILY(token) == TOKEN_BOOLEAN_UNARY) &&
+ is_unary_operand(parser)) ||
+ (token == TOKEN_LPAREN && is_left_paren_operand(parser)) ||
+ (token == TOKEN_RPAREN && is_right_paren_operand(parser))) {
+ return TOKEN_OPERAND;
+ }
+ return token;
+}
+
+static int
+parse_int(const char *text)
+{
+ char *end;
+ intmax_t value;
+
+ errno = 0;
+ value = strtoimax(text, &end, 10);
+ if (end == text)
+ die("%s: bad number", text);
+ if (errno == ERANGE || value < INT_MIN || value > INT_MAX)
+ die("%s: out of range", text);
+
+ while (*end != '\0' && isspace((unsigned char)*end))
+ end++;
+ if (*end != '\0')
+ die("%s: bad number", text);
+
+ return (int)value;
+}
+
+static intmax_t
+parse_intmax(const char *text)
+{
+ char *end;
+ intmax_t value;
+
+ errno = 0;
+ value = strtoimax(text, &end, 10);
+ if (end == text)
+ die("%s: bad number", text);
+ if (errno == ERANGE)
+ die("%s: out of range", text);
+
+ while (*end != '\0' && isspace((unsigned char)*end))
+ end++;
+ if (*end != '\0')
+ die("%s: bad number", text);
+
+ return value;
+}
+
+static int
+effective_access(const char *path, int mode)
+{
+ if (faccessat(AT_FDCWD, path, mode, AT_EACCESS) == 0)
+ return 0;
+ if (errno == EINVAL || errno == ENOSYS)
+ die("Linux effective access checks require faccessat(AT_EACCESS)");
+ return -1;
+}
+
+static int
+compare_mtime(const struct stat *lhs, const struct stat *rhs)
+{
+ if (lhs->st_mtim.tv_sec > rhs->st_mtim.tv_sec)
+ return 1;
+ if (lhs->st_mtim.tv_sec < rhs->st_mtim.tv_sec)
+ return -1;
+ if (lhs->st_mtim.tv_nsec > rhs->st_mtim.tv_nsec)
+ return 1;
+ if (lhs->st_mtim.tv_nsec < rhs->st_mtim.tv_nsec)
+ return -1;
+ return 0;
+}
+
+static int
+newer_file(const char *lhs, const char *rhs)
+{
+ struct stat lhs_stat;
+ struct stat rhs_stat;
+
+ if (stat(lhs, &lhs_stat) != 0 || stat(rhs, &rhs_stat) != 0)
+ return 0;
+ return compare_mtime(&lhs_stat, &rhs_stat) > 0;
+}
+
+static int
+older_file(const char *lhs, const char *rhs)
+{
+ return newer_file(rhs, lhs);
+}
+
+static int
+same_file(const char *lhs, const char *rhs)
+{
+ struct stat lhs_stat;
+ struct stat rhs_stat;
+
+ return stat(lhs, &lhs_stat) == 0 &&
+ stat(rhs, &rhs_stat) == 0 &&
+ lhs_stat.st_dev == rhs_stat.st_dev &&
+ lhs_stat.st_ino == rhs_stat.st_ino;
+}
+
+static int
+evaluate_file_test(const char *path, enum token token)
+{
+ struct stat st;
+ int stat_result;
+
+ stat_result = token == TOKEN_FILSYM ? lstat(path, &st) : stat(path, &st);
+ if (stat_result != 0)
+ return 0;
+
+ switch (token) {
+ case TOKEN_FILRD:
+ return effective_access(path, R_OK) == 0;
+ case TOKEN_FILWR:
+ return effective_access(path, W_OK) == 0;
+ case TOKEN_FILEX:
+ if (effective_access(path, X_OK) != 0)
+ return 0;
+ if (S_ISDIR(st.st_mode) || geteuid() != 0)
+ return 1;
+ return (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
+ case TOKEN_FILEXIST:
+ return 1;
+ case TOKEN_FILREG:
+ return S_ISREG(st.st_mode);
+ case TOKEN_FILDIR:
+ return S_ISDIR(st.st_mode);
+ case TOKEN_FILCDEV:
+ return S_ISCHR(st.st_mode);
+ case TOKEN_FILBDEV:
+ return S_ISBLK(st.st_mode);
+ case TOKEN_FILFIFO:
+ return S_ISFIFO(st.st_mode);
+ case TOKEN_FILSOCK:
+ return S_ISSOCK(st.st_mode);
+ case TOKEN_FILSYM:
+ return S_ISLNK(st.st_mode);
+ case TOKEN_FILSUID:
+ return (st.st_mode & S_ISUID) != 0;
+ case TOKEN_FILSGID:
+ return (st.st_mode & S_ISGID) != 0;
+ case TOKEN_FILSTCK:
+ return (st.st_mode & S_ISVTX) != 0;
+ case TOKEN_FILGZ:
+ return st.st_size > 0;
+ case TOKEN_FILUID:
+ return st.st_uid == geteuid();
+ case TOKEN_FILGID:
+ return st.st_gid == getegid();
+ default:
+ return 0;
+ }
+}
+
+static int
+compare_integers(const char *lhs, const char *rhs)
+{
+ intmax_t lhs_value;
+ intmax_t rhs_value;
+
+ lhs_value = parse_intmax(lhs);
+ rhs_value = parse_intmax(rhs);
+ if (lhs_value > rhs_value)
+ return 1;
+ if (lhs_value < rhs_value)
+ return -1;
+ return 0;
+}
+
+static int parse_oexpr(struct parser *parser, enum token token);
+
+static int
+parse_binop(struct parser *parser, enum token token)
+{
+ const char *lhs;
+ const char *op;
+ const char *rhs;
+
+ lhs = current_arg(parser);
+ advance_arg(parser);
+ op = current_arg(parser);
+ advance_arg(parser);
+ rhs = current_arg(parser);
+ if (rhs == NULL)
+ syntax_error(op, "argument expected");
+
+ switch (token) {
+ case TOKEN_STREQ:
+ return strcmp(lhs, rhs) == 0;
+ case TOKEN_STRNE:
+ return strcmp(lhs, rhs) != 0;
+ case TOKEN_STRLT:
+ return strcmp(lhs, rhs) < 0;
+ case TOKEN_STRGT:
+ return strcmp(lhs, rhs) > 0;
+ case TOKEN_INTEQ:
+ return compare_integers(lhs, rhs) == 0;
+ case TOKEN_INTNE:
+ return compare_integers(lhs, rhs) != 0;
+ case TOKEN_INTGE:
+ return compare_integers(lhs, rhs) >= 0;
+ case TOKEN_INTGT:
+ return compare_integers(lhs, rhs) > 0;
+ case TOKEN_INTLE:
+ return compare_integers(lhs, rhs) <= 0;
+ case TOKEN_INTLT:
+ return compare_integers(lhs, rhs) < 0;
+ case TOKEN_FILNT:
+ return newer_file(lhs, rhs);
+ case TOKEN_FILOT:
+ return older_file(lhs, rhs);
+ case TOKEN_FILEQ:
+ return same_file(lhs, rhs);
+ default:
+ abort();
+ }
+}
+
+static int
+parse_primary(struct parser *parser, enum token token)
+{
+ enum token next_token;
+ const char *operand;
+ int result;
+
+ if (token == TOKEN_EOI)
+ return 0;
+
+ if (token == TOKEN_LPAREN) {
+ parser->paren_level++;
+ next_token = lex_token(parser, advance_arg(parser));
+ if (next_token == TOKEN_RPAREN) {
+ parser->paren_level--;
+ return 0;
+ }
+ result = parse_oexpr(parser, next_token);
+ if (lex_token(parser, advance_arg(parser)) != TOKEN_RPAREN)
+ syntax_error(NULL, "closing paren expected");
+ parser->paren_level--;
+ return result;
+ }
+
+ if (TOKEN_FAMILY(token) == TOKEN_UNARY) {
+ if (parser->remaining <= 1)
+ syntax_error(NULL, "argument expected");
+ operand = advance_arg(parser);
+ switch (token) {
+ case TOKEN_STREZ:
+ return operand[0] == '\0';
+ case TOKEN_STRNZ:
+ return operand[0] != '\0';
+ case TOKEN_FILTT:
+ return isatty(parse_int(operand));
+ default:
+ return evaluate_file_test(operand, token);
+ }
+ }
+
+ next_token = lex_token(parser, peek_arg(parser, 1));
+ if (TOKEN_FAMILY(next_token) == TOKEN_BINARY)
+ return parse_binop(parser, next_token);
+
+ return current_arg(parser)[0] != '\0';
+}
+
+static int
+parse_nexpr(struct parser *parser, enum token token)
+{
+ if (token == TOKEN_UNOT)
+ return !parse_nexpr(parser, lex_token(parser, advance_arg(parser)));
+ return parse_primary(parser, token);
+}
+
+static int
+parse_aexpr(struct parser *parser, enum token token)
+{
+ int result;
+
+ result = parse_nexpr(parser, token);
+ if (lex_token(parser, advance_arg(parser)) == TOKEN_BAND)
+ return parse_aexpr(parser, lex_token(parser, advance_arg(parser))) &&
+ result;
+ rewind_arg(parser);
+ return result;
+}
+
+static int
+parse_oexpr(struct parser *parser, enum token token)
+{
+ int result;
+
+ result = parse_aexpr(parser, token);
+ if (lex_token(parser, advance_arg(parser)) == TOKEN_BOR)
+ return parse_oexpr(parser, lex_token(parser, advance_arg(parser))) ||
+ result;
+ rewind_arg(parser);
+ return result;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct parser parser;
+ int result;
+
+ program_name = base_name(argv[0]);
+ if (strcmp(program_name, "[") == 0) {
+ if (argc == 1 || strcmp(argv[argc - 1], "]") != 0)
+ die("missing ']'");
+ argc--;
+ argv[argc] = NULL;
+ }
+
+ if (argc <= 1)
+ return 1;
+
+ parser.argv = argv + 1;
+ parser.pos = 0;
+ parser.remaining = argc - 1;
+ parser.paren_level = 0;
+
+ if (parser.remaining == 4 && strcmp(current_arg(&parser), "!") == 0) {
+ advance_arg(&parser);
+ result = parse_oexpr(&parser,
+ lex_token(&parser, current_arg(&parser)));
+ } else {
+ result = !parse_oexpr(&parser,
+ lex_token(&parser, current_arg(&parser)));
+ }
+
+ advance_arg(&parser);
+ if (parser.remaining > 0)
+ syntax_error(current_arg(&parser), "unexpected operator");
+
+ return result;
+}