Serenity Operating System
1/*
2 * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <LibCompress/Gzip.h>
8#include <LibCore/ArgsParser.h>
9#include <LibCore/File.h>
10#include <LibCore/MappedFile.h>
11#include <LibCore/System.h>
12#include <LibMain/Main.h>
13#include <unistd.h>
14
15ErrorOr<int> serenity_main(Main::Arguments arguments)
16{
17 Vector<StringView> filenames;
18 bool keep_input_files { false };
19 bool write_to_stdout { false };
20 bool decompress { false };
21
22 Core::ArgsParser args_parser;
23 args_parser.add_option(keep_input_files, "Keep (don't delete) input files", "keep", 'k');
24 args_parser.add_option(write_to_stdout, "Write to stdout, keep original files unchanged", "stdout", 'c');
25 args_parser.add_option(decompress, "Decompress", "decompress", 'd');
26 args_parser.add_positional_argument(filenames, "Files", "FILES");
27 args_parser.parse(arguments);
28
29 if (write_to_stdout)
30 keep_input_files = true;
31
32 for (auto const& input_filename : filenames) {
33 DeprecatedString output_filename;
34 if (decompress) {
35 if (!input_filename.ends_with(".gz"sv)) {
36 warnln("unknown suffix for: {}, skipping", input_filename);
37 continue;
38 }
39 output_filename = input_filename.substring_view(0, input_filename.length() - ".gz"sv.length());
40 } else {
41 output_filename = DeprecatedString::formatted("{}.gz", input_filename);
42 }
43
44 // We map the whole file instead of streaming to reduce size overhead (gzip header) and increase the deflate block size (better compression)
45 // TODO: automatically fallback to buffered streaming for very large files
46 RefPtr<Core::MappedFile> file;
47 ReadonlyBytes input_bytes;
48 if (TRY(Core::System::stat(input_filename)).st_size > 0) {
49 file = TRY(Core::MappedFile::map(input_filename));
50 input_bytes = file->bytes();
51 }
52
53 ByteBuffer output_bytes;
54 if (decompress)
55 output_bytes = TRY(Compress::GzipDecompressor::decompress_all(input_bytes));
56 else
57 output_bytes = TRY(Compress::GzipCompressor::compress_all(input_bytes));
58
59 auto output_stream = write_to_stdout ? TRY(Core::File::standard_output()) : TRY(Core::File::open(output_filename, Core::File::OpenMode::Write));
60 TRY(output_stream->write_until_depleted(output_bytes));
61
62 if (!keep_input_files) {
63 TRY(Core::System::unlink(input_filename));
64 }
65 }
66 return 0;
67}