From c8fda23a0316ba525612e6ef248a13915470445c Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Thu, 16 May 2019 15:02:17 +0200 Subject: [PATCH] LibCore/Userland: Introduce a simple tail implementation Also introduce more seek modes on CIODevice, and an out param to find the current position inside the file -- this means less syscalls (and less potential races) than requesting it through a separate pos() accessor or something. --- LibCore/CIODevice.cpp | 22 ++++++-- LibCore/CIODevice.h | 8 ++- Userland/tail.cpp | 117 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 Userland/tail.cpp diff --git a/LibCore/CIODevice.cpp b/LibCore/CIODevice.cpp index fee3b72884..c4a5437aeb 100644 --- a/LibCore/CIODevice.cpp +++ b/LibCore/CIODevice.cpp @@ -174,15 +174,31 @@ bool CIODevice::close() return true; } -bool CIODevice::seek(signed_qword offset) +bool CIODevice::seek(signed_qword offset, SeekMode mode, off_t *pos) { - int rc = lseek(m_fd, offset, SEEK_SET); + int m = SEEK_SET; + switch (mode) { + case SeekMode::SetPosition: + m = SEEK_SET; + break; + case SeekMode::FromCurrentPosition: + m = SEEK_CUR; + break; + case SeekMode::FromEndPosition: + m = SEEK_END; + break; + } + off_t rc = lseek(m_fd, offset, m); if (rc < 0) { - perror("CIODevice::seek: lseek"); + set_error(errno); + if (pos) + *pos = -1; return false; } m_buffered_data.clear(); m_eof = false; + if (pos) + *pos = rc; return true; } diff --git a/LibCore/CIODevice.h b/LibCore/CIODevice.h index 5e892eb4aa..79e589eb9f 100644 --- a/LibCore/CIODevice.h +++ b/LibCore/CIODevice.h @@ -37,7 +37,13 @@ public: bool can_read() const; - bool seek(signed_qword); + enum class SeekMode { + SetPosition, + FromCurrentPosition, + FromEndPosition, + }; + + bool seek(signed_qword, SeekMode = SeekMode::SetPosition, off_t* = nullptr); virtual bool open(CIODevice::OpenMode) = 0; virtual bool close(); diff --git a/Userland/tail.cpp b/Userland/tail.cpp new file mode 100644 index 0000000000..c0970fc407 --- /dev/null +++ b/Userland/tail.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static void print_usage_and_exit() +{ + printf("usage: tail [-f, -n ] \n"); + exit(1); +} + +int tail_from_pos(CFile& file, off_t startline, bool want_follow) +{ + if (!file.seek(startline + 1)) + return -1; + + while (true) { + const auto& 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.pointer(), b.size()) < 0) { + return -1; + } + } + + return 0; +} + +off_t find_seek_pos(CFile& 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, CIODevice::SeekMode::FromEndPosition, &pos)) { + fprintf(stderr, "Failed to find end of file: %s\n", file.error_string()); + return -1; + } + + off_t end = pos; + int lines = 0; + + // FIXME: Reading char-by-char is only OK if CIODevice'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); + const auto& ch = file.read(1); + if (ch.is_empty()) { + // Presumably the file got truncated? + // Keep trying to read backwards... + } else { + if (*ch.pointer() == '\n' && (end - pos) > 1) { + lines++; + if (lines == wanted_lines) + break; + } + } + } + + return pos; +} + +static void exit_because_we_wanted_lines() +{ + fprintf(stderr, "Expected a line count after -n"); + exit(-1); +} + +int main(int argc, char *argv[]) +{ + int line_count = 0; + bool flag_follow = false; + int opt; + while ((opt = getopt(argc, argv, "fn:")) != -1) { + switch (opt) { + case 'f': + flag_follow = true; + break; + case 'n': + line_count = strtol(optarg, NULL, 10); + if (errno == EINVAL) { + exit_because_we_wanted_lines(); + } + break; + default: + print_usage_and_exit(); + } + } + const char *path = nullptr; + if (optind >= argc) { + print_usage_and_exit(); + } + + path = argv[optind]; + + CFile f(path); + if (!f.open(CIODevice::ReadOnly)) { + fprintf(stderr, "Error opening file %s: %s\n", path, strerror(errno)); + exit(-1); + } + + auto pos = find_seek_pos(f, line_count); + return tail_from_pos(f, pos, flag_follow); +}