1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/FileWatcher.h>
#include <LibCore/Stream.h>
#include <LibCore/System.h>
#define DEFAULT_LINE_COUNT 10
static ErrorOr<void> tail_from_pos(Core::Stream::File& file, off_t startline)
{
TRY(file.seek(startline + 1, Core::Stream::SeekMode::SetPosition));
auto buffer = TRY(file.read_until_eof());
out("{}", StringView { buffer });
return {};
}
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 = TRY(file.seek(0, Core::Stream::SeekMode::FromEndPosition));
off_t end = pos;
int lines = 0;
for (; pos >= 0; pos--) {
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;
}
}
return pos;
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
TRY(Core::System::pledge("stdio rpath"));
bool follow = false;
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(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::Stream::File::open_file_or_standard_stream(file, Core::Stream::OpenMode::Read));
if (!follow)
TRY(Core::System::pledge("stdio"));
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_until_eof(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_until_eof();
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;
}
|