file.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*
  2. * Copyright (c) 2021, Valtteri Koskivuori <vkoskiv@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/NumberFormat.h>
  7. #include <AK/String.h>
  8. #include <AK/Vector.h>
  9. #include <LibArchive/Zip.h>
  10. #include <LibAudio/Loader.h>
  11. #include <LibCompress/Gzip.h>
  12. #include <LibCore/ArgsParser.h>
  13. #include <LibCore/File.h>
  14. #include <LibCore/MappedFile.h>
  15. #include <LibCore/MimeData.h>
  16. #include <LibCore/System.h>
  17. #include <LibELF/Image.h>
  18. #include <LibELF/Validation.h>
  19. #include <LibGfx/ImageFormats/ImageDecoder.h>
  20. #include <LibMain/Main.h>
  21. #include <stdio.h>
  22. #include <sys/stat.h>
  23. #include <unistd.h>
  24. static ErrorOr<Optional<String>> description_only(StringView description, StringView)
  25. {
  26. return String::from_utf8(description);
  27. }
  28. // FIXME: Ideally Gfx::ImageDecoder could tell us the image type directly.
  29. static ErrorOr<Optional<String>> image_details(StringView description, StringView path)
  30. {
  31. auto mapped_file = TRY(Core::MappedFile::map(path));
  32. auto mime_type = Core::guess_mime_type_based_on_filename(path);
  33. auto image_decoder = Gfx::ImageDecoder::try_create_for_raw_bytes(mapped_file->bytes(), mime_type);
  34. if (!image_decoder)
  35. return OptionalNone {};
  36. StringBuilder builder;
  37. builder.appendff("{}, {} x {}", description, image_decoder->width(), image_decoder->height());
  38. if (image_decoder->is_animated()) {
  39. builder.appendff(", animated with {} frames that loop", image_decoder->frame_count());
  40. int loop_count = image_decoder->loop_count();
  41. if (loop_count == 0)
  42. builder.appendff(" indefinitely");
  43. else
  44. builder.appendff(" {} {}", loop_count, loop_count == 1 ? "time" : "times");
  45. }
  46. return builder.to_string();
  47. }
  48. static ErrorOr<Optional<String>> audio_details(StringView description, StringView path)
  49. {
  50. auto loader_or_error = Audio::Loader::create(path);
  51. if (loader_or_error.is_error())
  52. return OptionalNone {};
  53. auto loader = loader_or_error.release_value();
  54. StringBuilder builder;
  55. builder.appendff("{}, {} Hz, {}-bit {}, {} samples ({} s)",
  56. description,
  57. loader->sample_rate(),
  58. loader->bits_per_sample(),
  59. loader->num_channels() == 1 ? "Mono" : "Stereo",
  60. loader->total_samples(),
  61. loader->total_samples() / loader->sample_rate());
  62. auto metadata = loader->metadata();
  63. Vector<String> metadata_parts;
  64. if (metadata.title.has_value()) {
  65. // FIXME: Use “pretty quotation” once our terminal fonts support these characters.
  66. if (auto title_text = String::formatted("\"{}\"", metadata.title.value_or({})); !title_text.is_error()) {
  67. // We intentionally discard the error, because not printing part of the metadata due to OOM is not a problem.
  68. (void)metadata_parts.try_append(title_text.release_value());
  69. }
  70. }
  71. if (metadata.album.has_value()) {
  72. if (auto album_text = String::formatted("(Album: {})", metadata.album.value_or({})); !album_text.is_error())
  73. (void)metadata_parts.try_append(album_text.release_value());
  74. }
  75. if (auto all_artists = metadata.all_artists(); !all_artists.is_error() && all_artists.value().has_value()) {
  76. if (auto artist_text = String::formatted("by {}", all_artists.release_value().release_value()); !artist_text.is_error())
  77. (void)metadata_parts.try_append(artist_text.release_value());
  78. }
  79. if (!metadata_parts.is_empty()) {
  80. // New line for the metadata.
  81. builder.append_code_point('\n');
  82. builder.join(" "sv, metadata_parts);
  83. }
  84. return builder.to_string();
  85. }
  86. static ErrorOr<Optional<String>> gzip_details(StringView description, StringView path)
  87. {
  88. auto mapped_file = TRY(Core::MappedFile::map(path));
  89. if (!Compress::GzipDecompressor::is_likely_compressed(mapped_file->bytes()))
  90. return OptionalNone {};
  91. auto gzip_details = TRY(Compress::GzipDecompressor::describe_header(mapped_file->bytes()));
  92. if (!gzip_details.has_value())
  93. return OptionalNone {};
  94. return TRY(String::formatted("{}, {}", description, gzip_details.value()));
  95. }
  96. static ErrorOr<Optional<String>> zip_details(StringView description, StringView path)
  97. {
  98. auto mapped_file = TRY(Core::MappedFile::map(path));
  99. auto zip_file = Archive::Zip::try_create(mapped_file->bytes());
  100. auto statistics = TRY(zip_file->calculate_statistics());
  101. return TRY(String::formatted("{}, {} {}, {} {} totaling {} uncompressed",
  102. description,
  103. statistics.directory_count(),
  104. statistics.directory_count() == 1 ? "directory" : "directories",
  105. statistics.file_count(),
  106. statistics.file_count() == 1 ? "file" : "files",
  107. AK::human_readable_size(statistics.total_uncompressed_bytes())));
  108. }
  109. static ErrorOr<Optional<String>> elf_details(StringView description, StringView path)
  110. {
  111. auto mapped_file = TRY(Core::MappedFile::map(path));
  112. auto elf_data = mapped_file->bytes();
  113. ELF::Image elf_image(elf_data);
  114. if (!elf_image.is_valid())
  115. return OptionalNone {};
  116. StringBuilder interpreter_path_builder;
  117. auto result_or_error = ELF::validate_program_headers(*(const ElfW(Ehdr)*)elf_data.data(), elf_data.size(), elf_data, &interpreter_path_builder);
  118. if (result_or_error.is_error() || !result_or_error.value())
  119. return OptionalNone {};
  120. auto interpreter_path = interpreter_path_builder.string_view();
  121. auto& header = *reinterpret_cast<const ElfW(Ehdr)*>(elf_data.data());
  122. auto bitness = header.e_ident[EI_CLASS] == ELFCLASS64 ? "64" : "32";
  123. auto byteorder = header.e_ident[EI_DATA] == ELFDATA2LSB ? "LSB" : "MSB";
  124. bool is_dynamically_linked = !interpreter_path.is_empty();
  125. auto dynamic_section = TRY(String::formatted(", dynamically linked, interpreter {}", interpreter_path));
  126. return TRY(String::formatted("{} {}-bit {} {}, {}, version {} ({}){}",
  127. description,
  128. bitness,
  129. byteorder,
  130. ELF::Image::object_file_type_to_string(header.e_type).value_or("(?)"sv),
  131. ELF::Image::object_machine_type_to_string(header.e_machine).value_or("(?)"sv),
  132. header.e_ident[EI_ABIVERSION],
  133. ELF::Image::object_abi_type_to_string(header.e_ident[EI_OSABI]).value_or("(?)"sv),
  134. is_dynamically_linked ? dynamic_section : String {}));
  135. }
  136. struct PatternAndFunction {
  137. StringView matching_pattern;
  138. ErrorOr<Optional<String>> (*details)(StringView, StringView);
  139. };
  140. static constexpr Array s_pattern_with_specialized_functions {
  141. PatternAndFunction { "application/gzip"sv, gzip_details },
  142. PatternAndFunction { "application/zip"sv, zip_details },
  143. PatternAndFunction { "extra/elf"sv, elf_details },
  144. PatternAndFunction { "audio/*"sv, audio_details },
  145. PatternAndFunction { "image/*"sv, image_details },
  146. };
  147. static ErrorOr<Optional<String>> get_description_from_mime_type(StringView mime, StringView path)
  148. {
  149. auto const description = Core::get_description_from_mime_type(mime);
  150. if (!description.has_value())
  151. return OptionalNone {};
  152. for (auto const& pattern : s_pattern_with_specialized_functions) {
  153. if (mime.matches(pattern.matching_pattern))
  154. return pattern.details(*description, path);
  155. }
  156. return description_only(*description, path);
  157. }
  158. ErrorOr<int> serenity_main(Main::Arguments arguments)
  159. {
  160. TRY(Core::System::pledge("stdio rpath"));
  161. Vector<StringView> paths;
  162. bool flag_mime_only = false;
  163. bool flag_brief_mode = false;
  164. Core::ArgsParser args_parser;
  165. args_parser.set_general_help("Determine type of files");
  166. args_parser.add_option(flag_mime_only, "Only print mime type", "mime-type", 'I');
  167. args_parser.add_option(flag_brief_mode, "Do not prepend file names to output lines", "brief", 'b');
  168. args_parser.add_positional_argument(paths, "Files to identify", "files", Core::ArgsParser::Required::Yes);
  169. args_parser.parse(arguments);
  170. bool all_ok = true;
  171. for (auto const& path : paths) {
  172. auto file_or_error = Core::File::open(path, Core::File::OpenMode::Read);
  173. if (file_or_error.is_error()) {
  174. warnln("{}: {}", path, file_or_error.release_error());
  175. all_ok = false;
  176. continue;
  177. }
  178. auto file = file_or_error.release_value();
  179. struct stat file_stat = TRY(Core::System::lstat(path));
  180. if (!flag_brief_mode)
  181. out("{}: ", path);
  182. auto file_size_in_bytes = file_stat.st_size;
  183. if (S_ISDIR(file_stat.st_mode)) {
  184. outln("directory");
  185. } else if (!file_size_in_bytes) {
  186. outln("empty");
  187. } else {
  188. auto file_name_guess = Core::guess_mime_type_based_on_filename(path);
  189. auto mime_type = Core::guess_mime_type_based_on_sniffed_bytes(*file).value_or(file_name_guess);
  190. auto human_readable_description = TRY(get_description_from_mime_type(mime_type, path));
  191. if (!human_readable_description.has_value())
  192. human_readable_description = TRY(String::from_utf8(mime_type));
  193. outln("{}", flag_mime_only ? mime_type : *human_readable_description);
  194. }
  195. }
  196. return all_ok ? 0 : 1;
  197. }