cmp.cpp 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /*
  2. * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibCore/ArgsParser.h>
  7. #include <LibCore/File.h>
  8. #include <LibCore/System.h>
  9. #include <LibMain/Main.h>
  10. #include <unistd.h>
  11. static ErrorOr<NonnullOwnPtr<Core::BufferedFile>> open_file_or_stdin(DeprecatedString const& filename)
  12. {
  13. OwnPtr<Core::File> file;
  14. if (filename == "-") {
  15. file = TRY(Core::File::adopt_fd(STDIN_FILENO, Core::File::OpenMode::Read));
  16. } else {
  17. file = TRY(Core::File::open(filename, Core::File::OpenMode::Read));
  18. }
  19. return TRY(Core::BufferedFile::create(file.release_nonnull()));
  20. }
  21. ErrorOr<int> serenity_main(Main::Arguments arguments)
  22. {
  23. Main::set_return_code_for_errors(2);
  24. TRY(Core::System::pledge("stdio rpath"));
  25. Core::ArgsParser parser;
  26. DeprecatedString filename1;
  27. DeprecatedString filename2;
  28. bool verbose = false;
  29. bool silent = false;
  30. parser.set_general_help("Compare two files, and report the first byte that does not match. Returns 0 if files are identical, or 1 if they differ.");
  31. parser.add_positional_argument(filename1, "First file to compare", "file1", Core::ArgsParser::Required::Yes);
  32. parser.add_positional_argument(filename2, "Second file to compare", "file2", Core::ArgsParser::Required::Yes);
  33. parser.add_option(verbose, "Output every byte mismatch, not just the first", "verbose", 'l');
  34. parser.add_option(silent, "Disable all output", "silent", 's');
  35. parser.parse(arguments);
  36. // When opening STDIN as both files, the results are undefined.
  37. // Let's just report that it matches.
  38. if (filename1 == "-" && filename2 == "-")
  39. return 0;
  40. auto file1 = TRY(open_file_or_stdin(filename1));
  41. auto file2 = TRY(open_file_or_stdin(filename2));
  42. TRY(Core::System::unveil(nullptr, nullptr));
  43. int line_number = 1;
  44. int byte_number = 1;
  45. Array<u8, 1> buffer1;
  46. Array<u8, 1> buffer2;
  47. bool files_match = true;
  48. auto report_mismatch = [&]() {
  49. files_match = false;
  50. if (silent)
  51. return;
  52. if (verbose)
  53. outln("{} {:o} {:o}", byte_number, buffer1[0], buffer2[0]);
  54. else
  55. outln("{} {} differ: char {}, line {}", filename1, filename2, byte_number, line_number);
  56. };
  57. auto report_eof = [&](auto& shorter_file_name) {
  58. files_match = false;
  59. if (silent)
  60. return;
  61. auto additional_info = verbose
  62. ? DeprecatedString::formatted(" after byte {}", byte_number)
  63. : DeprecatedString::formatted(" after byte {}, line {}", byte_number, line_number);
  64. warnln("cmp: EOF on {}{}", shorter_file_name, additional_info);
  65. };
  66. while (true) {
  67. TRY(file1->read_some(buffer1));
  68. TRY(file2->read_some(buffer2));
  69. if (file1->is_eof() && file2->is_eof())
  70. break;
  71. if (file1->is_eof() || file2->is_eof()) {
  72. report_eof(file1->is_eof() ? filename1 : filename2);
  73. break;
  74. }
  75. if (buffer1[0] != buffer2[0]) {
  76. report_mismatch();
  77. if (!verbose)
  78. break;
  79. }
  80. if (buffer1[0] == '\n')
  81. ++line_number;
  82. ++byte_number;
  83. }
  84. return files_match ? 0 : 1;
  85. }