Serenity Operating System
at master 199 lines 11 kB view raw
1/* 2 * Copyright (c) 2021, Valtteri Koskivuori <vkoskiv@gmail.com> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Vector.h> 8#include <LibCompress/Gzip.h> 9#include <LibCore/ArgsParser.h> 10#include <LibCore/File.h> 11#include <LibCore/MappedFile.h> 12#include <LibCore/MimeData.h> 13#include <LibCore/System.h> 14#include <LibELF/Image.h> 15#include <LibELF/Validation.h> 16#include <LibGfx/ImageDecoder.h> 17#include <LibMain/Main.h> 18#include <stdio.h> 19#include <sys/stat.h> 20#include <unistd.h> 21 22static Optional<DeprecatedString> description_only(StringView description, StringView) 23{ 24 return description; 25} 26 27// FIXME: Ideally Gfx::ImageDecoder could tell us the image type directly. 28static Optional<DeprecatedString> image_details(StringView description, StringView path) 29{ 30 auto file_or_error = Core::MappedFile::map(path); 31 if (file_or_error.is_error()) 32 return {}; 33 34 auto& mapped_file = *file_or_error.value(); 35 auto mime_type = Core::guess_mime_type_based_on_filename(path); 36 auto image_decoder = Gfx::ImageDecoder::try_create_for_raw_bytes(mapped_file.bytes(), mime_type); 37 if (!image_decoder) 38 return {}; 39 40 StringBuilder builder; 41 builder.appendff("{}, {} x {}", description, image_decoder->width(), image_decoder->height()); 42 if (image_decoder->is_animated()) { 43 builder.appendff(", animated with {} frames that loop", image_decoder->frame_count()); 44 int loop_count = image_decoder->loop_count(); 45 if (loop_count == 0) 46 builder.appendff(" indefinitely"); 47 else 48 builder.appendff(" {} {}", loop_count, loop_count == 1 ? "time" : "times"); 49 } 50 51 return builder.to_deprecated_string(); 52} 53 54static Optional<DeprecatedString> gzip_details(StringView description, StringView path) 55{ 56 auto file_or_error = Core::MappedFile::map(path); 57 if (file_or_error.is_error()) 58 return {}; 59 60 auto& mapped_file = *file_or_error.value(); 61 if (!Compress::GzipDecompressor::is_likely_compressed(mapped_file.bytes())) 62 return {}; 63 64 auto gzip_details = Compress::GzipDecompressor::describe_header(mapped_file.bytes()); 65 if (!gzip_details.has_value()) 66 return {}; 67 68 return DeprecatedString::formatted("{}, {}", description, gzip_details.value()); 69} 70 71static Optional<DeprecatedString> elf_details(StringView description, StringView path) 72{ 73 auto file_or_error = Core::MappedFile::map(path); 74 if (file_or_error.is_error()) 75 return {}; 76 auto& mapped_file = *file_or_error.value(); 77 auto elf_data = mapped_file.bytes(); 78 ELF::Image elf_image(elf_data); 79 if (!elf_image.is_valid()) 80 return {}; 81 82 StringBuilder interpreter_path_builder; 83 auto result_or_error = ELF::validate_program_headers(*(const ElfW(Ehdr)*)elf_data.data(), elf_data.size(), elf_data, &interpreter_path_builder); 84 if (result_or_error.is_error() || !result_or_error.value()) 85 return {}; 86 auto interpreter_path = interpreter_path_builder.string_view(); 87 88 auto& header = *reinterpret_cast<const ElfW(Ehdr)*>(elf_data.data()); 89 90 auto bitness = header.e_ident[EI_CLASS] == ELFCLASS64 ? "64" : "32"; 91 auto byteorder = header.e_ident[EI_DATA] == ELFDATA2LSB ? "LSB" : "MSB"; 92 93 bool is_dynamically_linked = !interpreter_path.is_empty(); 94 DeprecatedString dynamic_section = DeprecatedString::formatted(", dynamically linked, interpreter {}", interpreter_path); 95 96 return DeprecatedString::formatted("{} {}-bit {} {}, {}, version {} ({}){}", 97 description, 98 bitness, 99 byteorder, 100 ELF::Image::object_file_type_to_string(header.e_type).value_or("(?)"sv), 101 ELF::Image::object_machine_type_to_string(header.e_machine).value_or("(?)"sv), 102 header.e_ident[EI_ABIVERSION], 103 ELF::Image::object_abi_type_to_string(header.e_ident[EI_OSABI]).value_or("(?)"sv), 104 is_dynamically_linked ? dynamic_section : ""); 105} 106 107#define ENUMERATE_MIME_TYPE_DESCRIPTIONS \ 108 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/gzip"sv, "gzip compressed data"sv, gzip_details) \ 109 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/javascript"sv, "JavaScript source"sv, description_only) \ 110 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/json"sv, "JSON data"sv, description_only) \ 111 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/pdf"sv, "PDF document"sv, description_only) \ 112 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/rtf"sv, "Rich text file"sv, description_only) \ 113 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/tar"sv, "tape archive"sv, description_only) \ 114 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/wasm"sv, "WebAssembly bytecode"sv, description_only) \ 115 __ENUMERATE_MIME_TYPE_DESCRIPTION("application/x-7z-compressed"sv, "7-Zip archive"sv, description_only) \ 116 __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/flac"sv, "FLAC audio"sv, description_only) \ 117 __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/midi"sv, "MIDI notes"sv, description_only) \ 118 __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/mpeg"sv, "MP3 audio"sv, description_only) \ 119 __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/qoa"sv, "Quite OK Audio"sv, description_only) \ 120 __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/wave"sv, "WAVE audio"sv, description_only) \ 121 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/blender"sv, "Blender project file"sv, description_only) \ 122 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/elf"sv, "ELF"sv, elf_details) \ 123 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/ext"sv, "ext filesystem"sv, description_only) \ 124 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/iso-9660"sv, "ISO 9660 CD/DVD image"sv, description_only) \ 125 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/isz"sv, "Compressed ISO image"sv, description_only) \ 126 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/lua-bytecode"sv, "Lua bytecode"sv, description_only) \ 127 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/matroska"sv, "Matroska container"sv, description_only) \ 128 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/nes-rom"sv, "Nintendo Entertainment System ROM"sv, description_only) \ 129 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/qcow"sv, "qcow file"sv, description_only) \ 130 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/raw-zlib"sv, "raw zlib stream"sv, description_only) \ 131 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/sqlite"sv, "sqlite database"sv, description_only) \ 132 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/win-31x-compressed"sv, "Windows 3.1X compressed file"sv, description_only) \ 133 __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/win-95-compressed"sv, "Windows 95 compressed file"sv, description_only) \ 134 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/bmp"sv, "BMP image data"sv, image_details) \ 135 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/gif"sv, "GIF image data"sv, image_details) \ 136 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/jpeg"sv, "JPEG image data"sv, image_details) \ 137 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/png"sv, "PNG image data"sv, image_details) \ 138 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/webp"sv, "WebP image data"sv, image_details) \ 139 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-bitmap"sv, "PBM image data"sv, image_details) \ 140 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-graymap"sv, "PGM image data"sv, image_details) \ 141 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-pixmap"sv, "PPM image data"sv, image_details) \ 142 __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-qoi"sv, "QOI image data"sv, image_details) \ 143 __ENUMERATE_MIME_TYPE_DESCRIPTION("text/markdown"sv, "Markdown document"sv, description_only) \ 144 __ENUMERATE_MIME_TYPE_DESCRIPTION("text/x-shellscript"sv, "POSIX shell script text executable"sv, description_only) 145 146static Optional<DeprecatedString> get_description_from_mime_type(StringView mime, StringView path) 147{ 148#define __ENUMERATE_MIME_TYPE_DESCRIPTION(mime_type, description, details) \ 149 if (mime_type == mime) \ 150 return details(description, path); 151 ENUMERATE_MIME_TYPE_DESCRIPTIONS; 152#undef __ENUMERATE_MIME_TYPE_DESCRIPTION 153 return {}; 154} 155 156ErrorOr<int> serenity_main(Main::Arguments arguments) 157{ 158 TRY(Core::System::pledge("stdio rpath")); 159 160 Vector<StringView> paths; 161 bool flag_mime_only = false; 162 163 Core::ArgsParser args_parser; 164 args_parser.set_general_help("Determine type of files"); 165 args_parser.add_option(flag_mime_only, "Only print mime type", "mime-type", 'I'); 166 args_parser.add_positional_argument(paths, "Files to identify", "files", Core::ArgsParser::Required::Yes); 167 args_parser.parse(arguments); 168 169 bool all_ok = true; 170 // Read accounts for longest possible offset + signature we currently match against. 171 auto buffer = TRY(ByteBuffer::create_uninitialized(0x9006)); 172 173 for (auto const& path : paths) { 174 auto file_or_error = Core::File::open(path, Core::File::OpenMode::Read); 175 if (file_or_error.is_error()) { 176 warnln("{}: {}", path, file_or_error.release_error()); 177 all_ok = false; 178 continue; 179 } 180 auto file = file_or_error.release_value(); 181 182 struct stat file_stat = TRY(Core::System::lstat(path)); 183 184 auto file_size_in_bytes = file_stat.st_size; 185 if (S_ISDIR(file_stat.st_mode)) { 186 outln("{}: directory", path); 187 } else if (!file_size_in_bytes) { 188 outln("{}: empty", path); 189 } else { 190 auto bytes = TRY(file->read_some(buffer)); 191 auto file_name_guess = Core::guess_mime_type_based_on_filename(path); 192 auto mime_type = Core::guess_mime_type_based_on_sniffed_bytes(bytes).value_or(file_name_guess); 193 auto human_readable_description = get_description_from_mime_type(mime_type, path).value_or(mime_type); 194 outln("{}: {}", path, flag_mime_only ? mime_type : human_readable_description); 195 } 196 } 197 198 return all_ok ? 0 : 1; 199}