|
@@ -5,67 +5,41 @@
|
|
|
*/
|
|
|
|
|
|
#include <LibCore/ArgsParser.h>
|
|
|
-#include <LibCore/File.h>
|
|
|
+#include <LibCore/EventLoop.h>
|
|
|
+#include <LibCore/FileWatcher.h>
|
|
|
+#include <LibCore/Stream.h>
|
|
|
#include <LibCore/System.h>
|
|
|
-#include <LibMain/Main.h>
|
|
|
-#include <stdlib.h>
|
|
|
-#include <unistd.h>
|
|
|
|
|
|
#define DEFAULT_LINE_COUNT 10
|
|
|
|
|
|
-static int tail_from_pos(Core::File& file, off_t startline, bool want_follow)
|
|
|
+static ErrorOr<void> tail_from_pos(Core::Stream::File& file, off_t startline)
|
|
|
{
|
|
|
- if (!file.seek(startline + 1))
|
|
|
- return 1;
|
|
|
-
|
|
|
- while (true) {
|
|
|
- auto const& b = file.read(4096);
|
|
|
- if (b.is_empty()) {
|
|
|
- if (!want_follow) {
|
|
|
- break;
|
|
|
- } else {
|
|
|
- while (!file.can_read()) {
|
|
|
- // FIXME: would be nice to have access to can_read_from_fd with an infinite timeout
|
|
|
- usleep(100);
|
|
|
- }
|
|
|
- continue;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (write(STDOUT_FILENO, b.data(), b.size()) < 0)
|
|
|
- return 1;
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
+ TRY(file.seek(startline + 1, Core::Stream::SeekMode::SetPosition));
|
|
|
+ auto buffer = TRY(file.read_all());
|
|
|
+ out("{}", StringView { buffer });
|
|
|
+ return {};
|
|
|
}
|
|
|
|
|
|
-static off_t find_seek_pos(Core::File& file, int wanted_lines)
|
|
|
+static ErrorOr<off_t> find_seek_pos(Core::Stream::File& file, int wanted_lines)
|
|
|
{
|
|
|
// Rather than reading the whole file, start at the end and work backwards,
|
|
|
// stopping when we've found the number of lines we want.
|
|
|
- off_t pos = 0;
|
|
|
- if (!file.seek(0, Core::SeekMode::FromEndPosition, &pos)) {
|
|
|
- warnln("Failed to find end of file: {}", file.error_string());
|
|
|
- return 1;
|
|
|
- }
|
|
|
+ off_t pos = TRY(file.seek(0, Core::Stream::SeekMode::FromEndPosition));
|
|
|
|
|
|
off_t end = pos;
|
|
|
int lines = 0;
|
|
|
|
|
|
- // FIXME: Reading char-by-char is only OK if IODevice's read buffer
|
|
|
- // is smart enough to not read char-by-char. Fix it there, or fix it here :)
|
|
|
for (; pos >= 0; pos--) {
|
|
|
- file.seek(pos);
|
|
|
- auto const& ch = file.read(1);
|
|
|
- if (ch.is_empty()) {
|
|
|
- // Presumably the file got truncated?
|
|
|
- // Keep trying to read backwards...
|
|
|
- } else {
|
|
|
- if (*ch.data() == '\n' && (end - pos) > 1) {
|
|
|
- lines++;
|
|
|
- if (lines == wanted_lines)
|
|
|
- break;
|
|
|
- }
|
|
|
+ TRY(file.seek(pos, Core::Stream::SeekMode::SetPosition));
|
|
|
+
|
|
|
+ if (file.is_eof())
|
|
|
+ break;
|
|
|
+ Array<u8, 1> buffer;
|
|
|
+ auto ch = TRY(file.read(buffer));
|
|
|
+ if (*ch.data() == '\n' && (end - pos) > 1) {
|
|
|
+ lines++;
|
|
|
+ if (lines == wanted_lines)
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -77,19 +51,85 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|
|
TRY(Core::System::pledge("stdio rpath"));
|
|
|
|
|
|
bool follow = false;
|
|
|
- int line_count = DEFAULT_LINE_COUNT;
|
|
|
- char const* file = nullptr;
|
|
|
+ size_t wanted_line_count = DEFAULT_LINE_COUNT;
|
|
|
+ StringView file;
|
|
|
|
|
|
Core::ArgsParser args_parser;
|
|
|
args_parser.set_general_help("Print the end ('tail') of a file.");
|
|
|
args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f');
|
|
|
- args_parser.add_option(line_count, "Fetch the specified number of lines", "lines", 'n', "number");
|
|
|
- args_parser.add_positional_argument(file, "File path", "file");
|
|
|
+ args_parser.add_option(wanted_line_count, "Fetch the specified number of lines", "lines", 'n', "number");
|
|
|
+ args_parser.add_positional_argument(file, "File path", "file", Core::ArgsParser::Required::No);
|
|
|
args_parser.parse(arguments);
|
|
|
|
|
|
- auto f = TRY(Core::File::open(file, Core::OpenMode::ReadOnly));
|
|
|
- TRY(Core::System::pledge("stdio"));
|
|
|
+ auto f = TRY(Core::Stream::File::open_file_or_standard_stream(file, Core::Stream::OpenMode::Read));
|
|
|
+ if (!follow)
|
|
|
+ TRY(Core::System::pledge("stdio"));
|
|
|
|
|
|
- auto pos = find_seek_pos(*f, line_count);
|
|
|
- return tail_from_pos(*f, pos, follow);
|
|
|
+ auto file_is_seekable = !f->tell().is_error();
|
|
|
+ if (!file_is_seekable) {
|
|
|
+ do {
|
|
|
+ // FIXME: If f is the standard input, f->read_all() does not block
|
|
|
+ // anymore after sending EOF (^D), despite f->is_open() returning true.
|
|
|
+ auto buffer = TRY(f->read_all(PAGE_SIZE));
|
|
|
+ auto line_count = StringView(buffer).count("\n"sv);
|
|
|
+ auto bytes = buffer.bytes();
|
|
|
+ size_t line_index = 0;
|
|
|
+ StringBuilder line;
|
|
|
+
|
|
|
+ if (!line_count && wanted_line_count) {
|
|
|
+ out("{}", StringView { bytes });
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (size_t i = 0; i < bytes.size(); i++) {
|
|
|
+ auto ch = bytes.at(i);
|
|
|
+ line.append(ch);
|
|
|
+ if (ch == '\n') {
|
|
|
+ if (wanted_line_count > line_count || line_index >= line_count - wanted_line_count)
|
|
|
+ out("{}", line.build());
|
|
|
+ line_index++;
|
|
|
+ line.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Since we can't have FileWatchers on the standard input either,
|
|
|
+ // we just loop forever if the -f option was passed.
|
|
|
+ } while (follow);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto pos = TRY(find_seek_pos(*f, wanted_line_count));
|
|
|
+ TRY(tail_from_pos(*f, pos));
|
|
|
+
|
|
|
+ if (follow) {
|
|
|
+ TRY(f->seek(0, Core::Stream::SeekMode::FromEndPosition));
|
|
|
+
|
|
|
+ Core::EventLoop event_loop;
|
|
|
+ auto watcher = TRY(Core::FileWatcher::create());
|
|
|
+ watcher->on_change = [&](Core::FileWatcherEvent const& event) {
|
|
|
+ if (event.type == Core::FileWatcherEvent::Type::ContentModified) {
|
|
|
+ auto buffer_or_error = f->read_all();
|
|
|
+ if (buffer_or_error.is_error()) {
|
|
|
+ auto error = buffer_or_error.error();
|
|
|
+ warnln(error.string_literal());
|
|
|
+ event_loop.quit(error.code());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ auto bytes = buffer_or_error.value().bytes();
|
|
|
+ out("{}", StringView { bytes });
|
|
|
+
|
|
|
+ auto potential_error = f->seek(0, Core::Stream::SeekMode::FromEndPosition);
|
|
|
+ if (potential_error.is_error()) {
|
|
|
+ auto error = potential_error.error();
|
|
|
+ warnln(error.string_literal());
|
|
|
+ event_loop.quit(error.code());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ TRY(watcher->add_watch(file, Core::FileWatcherEvent::Type::ContentModified));
|
|
|
+ TRY(Core::System::pledge("stdio"));
|
|
|
+ return event_loop.exec();
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
}
|