5
5
*/
6
6
7
7
#include < LibCore/ArgsParser.h>
8
- #include < LibCore/File.h>
8
+ #include < LibCore/EventLoop.h>
9
+ #include < LibCore/FileWatcher.h>
10
+ #include < LibCore/Stream.h>
9
11
#include < LibCore/System.h>
10
- #include < LibMain/Main.h>
11
- #include < stdlib.h>
12
- #include < unistd.h>
13
12
14
13
#define DEFAULT_LINE_COUNT 10
15
14
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)
17
16
{
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 {};
40
21
}
41
22
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)
43
24
{
44
25
// Rather than reading the whole file, start at the end and work backwards,
45
26
// 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));
51
28
52
29
off_t end = pos;
53
30
int lines = 0 ;
54
31
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 :)
57
32
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 ;
69
43
}
70
44
}
71
45
@@ -77,19 +51,85 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
77
51
TRY (Core::System::pledge (" stdio rpath" ));
78
52
79
53
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;
82
56
83
57
Core::ArgsParser args_parser;
84
58
args_parser.set_general_help (" Print the end ('tail') of a file." );
85
59
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 );
88
62
args_parser.parse (arguments);
89
63
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" ));
92
67
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 ;
95
135
}
0 commit comments