diff options
author | Jakob Kaivo <jkk@ung.org> | 2020-09-14 12:43:13 -0400 |
---|---|---|
committer | Jakob Kaivo <jkk@ung.org> | 2020-09-14 12:43:13 -0400 |
commit | 7930ea3e04e33f58ca5942c8b176b8d393ad89e2 (patch) | |
tree | 70e98f5a7c7ef2a3616120a0225d9b49bf6b9459 |
basic implementation
-rw-r--r-- | test.c | 313 |
1 files changed, 313 insertions, 0 deletions
@@ -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; +} |