test.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Assertions.h>
  7. #include <AK/LexicalPath.h>
  8. #include <AK/NonnullOwnPtr.h>
  9. #include <AK/OwnPtr.h>
  10. #include <LibCore/File.h>
  11. #include <getopt.h>
  12. #include <stdio.h>
  13. #include <sys/stat.h>
  14. #include <unistd.h>
  15. bool g_there_was_an_error = false;
  16. [[noreturn]] static void fatal_error(const char* format, ...)
  17. {
  18. fputs("\033[31m", stderr);
  19. va_list ap;
  20. va_start(ap, format);
  21. vfprintf(stderr, format, ap);
  22. va_end(ap);
  23. fputs("\033[0m\n", stderr);
  24. exit(126);
  25. }
  26. class Condition {
  27. public:
  28. virtual ~Condition() { }
  29. virtual bool check() const = 0;
  30. };
  31. class And : public Condition {
  32. public:
  33. And(NonnullOwnPtr<Condition> lhs, NonnullOwnPtr<Condition> rhs)
  34. : m_lhs(move(lhs))
  35. , m_rhs(move(rhs))
  36. {
  37. }
  38. private:
  39. virtual bool check() const override
  40. {
  41. return m_lhs->check() && m_rhs->check();
  42. }
  43. NonnullOwnPtr<Condition> m_lhs;
  44. NonnullOwnPtr<Condition> m_rhs;
  45. };
  46. class Or : public Condition {
  47. public:
  48. Or(NonnullOwnPtr<Condition> lhs, NonnullOwnPtr<Condition> rhs)
  49. : m_lhs(move(lhs))
  50. , m_rhs(move(rhs))
  51. {
  52. }
  53. private:
  54. virtual bool check() const override
  55. {
  56. return m_lhs->check() || m_rhs->check();
  57. }
  58. NonnullOwnPtr<Condition> m_lhs;
  59. NonnullOwnPtr<Condition> m_rhs;
  60. };
  61. class Not : public Condition {
  62. public:
  63. Not(NonnullOwnPtr<Condition> cond)
  64. : m_cond(move(cond))
  65. {
  66. }
  67. private:
  68. virtual bool check() const override
  69. {
  70. return !m_cond->check();
  71. }
  72. NonnullOwnPtr<Condition> m_cond;
  73. };
  74. class FileIsOfKind : public Condition {
  75. public:
  76. enum Kind {
  77. BlockDevice,
  78. CharacterDevice,
  79. Directory,
  80. FIFO,
  81. Regular,
  82. Socket,
  83. SymbolicLink,
  84. };
  85. FileIsOfKind(StringView path, Kind kind)
  86. : m_path(path)
  87. , m_kind(kind)
  88. {
  89. }
  90. private:
  91. virtual bool check() const override
  92. {
  93. struct stat statbuf;
  94. int rc;
  95. if (m_kind == SymbolicLink)
  96. rc = stat(m_path.characters(), &statbuf);
  97. else
  98. rc = lstat(m_path.characters(), &statbuf);
  99. if (rc < 0) {
  100. if (errno != ENOENT) {
  101. perror(m_path.characters());
  102. g_there_was_an_error = true;
  103. }
  104. return false;
  105. }
  106. switch (m_kind) {
  107. case BlockDevice:
  108. return S_ISBLK(statbuf.st_mode);
  109. case CharacterDevice:
  110. return S_ISCHR(statbuf.st_mode);
  111. case Directory:
  112. return S_ISDIR(statbuf.st_mode);
  113. case FIFO:
  114. return S_ISFIFO(statbuf.st_mode);
  115. case Regular:
  116. return S_ISREG(statbuf.st_mode);
  117. case Socket:
  118. return S_ISSOCK(statbuf.st_mode);
  119. case SymbolicLink:
  120. return S_ISLNK(statbuf.st_mode);
  121. default:
  122. VERIFY_NOT_REACHED();
  123. }
  124. }
  125. String m_path;
  126. Kind m_kind { Regular };
  127. };
  128. class UserHasPermission : public Condition {
  129. public:
  130. enum Permission {
  131. Any,
  132. Read,
  133. Write,
  134. Execute,
  135. };
  136. UserHasPermission(StringView path, Permission kind)
  137. : m_path(path)
  138. , m_kind(kind)
  139. {
  140. }
  141. private:
  142. virtual bool check() const override
  143. {
  144. switch (m_kind) {
  145. case Read:
  146. return access(m_path.characters(), R_OK) == 0;
  147. case Write:
  148. return access(m_path.characters(), W_OK) == 0;
  149. case Execute:
  150. return access(m_path.characters(), X_OK) == 0;
  151. case Any:
  152. return access(m_path.characters(), F_OK) == 0;
  153. default:
  154. VERIFY_NOT_REACHED();
  155. }
  156. }
  157. String m_path;
  158. Permission m_kind { Read };
  159. };
  160. class StringCompare : public Condition {
  161. public:
  162. enum Mode {
  163. Equal,
  164. NotEqual,
  165. };
  166. StringCompare(StringView lhs, StringView rhs, Mode mode)
  167. : m_lhs(move(lhs))
  168. , m_rhs(move(rhs))
  169. , m_mode(mode)
  170. {
  171. }
  172. private:
  173. virtual bool check() const override
  174. {
  175. if (m_mode == Equal)
  176. return m_lhs == m_rhs;
  177. return m_lhs != m_rhs;
  178. }
  179. StringView m_lhs;
  180. StringView m_rhs;
  181. Mode m_mode { Equal };
  182. };
  183. class NumericCompare : public Condition {
  184. public:
  185. enum Mode {
  186. Equal,
  187. Greater,
  188. GreaterOrEqual,
  189. Less,
  190. LessOrEqual,
  191. NotEqual,
  192. };
  193. NumericCompare(String lhs, String rhs, Mode mode)
  194. : m_mode(mode)
  195. {
  196. auto lhs_option = lhs.trim_whitespace().to_int();
  197. auto rhs_option = rhs.trim_whitespace().to_int();
  198. if (!lhs_option.has_value())
  199. fatal_error("expected integer expression: '%s'", lhs.characters());
  200. if (!rhs_option.has_value())
  201. fatal_error("expected integer expression: '%s'", rhs.characters());
  202. m_lhs = lhs_option.value();
  203. m_rhs = rhs_option.value();
  204. }
  205. private:
  206. virtual bool check() const override
  207. {
  208. switch (m_mode) {
  209. case Equal:
  210. return m_lhs == m_rhs;
  211. case Greater:
  212. return m_lhs > m_rhs;
  213. case GreaterOrEqual:
  214. return m_lhs >= m_rhs;
  215. case Less:
  216. return m_lhs < m_rhs;
  217. case LessOrEqual:
  218. return m_lhs <= m_rhs;
  219. case NotEqual:
  220. return m_lhs != m_rhs;
  221. default:
  222. VERIFY_NOT_REACHED();
  223. }
  224. }
  225. int m_lhs { 0 };
  226. int m_rhs { 0 };
  227. Mode m_mode { Equal };
  228. };
  229. class FileCompare : public Condition {
  230. public:
  231. enum Mode {
  232. Same,
  233. ModificationTimestampGreater,
  234. ModificationTimestampLess,
  235. };
  236. FileCompare(String lhs, String rhs, Mode mode)
  237. : m_lhs(move(lhs))
  238. , m_rhs(move(rhs))
  239. , m_mode(mode)
  240. {
  241. }
  242. private:
  243. virtual bool check() const override
  244. {
  245. struct stat statbuf_l;
  246. int rc = stat(m_lhs.characters(), &statbuf_l);
  247. if (rc < 0) {
  248. perror(m_lhs.characters());
  249. g_there_was_an_error = true;
  250. return false;
  251. }
  252. struct stat statbuf_r;
  253. rc = stat(m_rhs.characters(), &statbuf_r);
  254. if (rc < 0) {
  255. perror(m_rhs.characters());
  256. g_there_was_an_error = true;
  257. return false;
  258. }
  259. switch (m_mode) {
  260. case Same:
  261. return statbuf_l.st_dev == statbuf_r.st_dev && statbuf_l.st_ino == statbuf_r.st_ino;
  262. case ModificationTimestampLess:
  263. return statbuf_l.st_mtime < statbuf_r.st_mtime;
  264. case ModificationTimestampGreater:
  265. return statbuf_l.st_mtime > statbuf_r.st_mtime;
  266. default:
  267. VERIFY_NOT_REACHED();
  268. }
  269. }
  270. String m_lhs;
  271. String m_rhs;
  272. Mode m_mode { Same };
  273. };
  274. static OwnPtr<Condition> parse_complex_expression(char* argv[]);
  275. static bool should_treat_expression_as_single_string(const StringView& arg_after)
  276. {
  277. return arg_after.is_null() || arg_after == "-a" || arg_after == "-o";
  278. }
  279. static OwnPtr<Condition> parse_simple_expression(char* argv[])
  280. {
  281. StringView arg = argv[optind];
  282. if (arg.is_null()) {
  283. return {};
  284. }
  285. if (arg == "(") {
  286. optind++;
  287. auto command = parse_complex_expression(argv);
  288. if (command && argv[optind] && StringView(argv[++optind]) == ")")
  289. return command;
  290. fatal_error("Unmatched \033[1m(");
  291. }
  292. // Try to read a unary op.
  293. if (arg.starts_with('-') && arg.length() == 2) {
  294. optind++;
  295. if (should_treat_expression_as_single_string(argv[optind])) {
  296. --optind;
  297. return make<StringCompare>(move(arg), "", StringCompare::NotEqual);
  298. }
  299. StringView value = argv[optind];
  300. switch (arg[1]) {
  301. case 'b':
  302. return make<FileIsOfKind>(value, FileIsOfKind::BlockDevice);
  303. case 'c':
  304. return make<FileIsOfKind>(value, FileIsOfKind::CharacterDevice);
  305. case 'd':
  306. return make<FileIsOfKind>(value, FileIsOfKind::Directory);
  307. case 'f':
  308. return make<FileIsOfKind>(value, FileIsOfKind::Regular);
  309. case 'h':
  310. case 'L':
  311. return make<FileIsOfKind>(value, FileIsOfKind::SymbolicLink);
  312. case 'p':
  313. return make<FileIsOfKind>(value, FileIsOfKind::FIFO);
  314. case 'S':
  315. return make<FileIsOfKind>(value, FileIsOfKind::Socket);
  316. case 'r':
  317. return make<UserHasPermission>(value, UserHasPermission::Read);
  318. case 'w':
  319. return make<UserHasPermission>(value, UserHasPermission::Write);
  320. case 'x':
  321. return make<UserHasPermission>(value, UserHasPermission::Execute);
  322. case 'e':
  323. return make<UserHasPermission>(value, UserHasPermission::Any);
  324. case 'o':
  325. case 'a':
  326. // '-a' and '-o' are boolean ops, which are part of a complex expression
  327. // so we have nothing to parse, simply return to caller.
  328. --optind;
  329. return {};
  330. case 'n':
  331. return make<StringCompare>("", value, StringCompare::NotEqual);
  332. case 'z':
  333. return make<StringCompare>("", value, StringCompare::Equal);
  334. case 'g':
  335. case 'G':
  336. case 'k':
  337. case 'N':
  338. case 'O':
  339. case 's':
  340. fatal_error("Unsupported operator \033[1m%s", argv[optind]);
  341. default:
  342. --optind;
  343. break;
  344. }
  345. }
  346. // Try to read a binary op, this is either a <string> op <string>, <integer> op <integer>, or <file> op <file>.
  347. auto lhs = arg;
  348. arg = argv[++optind];
  349. if (arg == "=") {
  350. StringView rhs = argv[++optind];
  351. return make<StringCompare>(lhs, rhs, StringCompare::Equal);
  352. } else if (arg == "!=") {
  353. StringView rhs = argv[++optind];
  354. return make<StringCompare>(lhs, rhs, StringCompare::NotEqual);
  355. } else if (arg == "-eq") {
  356. StringView rhs = argv[++optind];
  357. return make<NumericCompare>(lhs, rhs, NumericCompare::Equal);
  358. } else if (arg == "-ge") {
  359. StringView rhs = argv[++optind];
  360. return make<NumericCompare>(lhs, rhs, NumericCompare::GreaterOrEqual);
  361. } else if (arg == "-gt") {
  362. StringView rhs = argv[++optind];
  363. return make<NumericCompare>(lhs, rhs, NumericCompare::Greater);
  364. } else if (arg == "-le") {
  365. StringView rhs = argv[++optind];
  366. return make<NumericCompare>(lhs, rhs, NumericCompare::LessOrEqual);
  367. } else if (arg == "-lt") {
  368. StringView rhs = argv[++optind];
  369. return make<NumericCompare>(lhs, rhs, NumericCompare::Less);
  370. } else if (arg == "-ne") {
  371. StringView rhs = argv[++optind];
  372. return make<NumericCompare>(lhs, rhs, NumericCompare::NotEqual);
  373. } else if (arg == "-ef") {
  374. StringView rhs = argv[++optind];
  375. return make<FileCompare>(lhs, rhs, FileCompare::Same);
  376. } else if (arg == "-nt") {
  377. StringView rhs = argv[++optind];
  378. return make<FileCompare>(lhs, rhs, FileCompare::ModificationTimestampGreater);
  379. } else if (arg == "-ot") {
  380. StringView rhs = argv[++optind];
  381. return make<FileCompare>(lhs, rhs, FileCompare::ModificationTimestampLess);
  382. } else if (arg == "-o" || arg == "-a") {
  383. // '-a' and '-o' are boolean ops, which are part of a complex expression
  384. // put them back and return with lhs as string compare.
  385. --optind;
  386. return make<StringCompare>("", lhs, StringCompare::NotEqual);
  387. } else {
  388. // Now that we know it's not a well-formed expression, see if it's actually a negation
  389. if (lhs == "!") {
  390. if (should_treat_expression_as_single_string(arg))
  391. return make<StringCompare>(move(lhs), "", StringCompare::NotEqual);
  392. auto command = parse_complex_expression(argv);
  393. if (!command)
  394. fatal_error("Expected an expression after \x1b[1m!");
  395. return make<Not>(command.release_nonnull());
  396. }
  397. --optind;
  398. return make<StringCompare>("", lhs, StringCompare::NotEqual);
  399. }
  400. }
  401. static OwnPtr<Condition> parse_complex_expression(char* argv[])
  402. {
  403. auto command = parse_simple_expression(argv);
  404. while (argv[optind] && argv[optind + 1]) {
  405. if (!command && argv[optind])
  406. fatal_error("expected an expression");
  407. StringView arg = argv[++optind];
  408. enum {
  409. AndOp,
  410. OrOp,
  411. } binary_operation { AndOp };
  412. if (arg == "-a") {
  413. optind++;
  414. binary_operation = AndOp;
  415. } else if (arg == "-o") {
  416. optind++;
  417. binary_operation = OrOp;
  418. } else {
  419. // Ooops, looked too far.
  420. optind--;
  421. return command;
  422. }
  423. auto rhs = parse_complex_expression(argv);
  424. if (!rhs)
  425. fatal_error("Missing right-hand side");
  426. if (binary_operation == AndOp)
  427. command = make<And>(command.release_nonnull(), rhs.release_nonnull());
  428. else
  429. command = make<Or>(command.release_nonnull(), rhs.release_nonnull());
  430. }
  431. return command;
  432. }
  433. int main(int argc, char* argv[])
  434. {
  435. if (pledge("stdio rpath", nullptr) < 0) {
  436. perror("pledge");
  437. return 126;
  438. }
  439. if (LexicalPath { argv[0] }.basename() == "[") {
  440. --argc;
  441. if (StringView { argv[argc] } != "]")
  442. fatal_error("test invoked as '[' requires a closing bracket ']'");
  443. argv[argc] = nullptr;
  444. }
  445. // Exit false when no arguments are given.
  446. if (argc == 1)
  447. return 1;
  448. auto condition = parse_complex_expression(argv);
  449. if (optind != argc - 1)
  450. fatal_error("Too many arguments");
  451. auto result = condition ? condition->check() : false;
  452. if (g_there_was_an_error)
  453. return 126;
  454. return result ? 0 : 1;
  455. }