123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- /*
- * Copyright (c) 2020, the SerenityOS developers.
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <AK/Assertions.h>
- #include <AK/LexicalPath.h>
- #include <AK/NonnullOwnPtr.h>
- #include <AK/OwnPtr.h>
- #include <LibCore/File.h>
- #include <getopt.h>
- #include <stdio.h>
- #include <sys/stat.h>
- #include <unistd.h>
- bool g_there_was_an_error = false;
- [[noreturn]] static void fatal_error(const char* format, ...)
- {
- fputs("\033[31m", stderr);
- va_list ap;
- va_start(ap, format);
- vfprintf(stderr, format, ap);
- va_end(ap);
- fputs("\033[0m\n", stderr);
- exit(126);
- }
- class Condition {
- public:
- virtual ~Condition() { }
- virtual bool check() const = 0;
- };
- class And : public Condition {
- public:
- And(NonnullOwnPtr<Condition> lhs, NonnullOwnPtr<Condition> rhs)
- : m_lhs(move(lhs))
- , m_rhs(move(rhs))
- {
- }
- private:
- virtual bool check() const override
- {
- return m_lhs->check() && m_rhs->check();
- }
- NonnullOwnPtr<Condition> m_lhs;
- NonnullOwnPtr<Condition> m_rhs;
- };
- class Or : public Condition {
- public:
- Or(NonnullOwnPtr<Condition> lhs, NonnullOwnPtr<Condition> rhs)
- : m_lhs(move(lhs))
- , m_rhs(move(rhs))
- {
- }
- private:
- virtual bool check() const override
- {
- return m_lhs->check() || m_rhs->check();
- }
- NonnullOwnPtr<Condition> m_lhs;
- NonnullOwnPtr<Condition> m_rhs;
- };
- class Not : public Condition {
- public:
- Not(NonnullOwnPtr<Condition> cond)
- : m_cond(move(cond))
- {
- }
- private:
- virtual bool check() const override
- {
- return !m_cond->check();
- }
- NonnullOwnPtr<Condition> m_cond;
- };
- class FileIsOfKind : public Condition {
- public:
- enum Kind {
- BlockDevice,
- CharacterDevice,
- Directory,
- FIFO,
- Regular,
- Socket,
- SymbolicLink,
- };
- FileIsOfKind(StringView path, Kind kind)
- : m_path(path)
- , m_kind(kind)
- {
- }
- private:
- virtual bool check() const override
- {
- struct stat statbuf;
- int rc;
- if (m_kind == SymbolicLink)
- rc = stat(m_path.characters(), &statbuf);
- else
- rc = lstat(m_path.characters(), &statbuf);
- if (rc < 0) {
- if (errno != ENOENT) {
- perror(m_path.characters());
- g_there_was_an_error = true;
- }
- return false;
- }
- switch (m_kind) {
- case BlockDevice:
- return S_ISBLK(statbuf.st_mode);
- case CharacterDevice:
- return S_ISCHR(statbuf.st_mode);
- case Directory:
- return S_ISDIR(statbuf.st_mode);
- case FIFO:
- return S_ISFIFO(statbuf.st_mode);
- case Regular:
- return S_ISREG(statbuf.st_mode);
- case Socket:
- return S_ISSOCK(statbuf.st_mode);
- case SymbolicLink:
- return S_ISLNK(statbuf.st_mode);
- default:
- VERIFY_NOT_REACHED();
- }
- }
- String m_path;
- Kind m_kind { Regular };
- };
- class UserHasPermission : public Condition {
- public:
- enum Permission {
- Any,
- Read,
- Write,
- Execute,
- };
- UserHasPermission(StringView path, Permission kind)
- : m_path(path)
- , m_kind(kind)
- {
- }
- private:
- virtual bool check() const override
- {
- switch (m_kind) {
- case Read:
- return access(m_path.characters(), R_OK) == 0;
- case Write:
- return access(m_path.characters(), W_OK) == 0;
- case Execute:
- return access(m_path.characters(), X_OK) == 0;
- case Any:
- return access(m_path.characters(), F_OK) == 0;
- default:
- VERIFY_NOT_REACHED();
- }
- }
- String m_path;
- Permission m_kind { Read };
- };
- class StringCompare : public Condition {
- public:
- enum Mode {
- Equal,
- NotEqual,
- };
- StringCompare(StringView lhs, StringView rhs, Mode mode)
- : m_lhs(move(lhs))
- , m_rhs(move(rhs))
- , m_mode(mode)
- {
- }
- private:
- virtual bool check() const override
- {
- if (m_mode == Equal)
- return m_lhs == m_rhs;
- return m_lhs != m_rhs;
- }
- StringView m_lhs;
- StringView m_rhs;
- Mode m_mode { Equal };
- };
- class NumericCompare : public Condition {
- public:
- enum Mode {
- Equal,
- Greater,
- GreaterOrEqual,
- Less,
- LessOrEqual,
- NotEqual,
- };
- NumericCompare(String lhs, String rhs, Mode mode)
- : m_mode(mode)
- {
- auto lhs_option = lhs.trim_whitespace().to_int();
- auto rhs_option = rhs.trim_whitespace().to_int();
- if (!lhs_option.has_value())
- fatal_error("expected integer expression: '%s'", lhs.characters());
- if (!rhs_option.has_value())
- fatal_error("expected integer expression: '%s'", rhs.characters());
- m_lhs = lhs_option.value();
- m_rhs = rhs_option.value();
- }
- private:
- virtual bool check() const override
- {
- switch (m_mode) {
- case Equal:
- return m_lhs == m_rhs;
- case Greater:
- return m_lhs > m_rhs;
- case GreaterOrEqual:
- return m_lhs >= m_rhs;
- case Less:
- return m_lhs < m_rhs;
- case LessOrEqual:
- return m_lhs <= m_rhs;
- case NotEqual:
- return m_lhs != m_rhs;
- default:
- VERIFY_NOT_REACHED();
- }
- }
- int m_lhs { 0 };
- int m_rhs { 0 };
- Mode m_mode { Equal };
- };
- class FileCompare : public Condition {
- public:
- enum Mode {
- Same,
- ModificationTimestampGreater,
- ModificationTimestampLess,
- };
- FileCompare(String lhs, String rhs, Mode mode)
- : m_lhs(move(lhs))
- , m_rhs(move(rhs))
- , m_mode(mode)
- {
- }
- private:
- virtual bool check() const override
- {
- struct stat statbuf_l;
- int rc = stat(m_lhs.characters(), &statbuf_l);
- if (rc < 0) {
- perror(m_lhs.characters());
- g_there_was_an_error = true;
- return false;
- }
- struct stat statbuf_r;
- rc = stat(m_rhs.characters(), &statbuf_r);
- if (rc < 0) {
- perror(m_rhs.characters());
- g_there_was_an_error = true;
- return false;
- }
- switch (m_mode) {
- case Same:
- return statbuf_l.st_dev == statbuf_r.st_dev && statbuf_l.st_ino == statbuf_r.st_ino;
- case ModificationTimestampLess:
- return statbuf_l.st_mtime < statbuf_r.st_mtime;
- case ModificationTimestampGreater:
- return statbuf_l.st_mtime > statbuf_r.st_mtime;
- default:
- VERIFY_NOT_REACHED();
- }
- }
- String m_lhs;
- String m_rhs;
- Mode m_mode { Same };
- };
- static OwnPtr<Condition> parse_complex_expression(char* argv[]);
- static bool should_treat_expression_as_single_string(const StringView& arg_after)
- {
- return arg_after.is_null() || arg_after == "-a" || arg_after == "-o";
- }
- static OwnPtr<Condition> parse_simple_expression(char* argv[])
- {
- StringView arg = argv[optind];
- if (arg.is_null()) {
- return {};
- }
- if (arg == "(") {
- optind++;
- auto command = parse_complex_expression(argv);
- if (command && argv[optind] && StringView(argv[++optind]) == ")")
- return command;
- fatal_error("Unmatched \033[1m(");
- }
- // Try to read a unary op.
- if (arg.starts_with('-') && arg.length() == 2) {
- optind++;
- if (should_treat_expression_as_single_string(argv[optind])) {
- --optind;
- return make<StringCompare>(move(arg), "", StringCompare::NotEqual);
- }
- StringView value = argv[optind];
- switch (arg[1]) {
- case 'b':
- return make<FileIsOfKind>(value, FileIsOfKind::BlockDevice);
- case 'c':
- return make<FileIsOfKind>(value, FileIsOfKind::CharacterDevice);
- case 'd':
- return make<FileIsOfKind>(value, FileIsOfKind::Directory);
- case 'f':
- return make<FileIsOfKind>(value, FileIsOfKind::Regular);
- case 'h':
- case 'L':
- return make<FileIsOfKind>(value, FileIsOfKind::SymbolicLink);
- case 'p':
- return make<FileIsOfKind>(value, FileIsOfKind::FIFO);
- case 'S':
- return make<FileIsOfKind>(value, FileIsOfKind::Socket);
- case 'r':
- return make<UserHasPermission>(value, UserHasPermission::Read);
- case 'w':
- return make<UserHasPermission>(value, UserHasPermission::Write);
- case 'x':
- return make<UserHasPermission>(value, UserHasPermission::Execute);
- case 'e':
- return make<UserHasPermission>(value, UserHasPermission::Any);
- case 'o':
- case 'a':
- // '-a' and '-o' are boolean ops, which are part of a complex expression
- // so we have nothing to parse, simply return to caller.
- --optind;
- return {};
- case 'n':
- return make<StringCompare>("", value, StringCompare::NotEqual);
- case 'z':
- return make<StringCompare>("", value, StringCompare::Equal);
- case 'g':
- case 'G':
- case 'k':
- case 'N':
- case 'O':
- case 's':
- fatal_error("Unsupported operator \033[1m%s", argv[optind]);
- default:
- --optind;
- break;
- }
- }
- // Try to read a binary op, this is either a <string> op <string>, <integer> op <integer>, or <file> op <file>.
- auto lhs = arg;
- arg = argv[++optind];
- if (arg == "=") {
- StringView rhs = argv[++optind];
- return make<StringCompare>(lhs, rhs, StringCompare::Equal);
- } else if (arg == "!=") {
- StringView rhs = argv[++optind];
- return make<StringCompare>(lhs, rhs, StringCompare::NotEqual);
- } else if (arg == "-eq") {
- StringView rhs = argv[++optind];
- return make<NumericCompare>(lhs, rhs, NumericCompare::Equal);
- } else if (arg == "-ge") {
- StringView rhs = argv[++optind];
- return make<NumericCompare>(lhs, rhs, NumericCompare::GreaterOrEqual);
- } else if (arg == "-gt") {
- StringView rhs = argv[++optind];
- return make<NumericCompare>(lhs, rhs, NumericCompare::Greater);
- } else if (arg == "-le") {
- StringView rhs = argv[++optind];
- return make<NumericCompare>(lhs, rhs, NumericCompare::LessOrEqual);
- } else if (arg == "-lt") {
- StringView rhs = argv[++optind];
- return make<NumericCompare>(lhs, rhs, NumericCompare::Less);
- } else if (arg == "-ne") {
- StringView rhs = argv[++optind];
- return make<NumericCompare>(lhs, rhs, NumericCompare::NotEqual);
- } else if (arg == "-ef") {
- StringView rhs = argv[++optind];
- return make<FileCompare>(lhs, rhs, FileCompare::Same);
- } else if (arg == "-nt") {
- StringView rhs = argv[++optind];
- return make<FileCompare>(lhs, rhs, FileCompare::ModificationTimestampGreater);
- } else if (arg == "-ot") {
- StringView rhs = argv[++optind];
- return make<FileCompare>(lhs, rhs, FileCompare::ModificationTimestampLess);
- } else if (arg == "-o" || arg == "-a") {
- // '-a' and '-o' are boolean ops, which are part of a complex expression
- // put them back and return with lhs as string compare.
- --optind;
- return make<StringCompare>("", lhs, StringCompare::NotEqual);
- } else {
- // Now that we know it's not a well-formed expression, see if it's actually a negation
- if (lhs == "!") {
- if (should_treat_expression_as_single_string(arg))
- return make<StringCompare>(move(lhs), "", StringCompare::NotEqual);
- auto command = parse_complex_expression(argv);
- if (!command)
- fatal_error("Expected an expression after \x1b[1m!");
- return make<Not>(command.release_nonnull());
- }
- --optind;
- return make<StringCompare>("", lhs, StringCompare::NotEqual);
- }
- }
- static OwnPtr<Condition> parse_complex_expression(char* argv[])
- {
- auto command = parse_simple_expression(argv);
- while (argv[optind] && argv[optind + 1]) {
- if (!command && argv[optind])
- fatal_error("expected an expression");
- StringView arg = argv[++optind];
- enum {
- AndOp,
- OrOp,
- } binary_operation { AndOp };
- if (arg == "-a") {
- optind++;
- binary_operation = AndOp;
- } else if (arg == "-o") {
- optind++;
- binary_operation = OrOp;
- } else {
- // Ooops, looked too far.
- optind--;
- return command;
- }
- auto rhs = parse_complex_expression(argv);
- if (!rhs)
- fatal_error("Missing right-hand side");
- if (binary_operation == AndOp)
- command = make<And>(command.release_nonnull(), rhs.release_nonnull());
- else
- command = make<Or>(command.release_nonnull(), rhs.release_nonnull());
- }
- return command;
- }
- int main(int argc, char* argv[])
- {
- if (pledge("stdio rpath", nullptr) < 0) {
- perror("pledge");
- return 126;
- }
- if (LexicalPath { argv[0] }.basename() == "[") {
- --argc;
- if (StringView { argv[argc] } != "]")
- fatal_error("test invoked as '[' requires a closing bracket ']'");
- argv[argc] = nullptr;
- }
- // Exit false when no arguments are given.
- if (argc == 1)
- return 1;
- auto condition = parse_complex_expression(argv);
- if (optind != argc - 1)
- fatal_error("Too many arguments");
- auto result = condition ? condition->check() : false;
- if (g_there_was_an_error)
- return 126;
- return result ? 0 : 1;
- }
|