Serenity Operating System
at master 134 lines 4.6 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibCore/ArgsParser.h> 8#include <LibCore/EventLoop.h> 9#include <LibCore/File.h> 10#include <LibCore/FileWatcher.h> 11#include <LibCore/System.h> 12 13#define DEFAULT_LINE_COUNT 10 14 15static ErrorOr<void> tail_from_pos(Core::File& file, off_t startline) 16{ 17 TRY(file.seek(startline + 1, SeekMode::SetPosition)); 18 auto buffer = TRY(file.read_until_eof()); 19 out("{}", StringView { buffer }); 20 return {}; 21} 22 23static ErrorOr<off_t> find_seek_pos(Core::File& file, int wanted_lines) 24{ 25 // Rather than reading the whole file, start at the end and work backwards, 26 // stopping when we've found the number of lines we want. 27 off_t pos = TRY(file.seek(0, SeekMode::FromEndPosition)); 28 29 off_t end = pos; 30 int lines = 0; 31 32 for (; pos >= 0; pos--) { 33 TRY(file.seek(pos, SeekMode::SetPosition)); 34 35 if (file.is_eof()) 36 break; 37 auto ch = TRY(file.read_value<u8>()); 38 if (ch == '\n' && (end - pos) > 1) { 39 lines++; 40 if (lines == wanted_lines) 41 break; 42 } 43 } 44 45 return pos; 46} 47 48ErrorOr<int> serenity_main(Main::Arguments arguments) 49{ 50 TRY(Core::System::pledge("stdio rpath")); 51 52 bool follow = false; 53 size_t wanted_line_count = DEFAULT_LINE_COUNT; 54 StringView file; 55 56 Core::ArgsParser args_parser; 57 args_parser.set_general_help("Print the end ('tail') of a file."); 58 args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f'); 59 args_parser.add_option(wanted_line_count, "Fetch the specified number of lines", "lines", 'n', "number"); 60 args_parser.add_positional_argument(file, "File path", "file", Core::ArgsParser::Required::No); 61 args_parser.parse(arguments); 62 63 auto f = TRY(Core::File::open_file_or_standard_stream(file, Core::File::OpenMode::Read)); 64 if (!follow) 65 TRY(Core::System::pledge("stdio")); 66 67 auto file_is_seekable = !f->tell().is_error(); 68 if (!file_is_seekable) { 69 do { 70 // FIXME: If f is the standard input, f->read_all() does not block 71 // anymore after sending EOF (^D), despite f->is_open() returning true. 72 auto buffer = TRY(f->read_until_eof(PAGE_SIZE)); 73 auto line_count = StringView(buffer).count("\n"sv); 74 auto bytes = buffer.bytes(); 75 size_t line_index = 0; 76 StringBuilder line; 77 78 if (!line_count && wanted_line_count) { 79 out("{}", StringView { bytes }); 80 continue; 81 } 82 83 for (size_t i = 0; i < bytes.size(); i++) { 84 auto ch = bytes.at(i); 85 line.append(ch); 86 if (ch == '\n') { 87 if (wanted_line_count > line_count || line_index >= line_count - wanted_line_count) 88 out("{}", line.to_deprecated_string()); 89 line_index++; 90 line.clear(); 91 } 92 } 93 94 // Since we can't have FileWatchers on the standard input either, 95 // we just loop forever if the -f option was passed. 96 } while (follow); 97 return 0; 98 } 99 100 auto pos = TRY(find_seek_pos(*f, wanted_line_count)); 101 TRY(tail_from_pos(*f, pos)); 102 103 if (follow) { 104 TRY(f->seek(0, SeekMode::FromEndPosition)); 105 106 Core::EventLoop event_loop; 107 auto watcher = TRY(Core::FileWatcher::create()); 108 watcher->on_change = [&](Core::FileWatcherEvent const& event) { 109 if (event.type == Core::FileWatcherEvent::Type::ContentModified) { 110 auto buffer_or_error = f->read_until_eof(); 111 if (buffer_or_error.is_error()) { 112 auto error = buffer_or_error.release_error(); 113 warnln(error.string_literal()); 114 event_loop.quit(error.code()); 115 return; 116 } 117 auto bytes = buffer_or_error.value().bytes(); 118 out("{}", StringView { bytes }); 119 120 auto potential_error = f->seek(0, SeekMode::FromEndPosition); 121 if (potential_error.is_error()) { 122 auto error = potential_error.release_error(); 123 warnln(error.string_literal()); 124 event_loop.quit(error.code()); 125 return; 126 } 127 } 128 }; 129 TRY(watcher->add_watch(file, Core::FileWatcherEvent::Type::ContentModified)); 130 TRY(Core::System::pledge("stdio")); 131 return event_loop.exec(); 132 } 133 return 0; 134}