/* * UNG's Not GNU * * Copyright (c) 2020, Jakob Kaivo * * 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 #include #include #include #include #include #include #include #include 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 a.n < INT_MAX && isatty((int)a.n) ? 0 : 1; } 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 *base, 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, "%s: invalid integer '%s'\n", base, a.s); 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(base, argv[first], bin_ops[i].type); struct test_arg b = test_arg(base, argv[first + 2], bin_ops[i].type); return bin_ops[i].fn(a, b); } } fprintf(stderr, "%s: unknown 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(base, argv[first + 1], un_ops[i].type); return un_ops[i].fn(a); } } fprintf(stderr, "%s: unknown 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(base, argv[first], STRING); ret = test_n(a); } else { ret = 1; } if (negate) { ret = ret ? 0 : 1; } return ret; }