summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Kaivo <jkk@ung.org>2020-09-14 12:43:13 -0400
committerJakob Kaivo <jkk@ung.org>2020-09-14 12:43:13 -0400
commit7930ea3e04e33f58ca5942c8b176b8d393ad89e2 (patch)
tree70e98f5a7c7ef2a3616120a0225d9b49bf6b9459
basic implementation
-rw-r--r--test.c313
1 files changed, 313 insertions, 0 deletions
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..7736d2d
--- /dev/null
+++ b/test.c
@@ -0,0 +1,313 @@
+/*
+ * UNG's Not GNU
+ *
+ * Copyright (c) 2020, Jakob Kaivo <jkk@ung.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define _XOPEN_SOURCE 700
+#include <inttypes.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+struct test_arg {
+ const char *s;
+ intmax_t n;
+ struct stat *st;
+};
+
+enum test_arg_type { PATHNAME, STRING, FILE_DESCRIPTOR, NUMBER };
+
+struct binary_op {
+ const char *op;
+ enum test_arg_type type;
+ int (*fn)(struct test_arg a, struct test_arg b);
+};
+
+struct unary_op {
+ const char *op;
+ enum test_arg_type type;
+ int (*fn)(struct test_arg a);
+};
+
+static int test_b(struct test_arg a)
+{
+ return a.st && S_ISBLK(a.st->st_mode) ? 0 : 1;
+}
+
+static int test_c(struct test_arg a)
+{
+ return a.st && S_ISCHR(a.st->st_mode) ? 0 : 1;
+}
+
+static int test_d(struct test_arg a)
+{
+ return a.st && S_ISDIR(a.st->st_mode) ? 0 : 1;
+}
+
+static int test_e(struct test_arg a)
+{
+ return a.st ? 0 : 1;
+}
+
+static int test_f(struct test_arg a)
+{
+ return a.st && S_ISREG(a.st->st_mode) ? 0 : 1;
+}
+
+static int test_g(struct test_arg a)
+{
+ return a.st && ((a.st->st_mode & S_ISGID) == S_ISGID) ? 0 : 1;
+}
+
+static int test_h(struct test_arg a)
+{
+ return a.st && S_ISLNK(a.st->st_mode) ? 0 : 1;
+}
+
+static int test_n(struct test_arg a)
+{
+ return strlen(a.s) != 0 ? 0 : 1;
+}
+
+static int test_p(struct test_arg a)
+{
+ return a.st && S_ISFIFO(a.st->st_mode) ? 0 : 1;
+}
+
+static int test_r(struct test_arg a)
+{
+ return access(a.s, R_OK) == 0 ? 0 : 1;
+}
+
+static int test_S(struct test_arg a)
+{
+ return a.st && S_ISSOCK(a.st->st_mode) ? 0 : 1;
+}
+
+static int test_s(struct test_arg a)
+{
+ return a.st && a.st->st_size > 0 ? 0 : 1;
+}
+
+static int test_t(struct test_arg a)
+{
+ return isatty((int)a.n);
+}
+
+static int test_u(struct test_arg a)
+{
+ return a.st && ((a.st->st_mode & S_ISUID) == S_ISUID) ? 0 : 1;
+}
+
+static int test_w(struct test_arg a)
+{
+ return access(a.s, W_OK) == 0 ? 0 : 1;
+}
+
+static int test_x(struct test_arg a)
+{
+ return access(a.s, X_OK) == 0 ? 0 : 1;
+}
+
+static int test_z(struct test_arg a)
+{
+ return strlen(a.s) == 0 ? 0 : 1;
+}
+
+static int test_seq(struct test_arg a, struct test_arg b)
+{
+ return strcmp(a.s, b.s) == 0 ? 0 : 1;
+}
+
+static int test_sne(struct test_arg a, struct test_arg b)
+{
+ return strcmp(a.s, b.s) != 0 ? 0 : 1;
+}
+
+static int test_eq(struct test_arg a, struct test_arg b)
+{
+ return a.n == b.n ? 0 : 1;
+}
+
+static int test_ne(struct test_arg a, struct test_arg b)
+{
+ return a.n != b.n ? 0 : 1;
+}
+
+static int test_gt(struct test_arg a, struct test_arg b)
+{
+ return a.n > b.n ? 0 : 1;
+}
+
+static int test_ge(struct test_arg a, struct test_arg b)
+{
+ return a.n >= b.n ? 0 : 1;
+}
+
+static int test_lt(struct test_arg a, struct test_arg b)
+{
+ return a.n < b.n ? 0 : 1;
+}
+
+static int test_le(struct test_arg a, struct test_arg b)
+{
+ return a.n <= b.n ? 0 : 1;
+}
+
+static struct binary_op bin_ops[] = {
+ { "=", STRING, test_seq },
+ { "!=", STRING, test_sne },
+ { "-eq", NUMBER, test_eq },
+ { "-ne", NUMBER, test_ne },
+ { "-gt", NUMBER, test_gt },
+ { "-ge", NUMBER, test_ge },
+ { "-lt", NUMBER, test_lt },
+ { "-le", NUMBER, test_le },
+};
+
+static struct unary_op un_ops[] = {
+ { "-b", PATHNAME, test_b },
+ { "-c", PATHNAME, test_c },
+ { "-d", PATHNAME, test_d },
+ { "-e", PATHNAME, test_e },
+ { "-f", PATHNAME, test_f },
+ { "-g", PATHNAME, test_g },
+ { "-h", PATHNAME, test_h },
+ { "-L", PATHNAME, test_h }, /* same as -h */
+ { "-n", STRING, test_n },
+ { "-p", PATHNAME, test_p },
+ { "-r", PATHNAME, test_r },
+ { "-S", PATHNAME, test_S },
+ { "-s", PATHNAME, test_s },
+ { "-t", FILE_DESCRIPTOR, test_t },
+ { "-u", PATHNAME, test_u },
+ { "-w", PATHNAME, test_w },
+ { "-x", PATHNAME, test_x },
+ { "-z", STRING, test_z },
+ /* { "", STRING, test_n }, */
+};
+
+static struct test_arg test_arg(const char *s, enum test_arg_type type)
+{
+ struct test_arg a = { 0 };
+ a.s = s;
+
+ if (type == FILE_DESCRIPTOR || type == NUMBER) {
+ char *end = NULL;
+ a.n = strtoimax(a.s, &end, 0);
+ if (end && *end != '\0') {
+ fprintf(stderr, "test: error converting %s to integer\n", a.s);
+ exit(2);
+ }
+ }
+
+ if (type == FILE_DESCRIPTOR && a.n > INT_MAX) {
+ fprintf(stderr, "test: invalid file descriptor %jd\n", a.n);
+ exit(2);
+ }
+
+ if (type == PATHNAME) {
+ static struct stat st;
+ if (stat(a.s, &st) == 0) {
+ a.st = &st;
+ } else {
+ a.st = NULL;
+ }
+ }
+
+ return a;
+}
+
+static int test_binary(const char *base, char *argv[], int first)
+{
+ for (size_t i = 0; i < sizeof(bin_ops) / sizeof(bin_ops[0]); i++) {
+ if (strcmp(argv[first + 1], bin_ops[i].op) == 0) {
+ struct test_arg a = test_arg(argv[first], bin_ops[i].type);
+ struct test_arg b = test_arg(argv[first + 2], bin_ops[i].type);
+ return bin_ops[i].fn(a, b);
+ }
+ }
+
+ fprintf(stderr, "%s: invalid binary operation '%s'\n", base, argv[first + 1]);
+ exit(2);
+}
+
+static int test_unary(const char *base, char *argv[], int first)
+{
+ for (size_t i = 0; i < sizeof(un_ops) / sizeof(un_ops[0]); i++) {
+ if (strcmp(argv[first], un_ops[i].op) == 0) {
+ struct test_arg a = test_arg(argv[first + 1], un_ops[i].type);
+ return un_ops[i].fn(a);
+ }
+ }
+
+ fprintf(stderr, "%s: invalid unary operations '%s'\n", base, argv[first]);
+ exit(2);
+}
+
+int main(int argc, char *argv[])
+{
+ int negate = 0;
+ int ret = 0;
+ int first = 1;
+ char *base = basename(argv[0]);
+
+ setlocale(LC_ALL, "");
+ if (strcmp(base, "[") == 0) {
+ if (strcmp(argv[argc - 1], "]") != 0) {
+ fprintf(stderr, "[: missing ]\n");
+ return 2;
+ }
+ argc--;
+ }
+
+ /* TODO: handle cases of (), -a, and -o, while providing warnings */
+
+ if (argc > first && strcmp(argv[first], "!") == 0) {
+ negate = 1;
+ first++;
+ }
+
+ if (argc - first > 3) {
+ fprintf(stderr, "%s: too many operands\n", base);
+ return 2;
+ } else if (argc - first == 3) {
+ ret = test_binary(base, argv, first);
+ } else if (argc - first == 2) {
+ ret = test_unary(base, argv, first);
+ } else if (argc - first == 1) {
+ struct test_arg a = test_arg(argv[first], STRING);
+ ret = test_n(a);
+ } else {
+ ret = 1;
+ }
+
+ if (negate) {
+ ret = ret ? 0 : 1;
+ }
+ return ret;
+}