Skip to content

Commit c8f790e

Browse files
demostanisADKaster
authored andcommitted
tail: Port to Core::Stream, use Core::FileWatcher
1 parent 629fbc2 commit c8f790e

File tree

1 file changed

+94
-54
lines changed

1 file changed

+94
-54
lines changed

Userland/Utilities/tail.cpp

Lines changed: 94 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,41 @@
55
*/
66

77
#include <LibCore/ArgsParser.h>
8-
#include <LibCore/File.h>
8+
#include <LibCore/EventLoop.h>
9+
#include <LibCore/FileWatcher.h>
10+
#include <LibCore/Stream.h>
911
#include <LibCore/System.h>
10-
#include <LibMain/Main.h>
11-
#include <stdlib.h>
12-
#include <unistd.h>
1312

1413
#define DEFAULT_LINE_COUNT 10
1514

16-
static int tail_from_pos(Core::File& file, off_t startline, bool want_follow)
15+
static ErrorOr<void> tail_from_pos(Core::Stream::File& file, off_t startline)
1716
{
18-
if (!file.seek(startline + 1))
19-
return 1;
20-
21-
while (true) {
22-
auto const& b = file.read(4096);
23-
if (b.is_empty()) {
24-
if (!want_follow) {
25-
break;
26-
} else {
27-
while (!file.can_read()) {
28-
// FIXME: would be nice to have access to can_read_from_fd with an infinite timeout
29-
usleep(100);
30-
}
31-
continue;
32-
}
33-
}
34-
35-
if (write(STDOUT_FILENO, b.data(), b.size()) < 0)
36-
return 1;
37-
}
38-
39-
return 0;
17+
TRY(file.seek(startline + 1, Core::Stream::SeekMode::SetPosition));
18+
auto buffer = TRY(file.read_all());
19+
out("{}", StringView { buffer });
20+
return {};
4021
}
4122

42-
static off_t find_seek_pos(Core::File& file, int wanted_lines)
23+
static ErrorOr<off_t> find_seek_pos(Core::Stream::File& file, int wanted_lines)
4324
{
4425
// Rather than reading the whole file, start at the end and work backwards,
4526
// stopping when we've found the number of lines we want.
46-
off_t pos = 0;
47-
if (!file.seek(0, Core::SeekMode::FromEndPosition, &pos)) {
48-
warnln("Failed to find end of file: {}", file.error_string());
49-
return 1;
50-
}
27+
off_t pos = TRY(file.seek(0, Core::Stream::SeekMode::FromEndPosition));
5128

5229
off_t end = pos;
5330
int lines = 0;
5431

55-
// FIXME: Reading char-by-char is only OK if IODevice's read buffer
56-
// is smart enough to not read char-by-char. Fix it there, or fix it here :)
5732
for (; pos >= 0; pos--) {
58-
file.seek(pos);
59-
auto const& ch = file.read(1);
60-
if (ch.is_empty()) {
61-
// Presumably the file got truncated?
62-
// Keep trying to read backwards...
63-
} else {
64-
if (*ch.data() == '\n' && (end - pos) > 1) {
65-
lines++;
66-
if (lines == wanted_lines)
67-
break;
68-
}
33+
TRY(file.seek(pos, Core::Stream::SeekMode::SetPosition));
34+
35+
if (file.is_eof())
36+
break;
37+
Array<u8, 1> buffer;
38+
auto ch = TRY(file.read(buffer));
39+
if (*ch.data() == '\n' && (end - pos) > 1) {
40+
lines++;
41+
if (lines == wanted_lines)
42+
break;
6943
}
7044
}
7145

@@ -77,19 +51,85 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
7751
TRY(Core::System::pledge("stdio rpath"));
7852

7953
bool follow = false;
80-
int line_count = DEFAULT_LINE_COUNT;
81-
char const* file = nullptr;
54+
size_t wanted_line_count = DEFAULT_LINE_COUNT;
55+
StringView file;
8256

8357
Core::ArgsParser args_parser;
8458
args_parser.set_general_help("Print the end ('tail') of a file.");
8559
args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f');
86-
args_parser.add_option(line_count, "Fetch the specified number of lines", "lines", 'n', "number");
87-
args_parser.add_positional_argument(file, "File path", "file");
60+
args_parser.add_option(wanted_line_count, "Fetch the specified number of lines", "lines", 'n', "number");
61+
args_parser.add_positional_argument(file, "File path", "file", Core::ArgsParser::Required::No);
8862
args_parser.parse(arguments);
8963

90-
auto f = TRY(Core::File::open(file, Core::OpenMode::ReadOnly));
91-
TRY(Core::System::pledge("stdio"));
64+
auto f = TRY(Core::Stream::File::open_file_or_standard_stream(file, Core::Stream::OpenMode::Read));
65+
if (!follow)
66+
TRY(Core::System::pledge("stdio"));
9267

93-
auto pos = find_seek_pos(*f, line_count);
94-
return tail_from_pos(*f, pos, follow);
68+
auto file_is_seekable = !f->tell().is_error();
69+
if (!file_is_seekable) {
70+
do {
71+
// FIXME: If f is the standard input, f->read_all() does not block
72+
// anymore after sending EOF (^D), despite f->is_open() returning true.
73+
auto buffer = TRY(f->read_all(PAGE_SIZE));
74+
auto line_count = StringView(buffer).count("\n"sv);
75+
auto bytes = buffer.bytes();
76+
size_t line_index = 0;
77+
StringBuilder line;
78+
79+
if (!line_count && wanted_line_count) {
80+
out("{}", StringView { bytes });
81+
continue;
82+
}
83+
84+
for (size_t i = 0; i < bytes.size(); i++) {
85+
auto ch = bytes.at(i);
86+
line.append(ch);
87+
if (ch == '\n') {
88+
if (wanted_line_count > line_count || line_index >= line_count - wanted_line_count)
89+
out("{}", line.build());
90+
line_index++;
91+
line.clear();
92+
}
93+
}
94+
95+
// Since we can't have FileWatchers on the standard input either,
96+
// we just loop forever if the -f option was passed.
97+
} while (follow);
98+
return 0;
99+
}
100+
101+
auto pos = TRY(find_seek_pos(*f, wanted_line_count));
102+
TRY(tail_from_pos(*f, pos));
103+
104+
if (follow) {
105+
TRY(f->seek(0, Core::Stream::SeekMode::FromEndPosition));
106+
107+
Core::EventLoop event_loop;
108+
auto watcher = TRY(Core::FileWatcher::create());
109+
watcher->on_change = [&](Core::FileWatcherEvent const& event) {
110+
if (event.type == Core::FileWatcherEvent::Type::ContentModified) {
111+
auto buffer_or_error = f->read_all();
112+
if (buffer_or_error.is_error()) {
113+
auto error = buffer_or_error.error();
114+
warnln(error.string_literal());
115+
event_loop.quit(error.code());
116+
return;
117+
}
118+
auto bytes = buffer_or_error.value().bytes();
119+
out("{}", StringView { bytes });
120+
121+
auto potential_error = f->seek(0, Core::Stream::SeekMode::FromEndPosition);
122+
if (potential_error.is_error()) {
123+
auto error = potential_error.error();
124+
warnln(error.string_literal());
125+
event_loop.quit(error.code());
126+
return;
127+
}
128+
}
129+
};
130+
TRY(watcher->add_watch(file, Core::FileWatcherEvent::Type::ContentModified));
131+
TRY(Core::System::pledge("stdio"));
132+
return event_loop.exec();
133+
}
134+
return 0;
95135
}

0 commit comments

Comments
 (0)