Serenity Operating System
at master 601 lines 17 kB view raw
1/* 2 * Copyright (c) 2020-2021, Sergey Bugaev <bugaevc@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Assertions.h> 8#include <AK/CheckedFormatString.h> 9#include <AK/LexicalPath.h> 10#include <AK/NonnullOwnPtr.h> 11#include <AK/OwnPtr.h> 12#include <AK/Vector.h> 13#include <LibCore/System.h> 14#include <LibMain/Main.h> 15#include <dirent.h> 16#include <errno.h> 17#include <fcntl.h> 18#include <grp.h> 19#include <pwd.h> 20#include <stdio.h> 21#include <string.h> 22#include <sys/stat.h> 23#include <sys/types.h> 24#include <sys/wait.h> 25#include <unistd.h> 26 27bool g_follow_symlinks = false; 28bool g_there_was_an_error = false; 29bool g_have_seen_action_command = false; 30 31template<typename... Parameters> 32[[noreturn]] static void fatal_error(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters) 33{ 34 warn("\033[31m"); 35 warn(move(fmtstr), parameters...); 36 warn("\033[0m"); 37 warnln(); 38 exit(1); 39} 40 41struct FileData { 42 // Full path to the file; either absolute or relative to cwd. 43 LexicalPath full_path; 44 // The parent directory of the file. 45 int dirfd { -1 }; 46 // The file's basename, relative to the directory. 47 char const* basename { nullptr }; 48 // Optionally, cached information as returned by stat/lstat/fstatat. 49 struct stat stat { 50 }; 51 bool stat_is_valid : 1 { false }; 52 // File type as returned from readdir(), or DT_UNKNOWN. 53 unsigned char d_type { DT_UNKNOWN }; 54 55 const struct stat* ensure_stat() 56 { 57 if (stat_is_valid) 58 return &stat; 59 60 int flags = g_follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW; 61 int rc = fstatat(dirfd, basename, &stat, flags); 62 if (rc < 0) { 63 perror(full_path.string().characters()); 64 g_there_was_an_error = true; 65 return nullptr; 66 } 67 68 stat_is_valid = true; 69 70 if (S_ISREG(stat.st_mode)) 71 d_type = DT_REG; 72 else if (S_ISDIR(stat.st_mode)) 73 d_type = DT_DIR; 74 else if (S_ISCHR(stat.st_mode)) 75 d_type = DT_CHR; 76 else if (S_ISBLK(stat.st_mode)) 77 d_type = DT_BLK; 78 else if (S_ISFIFO(stat.st_mode)) 79 d_type = DT_FIFO; 80 else if (S_ISLNK(stat.st_mode)) 81 d_type = DT_LNK; 82 else if (S_ISSOCK(stat.st_mode)) 83 d_type = DT_SOCK; 84 else 85 VERIFY_NOT_REACHED(); 86 87 return &stat; 88 } 89}; 90 91class Command { 92public: 93 virtual ~Command() = default; 94 virtual bool evaluate(FileData& file_data) const = 0; 95}; 96 97class StatCommand : public Command { 98public: 99 virtual bool evaluate(const struct stat&) const = 0; 100 101private: 102 virtual bool evaluate(FileData& file_data) const override 103 { 104 const struct stat* stat = file_data.ensure_stat(); 105 if (!stat) 106 return false; 107 return evaluate(*stat); 108 } 109}; 110 111class TypeCommand final : public Command { 112public: 113 TypeCommand(char const* arg) 114 { 115 StringView type { arg, strlen(arg) }; 116 if (type.length() != 1 || !"bcdlpfs"sv.contains(type[0])) 117 fatal_error("Invalid mode: \033[1m{}", arg); 118 m_type = type[0]; 119 } 120 121private: 122 virtual bool evaluate(FileData& file_data) const override 123 { 124 // First, make sure we have a type, but avoid calling 125 // sys$stat() unless we need to. 126 if (file_data.d_type == DT_UNKNOWN) { 127 if (file_data.ensure_stat() == nullptr) 128 return false; 129 } 130 131 auto type = file_data.d_type; 132 switch (m_type) { 133 case 'b': 134 return type == DT_BLK; 135 case 'c': 136 return type == DT_CHR; 137 case 'd': 138 return type == DT_DIR; 139 case 'l': 140 return type == DT_LNK; 141 case 'p': 142 return type == DT_FIFO; 143 case 'f': 144 return type == DT_REG; 145 case 's': 146 return type == DT_SOCK; 147 default: 148 // We've verified this is a correct character before. 149 VERIFY_NOT_REACHED(); 150 } 151 } 152 153 char m_type { 0 }; 154}; 155 156class LinksCommand final : public StatCommand { 157public: 158 LinksCommand(char const* arg) 159 { 160 auto number = StringView { arg, strlen(arg) }.to_uint(); 161 if (!number.has_value()) 162 fatal_error("Invalid number: \033[1m{}", arg); 163 m_links = number.value(); 164 } 165 166private: 167 virtual bool evaluate(const struct stat& stat) const override 168 { 169 return stat.st_nlink == m_links; 170 } 171 172 nlink_t m_links { 0 }; 173}; 174 175class UserCommand final : public StatCommand { 176public: 177 UserCommand(char const* arg) 178 { 179 if (struct passwd* passwd = getpwnam(arg)) { 180 m_uid = passwd->pw_uid; 181 } else { 182 // Attempt to parse it as decimal UID. 183 auto number = StringView { arg, strlen(arg) }.to_uint(); 184 if (!number.has_value()) 185 fatal_error("Invalid user: \033[1m{}", arg); 186 m_uid = number.value(); 187 } 188 } 189 190private: 191 virtual bool evaluate(const struct stat& stat) const override 192 { 193 return stat.st_uid == m_uid; 194 } 195 196 uid_t m_uid { 0 }; 197}; 198 199class GroupCommand final : public StatCommand { 200public: 201 GroupCommand(char const* arg) 202 { 203 if (struct group* gr = getgrnam(arg)) { 204 m_gid = gr->gr_gid; 205 } else { 206 // Attempt to parse it as decimal GID. 207 auto number = StringView { arg, strlen(arg) }.to_int(); 208 if (!number.has_value()) 209 fatal_error("Invalid group: \033[1m{}", arg); 210 m_gid = number.value(); 211 } 212 } 213 214private: 215 virtual bool evaluate(const struct stat& stat) const override 216 { 217 return stat.st_gid == m_gid; 218 } 219 220 gid_t m_gid { 0 }; 221}; 222 223class SizeCommand final : public StatCommand { 224public: 225 SizeCommand(char const* arg) 226 { 227 StringView view { arg, strlen(arg) }; 228 if (view.ends_with('c')) { 229 m_is_bytes = true; 230 view = view.substring_view(0, view.length() - 1); 231 } 232 auto number = view.to_uint(); 233 if (!number.has_value()) 234 fatal_error("Invalid size: \033[1m{}", arg); 235 m_size = number.value(); 236 } 237 238private: 239 virtual bool evaluate(const struct stat& stat) const override 240 { 241 if (m_is_bytes) 242 return stat.st_size == m_size; 243 244 auto size_divided_by_512_rounded_up = (stat.st_size + 511) / 512; 245 return size_divided_by_512_rounded_up == m_size; 246 } 247 248 off_t m_size { 0 }; 249 bool m_is_bytes { false }; 250}; 251 252class NameCommand : public Command { 253public: 254 NameCommand(char const* pattern, CaseSensitivity case_sensitivity) 255 : m_pattern(pattern, strlen(pattern)) 256 , m_case_sensitivity(case_sensitivity) 257 { 258 } 259 260private: 261 virtual bool evaluate(FileData& file_data) const override 262 { 263 return file_data.full_path.basename().matches(m_pattern, m_case_sensitivity); 264 } 265 266 StringView m_pattern; 267 CaseSensitivity m_case_sensitivity { CaseSensitivity::CaseSensitive }; 268}; 269 270class PrintCommand final : public Command { 271public: 272 PrintCommand(char terminator = '\n') 273 : m_terminator(terminator) 274 { 275 } 276 277private: 278 virtual bool evaluate(FileData& file_data) const override 279 { 280 out("{}{}", file_data.full_path, m_terminator); 281 return true; 282 } 283 284 char m_terminator { '\n' }; 285}; 286 287class ExecCommand final : public Command { 288public: 289 ExecCommand(Vector<char*>&& argv) 290 : m_argv(move(argv)) 291 { 292 } 293 294private: 295 virtual bool evaluate(FileData& file_data) const override 296 { 297 pid_t pid = fork(); 298 299 if (pid < 0) { 300 perror("fork"); 301 g_there_was_an_error = true; 302 return false; 303 } else if (pid == 0) { 304 // Replace any occurrences of "{}" with the path. Since we're in the 305 // child and going to exec real soon, let's just const_cast away the 306 // constness. 307 auto argv = const_cast<Vector<char*>&>(m_argv); 308 for (auto& arg : argv) { 309 if (StringView { arg, strlen(arg) } == "{}") 310 arg = const_cast<char*>(file_data.full_path.string().characters()); 311 } 312 argv.append(nullptr); 313 execvp(m_argv[0], argv.data()); 314 perror("execvp"); 315 exit(1); 316 } else { 317 int status; 318 int rc = waitpid(pid, &status, 0); 319 if (rc < 0) { 320 perror("waitpid"); 321 g_there_was_an_error = true; 322 return false; 323 } 324 return WIFEXITED(status) && WEXITSTATUS(status) == 0; 325 } 326 } 327 328 Vector<char*> m_argv; 329}; 330 331class AndCommand final : public Command { 332public: 333 AndCommand(NonnullOwnPtr<Command>&& lhs, NonnullOwnPtr<Command>&& rhs) 334 : m_lhs(move(lhs)) 335 , m_rhs(move(rhs)) 336 { 337 } 338 339private: 340 virtual bool evaluate(FileData& file_data) const override 341 { 342 return m_lhs->evaluate(file_data) && m_rhs->evaluate(file_data); 343 } 344 345 NonnullOwnPtr<Command> m_lhs; 346 NonnullOwnPtr<Command> m_rhs; 347}; 348 349class OrCommand final : public Command { 350public: 351 OrCommand(NonnullOwnPtr<Command>&& lhs, NonnullOwnPtr<Command>&& rhs) 352 : m_lhs(move(lhs)) 353 , m_rhs(move(rhs)) 354 { 355 } 356 357private: 358 virtual bool evaluate(FileData& file_data) const override 359 { 360 return m_lhs->evaluate(file_data) || m_rhs->evaluate(file_data); 361 } 362 363 NonnullOwnPtr<Command> m_lhs; 364 NonnullOwnPtr<Command> m_rhs; 365}; 366 367static OwnPtr<Command> parse_complex_command(Vector<char*>& args); 368 369// Parse a simple command starting at optind; leave optind at its the last 370// argument. Return nullptr if we reach the end of arguments. 371static OwnPtr<Command> parse_simple_command(Vector<char*>& args) 372{ 373 if (args.is_empty()) 374 return {}; 375 376 char* raw_arg = args.take_first(); 377 StringView arg { raw_arg, strlen(raw_arg) }; 378 379 if (arg == "(") { 380 auto command = parse_complex_command(args); 381 if (command && !args.is_empty() && StringView { args.first(), strlen(args.first()) } == ")") 382 return command; 383 fatal_error("Unmatched \033[1m("); 384 } else if (arg == "-type") { 385 if (args.is_empty()) 386 fatal_error("-type: requires additional arguments"); 387 return make<TypeCommand>(args.take_first()); 388 } else if (arg == "-links") { 389 if (args.is_empty()) 390 fatal_error("-links: requires additional arguments"); 391 return make<LinksCommand>(args.take_first()); 392 } else if (arg == "-user") { 393 if (args.is_empty()) 394 fatal_error("-user: requires additional arguments"); 395 return make<UserCommand>(args.take_first()); 396 } else if (arg == "-group") { 397 if (args.is_empty()) 398 fatal_error("-group: requires additional arguments"); 399 return make<GroupCommand>(args.take_first()); 400 } else if (arg == "-size") { 401 if (args.is_empty()) 402 fatal_error("-size: requires additional arguments"); 403 return make<SizeCommand>(args.take_first()); 404 } else if (arg == "-name") { 405 if (args.is_empty()) 406 fatal_error("-name: requires additional arguments"); 407 return make<NameCommand>(args.take_first(), CaseSensitivity::CaseSensitive); 408 } else if (arg == "-iname") { 409 if (args.is_empty()) 410 fatal_error("-iname: requires additional arguments"); 411 return make<NameCommand>(args.take_first(), CaseSensitivity::CaseInsensitive); 412 } else if (arg == "-print") { 413 g_have_seen_action_command = true; 414 return make<PrintCommand>(); 415 } else if (arg == "-print0") { 416 g_have_seen_action_command = true; 417 return make<PrintCommand>(0); 418 } else if (arg == "-exec") { 419 if (args.is_empty()) 420 fatal_error("-exec: requires additional arguments"); 421 g_have_seen_action_command = true; 422 Vector<char*> command_argv; 423 while (!args.is_empty()) { 424 char* next = args.take_first(); 425 if (next[0] == ';') 426 break; 427 command_argv.append(next); 428 } 429 return make<ExecCommand>(move(command_argv)); 430 } else { 431 fatal_error("Unsupported command \033[1m{}", arg); 432 } 433} 434 435static OwnPtr<Command> parse_complex_command(Vector<char*>& args) 436{ 437 auto command = parse_simple_command(args); 438 439 while (command && !args.is_empty()) { 440 char* raw_arg = args.take_first(); 441 StringView arg { raw_arg, strlen(raw_arg) }; 442 443 enum { 444 And, 445 Or, 446 } binary_operation { And }; 447 448 if (arg == "-a") { 449 binary_operation = And; 450 } else if (arg == "-o") { 451 binary_operation = Or; 452 } else if (arg == ")") { 453 // Ooops, looked too far. 454 args.prepend(raw_arg); 455 return command; 456 } else { 457 // Juxtaposition is an And too, and there's nothing to skip. 458 args.prepend(raw_arg); 459 binary_operation = And; 460 } 461 462 auto rhs = parse_complex_command(args); 463 if (!rhs) 464 fatal_error("Missing right-hand side"); 465 466 if (binary_operation == And) 467 command = make<AndCommand>(command.release_nonnull(), rhs.release_nonnull()); 468 else 469 command = make<OrCommand>(command.release_nonnull(), rhs.release_nonnull()); 470 } 471 472 return command; 473} 474 475static NonnullOwnPtr<Command> parse_all_commands(Vector<char*>& args) 476{ 477 auto command = parse_complex_command(args); 478 479 if (g_have_seen_action_command) { 480 VERIFY(command); 481 return command.release_nonnull(); 482 } 483 484 if (!command) { 485 return make<PrintCommand>(); 486 } 487 488 return make<AndCommand>(command.release_nonnull(), make<PrintCommand>()); 489} 490 491static void walk_tree(FileData& root_data, Command& command) 492{ 493 command.evaluate(root_data); 494 495 // We should try to read directory entries if either: 496 // * This is a directory. 497 // * This is a symlink (that could point to a directory), 498 // and we're following symlinks. 499 // * The type is unknown, so it could be a directory. 500 switch (root_data.d_type) { 501 case DT_DIR: 502 case DT_UNKNOWN: 503 break; 504 case DT_LNK: 505 if (g_follow_symlinks) 506 break; 507 return; 508 default: 509 return; 510 } 511 512 int dirfd = openat(root_data.dirfd, root_data.basename, O_RDONLY | O_DIRECTORY | O_CLOEXEC); 513 if (dirfd < 0) { 514 if (errno == ENOTDIR) { 515 // Above we decided to try to open this file because it could 516 // be a directory, but turns out it's not. This is fine though. 517 return; 518 } 519 perror(root_data.full_path.string().characters()); 520 g_there_was_an_error = true; 521 return; 522 } 523 524 DIR* dir = fdopendir(dirfd); 525 526 while (true) { 527 errno = 0; 528 auto* dirent = readdir(dir); 529 if (!dirent) 530 break; 531 532 if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) 533 continue; 534 535 FileData file_data { 536 root_data.full_path.append({ dirent->d_name, strlen(dirent->d_name) }), 537 dirfd, 538 dirent->d_name, 539 (struct stat) {}, 540 false, 541 dirent->d_type, 542 }; 543 walk_tree(file_data, command); 544 } 545 546 if (errno != 0) { 547 perror(root_data.full_path.string().characters()); 548 g_there_was_an_error = true; 549 } 550 551 closedir(dir); 552} 553 554ErrorOr<int> serenity_main(Main::Arguments arguments) 555{ 556 Vector<char*> args; 557 args.append(arguments.argv + 1, arguments.argc - 1); 558 559 OwnPtr<Command> command; 560 Vector<LexicalPath> paths; 561 562 while (!args.is_empty()) { 563 char* raw_arg = args.take_first(); 564 StringView arg { raw_arg, strlen(raw_arg) }; 565 if (arg == "-L") { 566 g_follow_symlinks = true; 567 } else if (!arg.starts_with('-')) { 568 paths.append(LexicalPath(arg)); 569 } else { 570 // No special case, so add back the argument and try to parse a command. 571 args.prepend(raw_arg); 572 command = parse_all_commands(args); 573 } 574 } 575 576 if (!command) 577 command = make<PrintCommand>(); 578 579 if (paths.is_empty()) 580 paths.append(LexicalPath(".")); 581 582 for (auto& path : paths) { 583 DeprecatedString dirname = path.dirname(); 584 DeprecatedString basename = path.basename(); 585 586 int dirfd = TRY(Core::System::open(dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC)); 587 588 FileData file_data { 589 path, 590 dirfd, 591 basename.characters(), 592 (struct stat) {}, 593 false, 594 DT_UNKNOWN, 595 }; 596 walk_tree(file_data, *command); 597 close(dirfd); 598 } 599 600 return g_there_was_an_error ? 1 : 0; 601}