Serenity Operating System
at master 628 lines 17 kB view raw
1/* 2 * Copyright (c) 2020-2022, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Assertions.h> 8#include <AK/LexicalPath.h> 9#include <AK/NonnullOwnPtr.h> 10#include <AK/OwnPtr.h> 11#include <LibCore/System.h> 12#include <LibMain/Main.h> 13#include <stdio.h> 14#include <sys/stat.h> 15#include <unistd.h> 16 17bool g_there_was_an_error = false; 18 19[[noreturn, gnu::format(printf, 1, 2)]] static void fatal_error(char const* format, ...) 20{ 21 fputs("\033[31m", stderr); 22 23 va_list ap; 24 va_start(ap, format); 25 vfprintf(stderr, format, ap); 26 va_end(ap); 27 28 fputs("\033[0m\n", stderr); 29 exit(126); 30} 31 32class Condition { 33public: 34 virtual ~Condition() = default; 35 virtual bool check() const = 0; 36}; 37 38class And : public Condition { 39public: 40 And(NonnullOwnPtr<Condition> lhs, NonnullOwnPtr<Condition> rhs) 41 : m_lhs(move(lhs)) 42 , m_rhs(move(rhs)) 43 { 44 } 45 46private: 47 virtual bool check() const override 48 { 49 return m_lhs->check() && m_rhs->check(); 50 } 51 52 NonnullOwnPtr<Condition> m_lhs; 53 NonnullOwnPtr<Condition> m_rhs; 54}; 55 56class Or : public Condition { 57public: 58 Or(NonnullOwnPtr<Condition> lhs, NonnullOwnPtr<Condition> rhs) 59 : m_lhs(move(lhs)) 60 , m_rhs(move(rhs)) 61 { 62 } 63 64private: 65 virtual bool check() const override 66 { 67 return m_lhs->check() || m_rhs->check(); 68 } 69 70 NonnullOwnPtr<Condition> m_lhs; 71 NonnullOwnPtr<Condition> m_rhs; 72}; 73 74class Not : public Condition { 75public: 76 Not(NonnullOwnPtr<Condition> cond) 77 : m_cond(move(cond)) 78 { 79 } 80 81private: 82 virtual bool check() const override 83 { 84 return !m_cond->check(); 85 } 86 87 NonnullOwnPtr<Condition> m_cond; 88}; 89 90class FileIsOfKind : public Condition { 91public: 92 enum Kind { 93 BlockDevice, 94 CharacterDevice, 95 Directory, 96 FIFO, 97 Regular, 98 Socket, 99 SymbolicLink, 100 }; 101 FileIsOfKind(StringView path, Kind kind) 102 : m_path(path) 103 , m_kind(kind) 104 { 105 } 106 107private: 108 virtual bool check() const override 109 { 110 struct stat statbuf; 111 int rc; 112 113 if (m_kind == SymbolicLink) 114 rc = stat(m_path.characters(), &statbuf); 115 else 116 rc = lstat(m_path.characters(), &statbuf); 117 118 if (rc < 0) { 119 if (errno != ENOENT) { 120 perror(m_path.characters()); 121 g_there_was_an_error = true; 122 } 123 return false; 124 } 125 126 switch (m_kind) { 127 case BlockDevice: 128 return S_ISBLK(statbuf.st_mode); 129 case CharacterDevice: 130 return S_ISCHR(statbuf.st_mode); 131 case Directory: 132 return S_ISDIR(statbuf.st_mode); 133 case FIFO: 134 return S_ISFIFO(statbuf.st_mode); 135 case Regular: 136 return S_ISREG(statbuf.st_mode); 137 case Socket: 138 return S_ISSOCK(statbuf.st_mode); 139 case SymbolicLink: 140 return S_ISLNK(statbuf.st_mode); 141 default: 142 VERIFY_NOT_REACHED(); 143 } 144 } 145 146 DeprecatedString m_path; 147 Kind m_kind { Regular }; 148}; 149 150class UserHasPermission : public Condition { 151public: 152 enum Permission { 153 Any, 154 Read, 155 Write, 156 Execute, 157 }; 158 UserHasPermission(StringView path, Permission kind) 159 : m_path(path) 160 , m_kind(kind) 161 { 162 } 163 164private: 165 virtual bool check() const override 166 { 167 switch (m_kind) { 168 case Read: 169 return access(m_path.characters(), R_OK) == 0; 170 case Write: 171 return access(m_path.characters(), W_OK) == 0; 172 case Execute: 173 return access(m_path.characters(), X_OK) == 0; 174 case Any: 175 return access(m_path.characters(), F_OK) == 0; 176 default: 177 VERIFY_NOT_REACHED(); 178 } 179 } 180 181 DeprecatedString m_path; 182 Permission m_kind { Read }; 183}; 184 185class FileHasFlag : public Condition { 186public: 187 enum Flag { 188 SGID, 189 SUID, 190 SVTX, 191 }; 192 FileHasFlag(StringView path, Flag kind) 193 : m_path(path) 194 , m_kind(kind) 195 { 196 } 197 198private: 199 virtual bool check() const override 200 { 201 struct stat statbuf; 202 int rc = stat(m_path.characters(), &statbuf); 203 204 if (rc < 0) { 205 if (errno != ENOENT) { 206 perror(m_path.characters()); 207 g_there_was_an_error = true; 208 } 209 return false; 210 } 211 212 switch (m_kind) { 213 case SGID: 214 return statbuf.st_mode & S_ISGID; 215 case SUID: 216 return statbuf.st_mode & S_ISUID; 217 case SVTX: 218 return statbuf.st_mode & S_ISVTX; 219 default: 220 VERIFY_NOT_REACHED(); 221 } 222 } 223 224 DeprecatedString m_path; 225 Flag m_kind { SGID }; 226}; 227 228class FileIsOwnedBy : public Condition { 229public: 230 enum Owner { 231 EffectiveGID, 232 EffectiveUID, 233 }; 234 FileIsOwnedBy(StringView path, Owner kind) 235 : m_path(path) 236 , m_kind(kind) 237 { 238 } 239 240private: 241 virtual bool check() const override 242 { 243 struct stat statbuf; 244 int rc = stat(m_path.characters(), &statbuf); 245 246 if (rc < 0) { 247 if (errno != ENOENT) { 248 perror(m_path.characters()); 249 g_there_was_an_error = true; 250 } 251 return false; 252 } 253 254 switch (m_kind) { 255 case EffectiveGID: 256 return statbuf.st_gid == getgid(); 257 case EffectiveUID: 258 return statbuf.st_uid == getuid(); 259 default: 260 VERIFY_NOT_REACHED(); 261 } 262 } 263 264 DeprecatedString m_path; 265 Owner m_kind { EffectiveGID }; 266}; 267 268class StringCompare : public Condition { 269public: 270 enum Mode { 271 Equal, 272 NotEqual, 273 }; 274 275 StringCompare(StringView lhs, StringView rhs, Mode mode) 276 : m_lhs(move(lhs)) 277 , m_rhs(move(rhs)) 278 , m_mode(mode) 279 { 280 } 281 282private: 283 virtual bool check() const override 284 { 285 if (m_mode == Equal) 286 return m_lhs == m_rhs; 287 return m_lhs != m_rhs; 288 } 289 290 StringView m_lhs; 291 StringView m_rhs; 292 Mode m_mode { Equal }; 293}; 294 295class NumericCompare : public Condition { 296public: 297 enum Mode { 298 Equal, 299 Greater, 300 GreaterOrEqual, 301 Less, 302 LessOrEqual, 303 NotEqual, 304 }; 305 306 NumericCompare(DeprecatedString lhs, DeprecatedString rhs, Mode mode) 307 : m_mode(mode) 308 { 309 auto lhs_option = lhs.trim_whitespace().to_int(); 310 auto rhs_option = rhs.trim_whitespace().to_int(); 311 312 if (!lhs_option.has_value()) 313 fatal_error("expected integer expression: '%s'", lhs.characters()); 314 315 if (!rhs_option.has_value()) 316 fatal_error("expected integer expression: '%s'", rhs.characters()); 317 318 m_lhs = lhs_option.value(); 319 m_rhs = rhs_option.value(); 320 } 321 322private: 323 virtual bool check() const override 324 { 325 switch (m_mode) { 326 case Equal: 327 return m_lhs == m_rhs; 328 case Greater: 329 return m_lhs > m_rhs; 330 case GreaterOrEqual: 331 return m_lhs >= m_rhs; 332 case Less: 333 return m_lhs < m_rhs; 334 case LessOrEqual: 335 return m_lhs <= m_rhs; 336 case NotEqual: 337 return m_lhs != m_rhs; 338 default: 339 VERIFY_NOT_REACHED(); 340 } 341 } 342 343 int m_lhs { 0 }; 344 int m_rhs { 0 }; 345 Mode m_mode { Equal }; 346}; 347 348class FileCompare : public Condition { 349public: 350 enum Mode { 351 Same, 352 ModificationTimestampGreater, 353 ModificationTimestampLess, 354 }; 355 356 FileCompare(DeprecatedString lhs, DeprecatedString rhs, Mode mode) 357 : m_lhs(move(lhs)) 358 , m_rhs(move(rhs)) 359 , m_mode(mode) 360 { 361 } 362 363private: 364 virtual bool check() const override 365 { 366 struct stat statbuf_l; 367 int rc = stat(m_lhs.characters(), &statbuf_l); 368 369 if (rc < 0) { 370 perror(m_lhs.characters()); 371 g_there_was_an_error = true; 372 return false; 373 } 374 375 struct stat statbuf_r; 376 rc = stat(m_rhs.characters(), &statbuf_r); 377 378 if (rc < 0) { 379 perror(m_rhs.characters()); 380 g_there_was_an_error = true; 381 return false; 382 } 383 384 switch (m_mode) { 385 case Same: 386 return statbuf_l.st_dev == statbuf_r.st_dev && statbuf_l.st_ino == statbuf_r.st_ino; 387 case ModificationTimestampLess: 388 return statbuf_l.st_mtime < statbuf_r.st_mtime; 389 case ModificationTimestampGreater: 390 return statbuf_l.st_mtime > statbuf_r.st_mtime; 391 default: 392 VERIFY_NOT_REACHED(); 393 } 394 } 395 396 DeprecatedString m_lhs; 397 DeprecatedString m_rhs; 398 Mode m_mode { Same }; 399}; 400 401static OwnPtr<Condition> parse_complex_expression(char* argv[]); 402 403static bool should_treat_expression_as_single_string(StringView arg_after) 404{ 405 return arg_after.is_null() || arg_after == "-a" || arg_after == "-o"; 406} 407 408static OwnPtr<Condition> parse_simple_expression(char* argv[]) 409{ 410 StringView arg { argv[optind], strlen(argv[optind]) }; 411 if (arg.is_null()) { 412 return {}; 413 } 414 415 if (arg == "(") { 416 optind++; 417 auto command = parse_complex_expression(argv); 418 if (command && argv[optind]) { 419 auto const* next_option = argv[++optind]; 420 if (StringView { next_option, strlen(next_option) } == ")") 421 return command; 422 } 423 424 fatal_error("Unmatched \033[1m("); 425 } 426 427 // Try to read a unary op. 428 if (arg.starts_with('-') && arg.length() == 2) { 429 if (argv[++optind] == nullptr) 430 fatal_error("expected an argument"); 431 if (should_treat_expression_as_single_string({ argv[optind], strlen(argv[optind]) })) { 432 --optind; 433 return make<StringCompare>(move(arg), ""sv, StringCompare::NotEqual); 434 } 435 436 StringView value { argv[optind], strlen(argv[optind]) }; 437 switch (arg[1]) { 438 case 'b': 439 return make<FileIsOfKind>(value, FileIsOfKind::BlockDevice); 440 case 'c': 441 return make<FileIsOfKind>(value, FileIsOfKind::CharacterDevice); 442 case 'd': 443 return make<FileIsOfKind>(value, FileIsOfKind::Directory); 444 case 'f': 445 return make<FileIsOfKind>(value, FileIsOfKind::Regular); 446 case 'h': 447 case 'L': 448 return make<FileIsOfKind>(value, FileIsOfKind::SymbolicLink); 449 case 'p': 450 return make<FileIsOfKind>(value, FileIsOfKind::FIFO); 451 case 'S': 452 return make<FileIsOfKind>(value, FileIsOfKind::Socket); 453 case 'r': 454 return make<UserHasPermission>(value, UserHasPermission::Read); 455 case 'w': 456 return make<UserHasPermission>(value, UserHasPermission::Write); 457 case 'x': 458 return make<UserHasPermission>(value, UserHasPermission::Execute); 459 case 'e': 460 return make<UserHasPermission>(value, UserHasPermission::Any); 461 case 'g': 462 return make<FileHasFlag>(value, FileHasFlag::SGID); 463 case 'k': 464 return make<FileHasFlag>(value, FileHasFlag::SVTX); 465 case 'u': 466 return make<FileHasFlag>(value, FileHasFlag::SUID); 467 case 'o': 468 case 'a': 469 // '-a' and '-o' are boolean ops, which are part of a complex expression 470 // so we have nothing to parse, simply return to caller. 471 --optind; 472 return {}; 473 case 'n': 474 return make<StringCompare>(""sv, value, StringCompare::NotEqual); 475 case 'z': 476 return make<StringCompare>(""sv, value, StringCompare::Equal); 477 case 'G': 478 return make<FileIsOwnedBy>(value, FileIsOwnedBy::EffectiveGID); 479 case 'O': 480 return make<FileIsOwnedBy>(value, FileIsOwnedBy::EffectiveUID); 481 case 'N': 482 case 's': 483 // 'optind' has been incremented to refer to the argument after the 484 // operator, while we want to print the operator itself. 485 fatal_error("Unsupported operator \033[1m%s", argv[optind - 1]); 486 default: 487 --optind; 488 break; 489 } 490 } 491 492 auto get_next_arg = [&argv]() -> StringView { 493 auto const* next_arg = argv[++optind]; 494 if (next_arg == NULL) 495 return StringView {}; 496 return StringView { next_arg, strlen(next_arg) }; 497 }; 498 499 // Try to read a binary op, this is either a <string> op <string>, <integer> op <integer>, or <file> op <file>. 500 auto lhs = arg; 501 arg = get_next_arg(); 502 503 if (arg == "=") { 504 StringView rhs = get_next_arg(); 505 return make<StringCompare>(lhs, rhs, StringCompare::Equal); 506 } else if (arg == "!=") { 507 StringView rhs = get_next_arg(); 508 return make<StringCompare>(lhs, rhs, StringCompare::NotEqual); 509 } else if (arg == "-eq") { 510 StringView rhs = get_next_arg(); 511 return make<NumericCompare>(lhs, rhs, NumericCompare::Equal); 512 } else if (arg == "-ge") { 513 StringView rhs = get_next_arg(); 514 return make<NumericCompare>(lhs, rhs, NumericCompare::GreaterOrEqual); 515 } else if (arg == "-gt") { 516 StringView rhs = get_next_arg(); 517 return make<NumericCompare>(lhs, rhs, NumericCompare::Greater); 518 } else if (arg == "-le") { 519 StringView rhs = get_next_arg(); 520 return make<NumericCompare>(lhs, rhs, NumericCompare::LessOrEqual); 521 } else if (arg == "-lt") { 522 StringView rhs = get_next_arg(); 523 return make<NumericCompare>(lhs, rhs, NumericCompare::Less); 524 } else if (arg == "-ne") { 525 StringView rhs = get_next_arg(); 526 return make<NumericCompare>(lhs, rhs, NumericCompare::NotEqual); 527 } else if (arg == "-ef") { 528 StringView rhs = get_next_arg(); 529 return make<FileCompare>(lhs, rhs, FileCompare::Same); 530 } else if (arg == "-nt") { 531 StringView rhs = get_next_arg(); 532 return make<FileCompare>(lhs, rhs, FileCompare::ModificationTimestampGreater); 533 } else if (arg == "-ot") { 534 StringView rhs = get_next_arg(); 535 return make<FileCompare>(lhs, rhs, FileCompare::ModificationTimestampLess); 536 } else if (arg == "-o" || arg == "-a") { 537 // '-a' and '-o' are boolean ops, which are part of a complex expression 538 // put them back and return with lhs as string compare. 539 --optind; 540 return make<StringCompare>(""sv, lhs, StringCompare::NotEqual); 541 } else { 542 // Now that we know it's not a well-formed expression, see if it's actually a negation 543 if (lhs == "!") { 544 if (should_treat_expression_as_single_string(arg)) 545 return make<StringCompare>(move(lhs), ""sv, StringCompare::NotEqual); 546 547 auto command = parse_complex_expression(argv); 548 if (!command) 549 fatal_error("Expected an expression after \x1b[1m!"); 550 551 return make<Not>(command.release_nonnull()); 552 } 553 --optind; 554 return make<StringCompare>(""sv, lhs, StringCompare::NotEqual); 555 } 556} 557 558static OwnPtr<Condition> parse_complex_expression(char* argv[]) 559{ 560 auto command = parse_simple_expression(argv); 561 562 while (argv[optind] && argv[optind + 1]) { 563 if (!command && argv[optind]) 564 fatal_error("expected an expression"); 565 566 auto const* arg_ptr = argv[++optind]; 567 StringView arg { arg_ptr, strlen(arg_ptr) }; 568 569 enum { 570 AndOp, 571 OrOp, 572 } binary_operation { AndOp }; 573 574 if (arg == "-a") { 575 if (argv[++optind] == nullptr) 576 fatal_error("expected an expression"); 577 binary_operation = AndOp; 578 } else if (arg == "-o") { 579 if (argv[++optind] == nullptr) 580 fatal_error("expected an expression"); 581 binary_operation = OrOp; 582 } else { 583 // Ooops, looked too far. 584 optind--; 585 return command; 586 } 587 auto rhs = parse_complex_expression(argv); 588 if (!rhs) 589 fatal_error("Missing right-hand side"); 590 591 if (binary_operation == AndOp) 592 command = make<And>(command.release_nonnull(), rhs.release_nonnull()); 593 else 594 command = make<Or>(command.release_nonnull(), rhs.release_nonnull()); 595 } 596 597 return command; 598} 599 600ErrorOr<int> serenity_main(Main::Arguments arguments) 601{ 602 auto maybe_error = Core::System::pledge("stdio rpath"); 603 if (maybe_error.is_error()) { 604 warnln("{}", maybe_error.error()); 605 return 126; 606 } 607 608 int argc = arguments.argc; 609 if (LexicalPath::basename(arguments.strings[0]) == "[") { 610 --argc; 611 if (StringView { arguments.strings[argc] } != "]") 612 fatal_error("test invoked as '[' requires a closing bracket ']'"); 613 arguments.strings[argc] = {}; 614 } 615 616 // Exit false when no arguments are given. 617 if (argc == 1) 618 return 1; 619 620 auto condition = parse_complex_expression(arguments.argv); 621 if (optind != argc - 1) 622 fatal_error("Too many arguments"); 623 auto result = condition ? condition->check() : false; 624 625 if (g_there_was_an_error) 626 return 126; 627 return result ? 0 : 1; 628}