Compare commits
4 Commits
7691b019e2
...
50024fbf8b
Author | SHA1 | Date |
---|---|---|
|
50024fbf8b | |
|
4d4fb3b6ec | |
|
37dea8aee7 | |
|
69c4940b27 |
|
@ -133,6 +133,9 @@ namespace Kernel
|
|||
void del_epoll(class Epoll*);
|
||||
void epoll_notify(uint32_t event);
|
||||
|
||||
virtual void on_close(int status_flags) { (void)status_flags; }
|
||||
virtual void on_clone(int status_flags) { (void)status_flags; }
|
||||
|
||||
protected:
|
||||
// Directory API
|
||||
virtual BAN::ErrorOr<BAN::RefPtr<Inode>> find_inode_impl(BAN::StringView) { return BAN::Error::from_errno(ENOTSUP); }
|
||||
|
|
|
@ -13,8 +13,9 @@ namespace Kernel
|
|||
static BAN::ErrorOr<BAN::RefPtr<Inode>> create(const Credentials&);
|
||||
|
||||
virtual bool is_pipe() const override { return true; }
|
||||
void clone_writing();
|
||||
void close_writing();
|
||||
|
||||
void on_close(int status_flags) override;
|
||||
void on_clone(int status_flags) override;
|
||||
|
||||
virtual ino_t ino() const override { return 0; } // FIXME
|
||||
virtual Mode mode() const override { return { Mode::IFIFO | Mode::IRUSR | Mode::IWUSR }; }
|
||||
|
@ -39,7 +40,7 @@ namespace Kernel
|
|||
|
||||
virtual bool can_read_impl() const override { return m_buffer_size > 0; }
|
||||
virtual bool can_write_impl() const override { return true; }
|
||||
virtual bool has_error_impl() const override { return false; }
|
||||
virtual bool has_error_impl() const override { return m_reading_count == 0; }
|
||||
virtual bool has_hungup_impl() const override { return m_writing_count == 0; }
|
||||
|
||||
private:
|
||||
|
@ -58,6 +59,7 @@ namespace Kernel
|
|||
size_t m_buffer_tail { 0 };
|
||||
|
||||
BAN::Atomic<uint32_t> m_writing_count { 1 };
|
||||
BAN::Atomic<uint32_t> m_reading_count { 1 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <kernel/Thread.h>
|
||||
#include <kernel/Timer/Timer.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
namespace Kernel
|
||||
|
@ -26,19 +27,41 @@ namespace Kernel
|
|||
m_ctime = current_time;
|
||||
}
|
||||
|
||||
void Pipe::clone_writing()
|
||||
void Pipe::on_clone(int status_flags)
|
||||
{
|
||||
[[maybe_unused]] auto old_writing_count = m_writing_count.fetch_add(1);
|
||||
ASSERT(old_writing_count > 0);
|
||||
if (status_flags & O_WRONLY)
|
||||
{
|
||||
[[maybe_unused]] auto old_writing_count = m_writing_count.fetch_add(1);
|
||||
ASSERT(old_writing_count > 0);
|
||||
}
|
||||
|
||||
if (status_flags & O_RDONLY)
|
||||
{
|
||||
[[maybe_unused]] auto old_reading_count = m_reading_count.fetch_add(1);
|
||||
ASSERT(old_reading_count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Pipe::close_writing()
|
||||
void Pipe::on_close(int status_flags)
|
||||
{
|
||||
auto old_writing_count = m_writing_count.fetch_sub(1);
|
||||
ASSERT(old_writing_count > 0);
|
||||
if (old_writing_count != 1)
|
||||
return;
|
||||
epoll_notify(EPOLLHUP);
|
||||
if (status_flags & O_WRONLY)
|
||||
{
|
||||
auto old_writing_count = m_writing_count.fetch_sub(1);
|
||||
ASSERT(old_writing_count > 0);
|
||||
if (old_writing_count != 1)
|
||||
return;
|
||||
epoll_notify(EPOLLHUP);
|
||||
}
|
||||
|
||||
if (status_flags & O_RDONLY)
|
||||
{
|
||||
auto old_reading_count = m_reading_count.fetch_sub(1);
|
||||
ASSERT(old_reading_count > 0);
|
||||
if (old_reading_count != 1)
|
||||
return;
|
||||
epoll_notify(EPOLLERR);
|
||||
}
|
||||
|
||||
m_thread_blocker.unblock();
|
||||
}
|
||||
|
||||
|
@ -84,6 +107,11 @@ namespace Kernel
|
|||
|
||||
while (m_buffer_size >= m_buffer.size())
|
||||
{
|
||||
if (m_reading_count == 0)
|
||||
{
|
||||
Thread::current().add_signal(SIGPIPE);
|
||||
return BAN::Error::from_errno(EPIPE);
|
||||
}
|
||||
LockFreeGuard lock_free(m_mutex);
|
||||
TRY(Thread::current().block_or_eintr_or_timeout_ms(m_thread_blocker, 100, false));
|
||||
}
|
||||
|
|
|
@ -43,12 +43,7 @@ namespace Kernel
|
|||
auto& open_file = m_open_files[fd];
|
||||
open_file.description = other.m_open_files[fd].description;
|
||||
open_file.descriptor_flags = other.m_open_files[fd].descriptor_flags;
|
||||
|
||||
if (open_file.path() == "<pipe wr>"_sv)
|
||||
{
|
||||
ASSERT(open_file.inode()->is_pipe());
|
||||
static_cast<Pipe*>(open_file.inode().ptr())->clone_writing();
|
||||
}
|
||||
open_file.inode()->on_clone(open_file.status_flags());
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -204,14 +199,10 @@ namespace Kernel
|
|||
|
||||
(void)close(fildes2);
|
||||
|
||||
m_open_files[fildes2].description = m_open_files[fildes].description;
|
||||
m_open_files[fildes2].descriptor_flags = 0;
|
||||
|
||||
if (m_open_files[fildes2].path() == "<pipe wr>"_sv)
|
||||
{
|
||||
ASSERT(m_open_files[fildes2].inode()->is_pipe());
|
||||
static_cast<Pipe*>(m_open_files[fildes2].inode().ptr())->clone_writing();
|
||||
}
|
||||
auto& open_file = m_open_files[fildes2];
|
||||
open_file.description = m_open_files[fildes].description;
|
||||
open_file.descriptor_flags = 0;
|
||||
open_file.inode()->on_clone(open_file.status_flags());
|
||||
|
||||
return fildes;
|
||||
}
|
||||
|
@ -229,13 +220,10 @@ namespace Kernel
|
|||
{
|
||||
const int new_fd = TRY(get_free_fd());
|
||||
|
||||
m_open_files[new_fd].description = m_open_files[fd].description;
|
||||
m_open_files[new_fd].descriptor_flags = (cmd == F_DUPFD_CLOEXEC) ? O_CLOEXEC : 0;
|
||||
if (m_open_files[new_fd].path() == "<pipe wr>"_sv)
|
||||
{
|
||||
ASSERT(m_open_files[new_fd].inode()->is_pipe());
|
||||
static_cast<Pipe*>(m_open_files[new_fd].inode().ptr())->clone_writing();
|
||||
}
|
||||
auto& open_file = m_open_files[new_fd];
|
||||
open_file.description = m_open_files[fd].description;
|
||||
open_file.descriptor_flags = (cmd == F_DUPFD_CLOEXEC) ? O_CLOEXEC : 0;
|
||||
open_file.inode()->on_clone(open_file.status_flags());
|
||||
|
||||
return new_fd;
|
||||
}
|
||||
|
@ -313,14 +301,10 @@ namespace Kernel
|
|||
|
||||
TRY(validate_fd(fd));
|
||||
|
||||
if (m_open_files[fd].path() == "<pipe wr>"_sv)
|
||||
{
|
||||
ASSERT(m_open_files[fd].inode()->is_pipe());
|
||||
static_cast<Pipe*>(m_open_files[fd].inode().ptr())->close_writing();
|
||||
}
|
||||
|
||||
m_open_files[fd].description.clear();
|
||||
m_open_files[fd].descriptor_flags = 0;
|
||||
auto& open_file = m_open_files[fd];
|
||||
open_file.inode()->on_close(open_file.status_flags());
|
||||
open_file.description.clear();
|
||||
open_file.descriptor_flags = 0;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -17,3 +17,10 @@ CONFIGURE_OPTIONS=(
|
|||
'vim_cv_stat_ignores_slash=yes'
|
||||
'vim_cv_memmove_handles_overlap=yes'
|
||||
)
|
||||
|
||||
install() {
|
||||
make install "DESTDIR=$BANAN_SYSROOT" || exit 1
|
||||
|
||||
shellrc="$BANAN_SYSROOT/home/user/.shellrc"
|
||||
grep -q 'export EDITOR=' "$shellrc" || echo 'export EDITOR=vim' >> "$shellrc"
|
||||
}
|
||||
|
|
|
@ -121,14 +121,14 @@ int sigismember(const sigset_t* set, int signo)
|
|||
|
||||
void (*signal(int sig, void (*func)(int)))(int)
|
||||
{
|
||||
struct sigaction act;
|
||||
struct sigaction act, oact;
|
||||
act.sa_handler = func;
|
||||
act.sa_flags = 0;
|
||||
|
||||
int ret = sigaction(sig, &act, nullptr);
|
||||
int ret = sigaction(sig, &act, &oact);
|
||||
if (ret == -1)
|
||||
return SIG_ERR;
|
||||
return func;
|
||||
return oact.sa_handler;
|
||||
}
|
||||
|
||||
int sigpending(sigset_t* set)
|
||||
|
|
|
@ -16,6 +16,7 @@ set(USERSPACE_PROGRAMS
|
|||
id
|
||||
image
|
||||
init
|
||||
less
|
||||
ln
|
||||
loadfont
|
||||
loadkeys
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(less ${SOURCES})
|
||||
banan_link_library(less ban)
|
||||
banan_link_library(less libc)
|
||||
|
||||
install(TARGETS less OPTIONAL)
|
|
@ -0,0 +1,367 @@
|
|||
#include <BAN/Vector.h>
|
||||
#include <BAN/String.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct config_t
|
||||
{
|
||||
bool raw { false };
|
||||
bool quit_if_one_screen { false };
|
||||
bool no_init { false };
|
||||
};
|
||||
static config_t config;
|
||||
|
||||
static void usage(const char* argv0, int ret)
|
||||
{
|
||||
FILE* fout = ret ? stderr : stdout;
|
||||
fprintf(fout, "usage: %s [OPTIONS]... [--] [FILE]...\n", argv0);
|
||||
fprintf(fout, " -r, -R, --raw print ANSI control sequences\n");
|
||||
fprintf(fout, " -F, -quit-if-one-screen exit immediately if output fits in one screen\n");
|
||||
fprintf(fout, " -X, -no-init don't clear screen at the startup\n");
|
||||
fprintf(fout, " -h, --help show this message and exit\n");
|
||||
exit(ret);
|
||||
}
|
||||
|
||||
static void handle_option_short(const char* argv0, char opt, bool exit_on_error)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 'r':
|
||||
case 'R':
|
||||
config.raw = true;
|
||||
break;
|
||||
case 'F':
|
||||
config.quit_if_one_screen = true;
|
||||
break;
|
||||
case 'X':
|
||||
config.no_init = true;
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv0, 0);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown option: %c\n", opt);
|
||||
fprintf(stderr, "see --help for usage\n");
|
||||
if (exit_on_error)
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_option_long(const char* argv0, const char* opt, bool exit_on_error)
|
||||
{
|
||||
if (!strcmp(opt, "--raw"))
|
||||
config.raw = true;
|
||||
else if (!strcmp(opt, "--quit-if-one-screen"))
|
||||
config.quit_if_one_screen = true;
|
||||
else if (!strcmp(opt, "--no-init"))
|
||||
config.no_init = true;
|
||||
else if (!strcmp(opt, "--help"))
|
||||
usage(argv0, 0);
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "unknown option: %s\n", opt);
|
||||
fprintf(stderr, "see --help for usage\n");
|
||||
if (exit_on_error)
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_config_or_exit(int argc, char** argv)
|
||||
{
|
||||
// FIXME: long environment options
|
||||
if (const char* env = getenv("LESS"))
|
||||
for (size_t i = 0; env[i]; i++)
|
||||
(void)handle_option_short(argv[0], env[i], false);
|
||||
|
||||
int i = 1;
|
||||
for (; i < argc; i++)
|
||||
{
|
||||
if (argv[i][0] != '-' || !strcmp(argv[i], "--"))
|
||||
break;
|
||||
if (argv[i][1] == '-')
|
||||
handle_option_long(argv[0], argv[i], true);
|
||||
else for (size_t j = 1; argv[i][j]; j++)
|
||||
handle_option_short(argv[0], argv[i][j], true);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int get_keyboard_fd()
|
||||
{
|
||||
// if stdin is a terminal, use it
|
||||
if (isatty(STDIN_FILENO))
|
||||
return STDIN_FILENO;
|
||||
|
||||
// otherwise try to open our controlling terminal
|
||||
int fd = open("/dev/tty", O_RDONLY);
|
||||
if (fd != -1)
|
||||
return fd;
|
||||
|
||||
// if that fails, try stderr as the last fallback
|
||||
if (isatty(STDERR_FILENO))
|
||||
return STDERR_FILENO;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int output_to_non_terminal(int fd)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
char buffer[128];
|
||||
const ssize_t nread = read(fd, buffer, sizeof(buffer));
|
||||
if (nread == -1)
|
||||
perror("read");
|
||||
if (nread <= 0)
|
||||
break;
|
||||
|
||||
ssize_t total = 0;
|
||||
while (total < nread)
|
||||
{
|
||||
const ssize_t nwrite = write(STDOUT_FILENO, buffer + total, nread - total);
|
||||
if (nwrite < 0)
|
||||
perror("write");
|
||||
if (nwrite <= 0)
|
||||
break;
|
||||
total += nwrite;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool read_lines(int fd, winsize ws, BAN::Vector<BAN::String>& out)
|
||||
{
|
||||
char buffer[128];
|
||||
const ssize_t nread = read(fd, buffer, sizeof(buffer));
|
||||
if (nread < 0)
|
||||
perror("read");
|
||||
if (nread <= 0)
|
||||
return false;
|
||||
|
||||
if (out.empty())
|
||||
MUST(out.emplace_back());
|
||||
|
||||
bool in_ansi = false;
|
||||
|
||||
size_t col = 0;
|
||||
for (size_t i = 0; i < out.back().size(); i++)
|
||||
{
|
||||
if (in_ansi)
|
||||
{
|
||||
if (isalpha(out.back()[i]))
|
||||
in_ansi = false;
|
||||
}
|
||||
else if (out.back()[i] == '\e')
|
||||
in_ansi = true;
|
||||
else
|
||||
col++;
|
||||
}
|
||||
|
||||
for (ssize_t i = 0; i < nread; i++)
|
||||
{
|
||||
if (in_ansi)
|
||||
{
|
||||
if (config.raw)
|
||||
MUST(out.back().push_back(buffer[i]));
|
||||
if (isalpha(buffer[i]))
|
||||
in_ansi = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto append_char =
|
||||
[&col, &out, ws](char ch)
|
||||
{
|
||||
if (col >= ws.ws_col)
|
||||
{
|
||||
MUST(out.emplace_back());
|
||||
col = 0;
|
||||
}
|
||||
MUST(out.back().push_back(ch));
|
||||
col++;
|
||||
};
|
||||
|
||||
switch (buffer[i])
|
||||
{
|
||||
case '\e':
|
||||
if (config.raw)
|
||||
MUST(out.back().push_back(buffer[i]));
|
||||
in_ansi = true;
|
||||
break;
|
||||
case '\n':
|
||||
MUST(out.emplace_back());
|
||||
col = 0;
|
||||
break;
|
||||
case '\t':
|
||||
append_char(' ');
|
||||
while (col % 8)
|
||||
append_char(' ');
|
||||
break;
|
||||
default:
|
||||
append_char(buffer[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool less_file(int fd, int kb_fd, winsize ws)
|
||||
{
|
||||
if (!isatty(STDOUT_FILENO) || kb_fd == -1 || ws.ws_col == 0 || ws.ws_row == 0)
|
||||
return output_to_non_terminal(fd);
|
||||
|
||||
BAN::Vector<BAN::String> lines;
|
||||
size_t lines_size = 0;
|
||||
const auto update_lines_size =
|
||||
[&lines, &lines_size]
|
||||
{
|
||||
lines_size = lines.size();
|
||||
if (!lines.empty() && lines.back().empty())
|
||||
lines_size--;
|
||||
};
|
||||
|
||||
while (lines_size < ws.ws_row)
|
||||
{
|
||||
if (!read_lines(fd, ws, lines))
|
||||
break;
|
||||
update_lines_size();
|
||||
}
|
||||
|
||||
if (!config.no_init)
|
||||
{
|
||||
int y = 1;
|
||||
if (lines_size < ws.ws_row)
|
||||
y = ws.ws_row - lines_size;
|
||||
printf("\e[2J\e[%dH", y);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < lines_size && i < static_cast<size_t>(ws.ws_row - 1); i++)
|
||||
printf("%s\n", lines[i].data());
|
||||
fflush(stdout);
|
||||
|
||||
if (lines_size < ws.ws_row && config.quit_if_one_screen)
|
||||
return true;
|
||||
|
||||
printf(":");
|
||||
fflush(stdout);
|
||||
|
||||
size_t line = 0;
|
||||
for (;;)
|
||||
{
|
||||
char ch;
|
||||
if (read(kb_fd, &ch, 1) != 1)
|
||||
break;
|
||||
switch (ch)
|
||||
{
|
||||
case 'q':
|
||||
printf("\e[G\e[K");
|
||||
fflush(stdout);
|
||||
return true;
|
||||
case '\e':
|
||||
{
|
||||
if (read(kb_fd, &ch, 1) != 1 || ch != '[')
|
||||
break;
|
||||
if (read(kb_fd, &ch, 1) != 1)
|
||||
break;
|
||||
switch (ch)
|
||||
{
|
||||
case 'A':
|
||||
if (line == 0)
|
||||
break;
|
||||
line--;
|
||||
|
||||
printf("\e[H");
|
||||
for (int i = 0; i < ws.ws_row - 1; i++)
|
||||
printf("%s\e[K\n", lines[line + i].data());
|
||||
printf(":\e[K");
|
||||
fflush(stdout);
|
||||
|
||||
break;
|
||||
case 'B':
|
||||
while (lines_size - (line + 1) < ws.ws_row)
|
||||
{
|
||||
if (!read_lines(fd, ws, lines))
|
||||
break;
|
||||
update_lines_size();
|
||||
}
|
||||
if (lines_size - (line + 1) < static_cast<size_t>(ws.ws_row - 1))
|
||||
break;
|
||||
line++;
|
||||
|
||||
printf("\e[H");
|
||||
for (int i = 0; i < ws.ws_row - 1; i++)
|
||||
printf("%s\e[K\n", lines[line + i].data());
|
||||
printf(":\e[K");
|
||||
fflush(stdout);
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int i = parse_config_or_exit(argc, argv);
|
||||
|
||||
int kb_fd = get_keyboard_fd();
|
||||
if (kb_fd != -1)
|
||||
{
|
||||
int flags = 0;
|
||||
fcntl(kb_fd, F_GETFL, &flags);
|
||||
if (flags & O_NONBLOCK)
|
||||
fcntl(kb_fd, F_SETFL, flags & ~O_NONBLOCK);
|
||||
|
||||
termios termios;
|
||||
tcgetattr(kb_fd, &termios);
|
||||
termios.c_lflag &= ~(ECHO | ICANON);
|
||||
tcsetattr(kb_fd, TCSANOW, &termios);
|
||||
}
|
||||
|
||||
winsize ws { .ws_row = 0, .ws_col = 0 };
|
||||
if (isatty(STDOUT_FILENO))
|
||||
{
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
|
||||
{
|
||||
// try to make stdout fully buffered to reduce flicker
|
||||
const size_t bufsize = 2 * ws.ws_row * ws.ws_col;
|
||||
if (char* buffer = static_cast<char*>(malloc(bufsize)))
|
||||
setvbuf(stdout, buffer, _IOFBF, bufsize);
|
||||
}
|
||||
}
|
||||
|
||||
if (i == argc)
|
||||
{
|
||||
if (!less_file(STDIN_FILENO, kb_fd, ws))
|
||||
return EXIT_FAILURE;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int ret = EXIT_SUCCESS;
|
||||
for (; i < argc; i++)
|
||||
{
|
||||
int fd = open(argv[i], O_RDONLY);
|
||||
if (fd == -1)
|
||||
{
|
||||
perror(argv[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!less_file(fd, kb_fd, ws))
|
||||
ret = EXIT_FAILURE;
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in New Issue