From ac7a60225e9c2024a167d6f3f440780cf7af304a Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 29 Nov 2018 03:45:23 +0100 Subject: [PATCH] Add TIOCGWINSZ ioctl so userland can determine terminal geometry. (Don't) use this to implement short-form output in ls. I'm too tired to make a nice column formatting algorithm. I just wanted something concise when I type "ls". --- Kernel/TTY.cpp | 14 +++ Kernel/TTY.h | 6 ++ Kernel/VirtualConsole.cpp | 39 +++---- Kernel/VirtualConsole.h | 2 - Kernel/run | 6 +- Kernel/sync.sh | 2 + LibC/sys/ioctl.h | 5 + LibC/sys/ioctl_numbers.h | 1 + Userland/ls.cpp | 184 +++++++++++++++++++++++++++------- Userland/tst.cpp | 8 +- VirtualFileSystem/UnixTypes.h | 4 + 11 files changed, 208 insertions(+), 63 deletions(-) diff --git a/Kernel/TTY.cpp b/Kernel/TTY.cpp index ebd5146d422..8325f56c4ff 100644 --- a/Kernel/TTY.cpp +++ b/Kernel/TTY.cpp @@ -109,6 +109,7 @@ int TTY::ioctl(Process& process, unsigned request, unsigned arg) { pid_t pgid; Unix::termios* tp; + Unix::winsize* ws; if (process.tty() != this) return -ENOTTY; @@ -136,7 +137,20 @@ int TTY::ioctl(Process& process, unsigned request, unsigned arg) return -EFAULT; set_termios(*tp); return 0; + case TIOCGWINSZ: + ws = reinterpret_cast(arg); + if (!process.validate_write(ws, sizeof(Unix::winsize))) + return -EFAULT; + ws->ws_row = m_rows; + ws->ws_col = m_columns; + return 0; } ASSERT_NOT_REACHED(); return -EINVAL; } + +void TTY::set_size(unsigned short columns, unsigned short rows) +{ + m_rows = rows; + m_columns = columns; +} diff --git a/Kernel/TTY.h b/Kernel/TTY.h index e1d5f36874b..5d58c44c3f1 100644 --- a/Kernel/TTY.h +++ b/Kernel/TTY.h @@ -39,6 +39,9 @@ public: virtual String ttyName() const = 0; + unsigned short rows() const { return m_rows; } + unsigned short columns() const { return m_columns; } + void set_pgid(pid_t pgid) { m_pgid = pgid; } pid_t pgid() const { return m_pgid; } @@ -50,6 +53,7 @@ public: protected: virtual void onTTYWrite(const byte*, size_t) = 0; + void set_size(unsigned short columns, unsigned short rows); TTY(unsigned major, unsigned minor); void emit(byte); @@ -63,5 +67,7 @@ private: DoubleBuffer m_buffer; pid_t m_pgid { 0 }; Unix::termios m_termios; + unsigned short m_rows { 0 }; + unsigned short m_columns { 0 }; }; diff --git a/Kernel/VirtualConsole.cpp b/Kernel/VirtualConsole.cpp index f9f3df94356..f0708f74cd6 100644 --- a/Kernel/VirtualConsole.cpp +++ b/Kernel/VirtualConsole.cpp @@ -17,13 +17,13 @@ void VirtualConsole::get_vga_cursor(byte& row, byte& column) value = IO::in8(0x3d5) << 8; IO::out8(0x3d4, 0x0f); value |= IO::in8(0x3d5); - row = value / 80; - column = value % 80; + row = value / columns(); + column = value % columns(); } void VirtualConsole::flush_vga_cursor() { - word value = m_current_vga_start_address + (m_cursor_row * 80 + m_cursor_column); + word value = m_current_vga_start_address + (m_cursor_row * columns() + m_cursor_column); IO::out8(0x3d4, 0x0e); IO::out8(0x3d5, MSB(value)); IO::out8(0x3d4, 0x0f); @@ -41,14 +41,15 @@ VirtualConsole::VirtualConsole(unsigned index, InitialContents initial_contents) : TTY(4, index) , m_index(index) { + set_size(80, 25); s_consoles[index] = this; - m_buffer = (byte*)kmalloc_eternal(80 * 25 * 2); + m_buffer = (byte*)kmalloc_eternal(rows() * columns() * 2); if (initial_contents == AdoptCurrentVGABuffer) { - memcpy(m_buffer, s_vga_buffer, 80 * 25 * 2); + memcpy(m_buffer, s_vga_buffer, rows() * columns() * 2); get_vga_cursor(m_cursor_row, m_cursor_column); } else { word* line_mem = reinterpret_cast(m_buffer); - for (word i = 0; i < 80 * 25; ++i) + for (word i = 0; i < rows() * columns(); ++i) line_mem[i] = 0x0720; } } @@ -60,7 +61,7 @@ VirtualConsole::~VirtualConsole() void VirtualConsole::clear() { word* linemem = m_active ? (word*)s_vga_buffer : (word*)m_buffer; - for (word i = 0; i < 80 * 25; ++i) + for (word i = 0; i < rows() * columns(); ++i) linemem[i] = 0x0720; if (m_active) set_vga_start_row(0); @@ -91,11 +92,11 @@ void VirtualConsole::set_active(bool b) m_active = b; if (!m_active) { - memcpy(m_buffer, m_current_vga_window, 80 * 25 * 2); + memcpy(m_buffer, m_current_vga_window, rows() * columns() * 2); return; } - memcpy(s_vga_buffer, m_buffer, 80 * 25 * 2); + memcpy(s_vga_buffer, m_buffer, rows() * columns() * 2); set_vga_start_row(0); flush_vga_cursor(); @@ -325,16 +326,16 @@ void VirtualConsole::execute_escape_sequence(byte final) void VirtualConsole::clear_vga_row(word row) { word* linemem = (word*)&m_current_vga_window[row * 160]; - for (word i = 0; i < 80; ++i) + for (word i = 0; i < columns(); ++i) linemem[i] = 0x0720; } void VirtualConsole::scroll_up() { - if (m_cursor_row == (m_rows - 1)) { + if (m_cursor_row == (rows() - 1)) { if (m_active) { if (m_vga_start_row >= 160) { - memcpy(s_vga_buffer, m_current_vga_window + 160, 80 * 24 * 2); + memcpy(s_vga_buffer, m_current_vga_window + 160, (rows() - 1) * columns() * 2); set_vga_start_row(0); clear_vga_row(24); } else { @@ -344,7 +345,7 @@ void VirtualConsole::scroll_up() } else { memcpy(m_buffer, m_buffer + 160, 160 * 24); word* linemem = (word*)&m_buffer[24 * 160]; - for (word i = 0; i < 80; ++i) + for (word i = 0; i < columns(); ++i) linemem[i] = 0x0720; } } else { @@ -355,8 +356,8 @@ void VirtualConsole::scroll_up() void VirtualConsole::set_cursor(unsigned row, unsigned column) { - ASSERT(row < m_rows); - ASSERT(column < m_columns); + ASSERT(row < rows()); + ASSERT(column < columns()); m_cursor_row = row; m_cursor_column = column; if (m_active) @@ -365,8 +366,8 @@ void VirtualConsole::set_cursor(unsigned row, unsigned column) void VirtualConsole::put_character_at(unsigned row, unsigned column, byte ch) { - ASSERT(row < m_rows); - ASSERT(column < m_columns); + ASSERT(row < rows()); + ASSERT(column < columns()); word cur = (row * 160) + (column * 2); if (m_active) { word cur = (row * 160) + (column * 2); @@ -435,7 +436,7 @@ void VirtualConsole::on_char(byte ch) put_character_at(m_cursor_row, m_cursor_column, ch); ++m_cursor_column; - if (m_cursor_column >= m_columns) + if (m_cursor_column >= columns()) scroll_up(); set_cursor(m_cursor_row, m_cursor_column); } @@ -480,7 +481,7 @@ String VirtualConsole::ttyName() const void VirtualConsole::set_vga_start_row(word row) { m_vga_start_row = row; - m_current_vga_start_address = row * 80; + m_current_vga_start_address = row * columns(); m_current_vga_window = s_vga_buffer + row * 160; IO::out8(0x3d4, 0x0c); IO::out8(0x3d5, MSB(m_current_vga_start_address)); diff --git a/Kernel/VirtualConsole.h b/Kernel/VirtualConsole.h index 4aae3bad0ec..39f1d7dfcef 100644 --- a/Kernel/VirtualConsole.h +++ b/Kernel/VirtualConsole.h @@ -49,8 +49,6 @@ private: void clear(); - const byte m_rows { 25 }; - const byte m_columns { 80 }; byte m_cursor_row { 0 }; byte m_cursor_column { 0 }; byte m_saved_cursor_row { 0 }; diff --git a/Kernel/run b/Kernel/run index 5fe20b9986c..d6790461487 100755 --- a/Kernel/run +++ b/Kernel/run @@ -1,8 +1,8 @@ #!/bin/sh -if [ "$1" = "b" ]; then - bochs -q -f .bochsrc -else +if [ "$1" = "q" ]; then qemu-system-i386 -s -m 32 -drive format=raw,file=.floppy-image,if=floppy -drive format=raw,file=_fs_contents #$@ +else + bochs -q -f .bochsrc fi diff --git a/Kernel/sync.sh b/Kernel/sync.sh index de4586d43e2..130ceba83cd 100755 --- a/Kernel/sync.sh +++ b/Kernel/sync.sh @@ -32,5 +32,7 @@ cp -v ../Userland/strsignal mnt/bin/strsignal cp -v ../Userland/mkdir mnt/bin/mkdir sh sync-local.sh cp -v kernel.map mnt/ +ln -s dir_a mnt/dir_cur +ln -s nowhere mnt/bad_link umount mnt sync diff --git a/LibC/sys/ioctl.h b/LibC/sys/ioctl.h index 3a2dcd0b5f2..95d69f0504b 100644 --- a/LibC/sys/ioctl.h +++ b/LibC/sys/ioctl.h @@ -5,6 +5,11 @@ __BEGIN_DECLS +struct winsize { + unsigned short ws_row; + unsigned short ws_col; +}; + int ioctl(int fd, unsigned request, ...); __END_DECLS diff --git a/LibC/sys/ioctl_numbers.h b/LibC/sys/ioctl_numbers.h index ad4fdc0e9bf..bc4a6dae23d 100644 --- a/LibC/sys/ioctl_numbers.h +++ b/LibC/sys/ioctl_numbers.h @@ -7,5 +7,6 @@ enum IOCtlNumber { TCSETS, TCSETSW, TCSETSF, + TIOCGWINSZ, }; diff --git a/Userland/ls.cpp b/Userland/ls.cpp index fe0f33f4eb5..fa15d8a23b8 100644 --- a/Userland/ls.cpp +++ b/Userland/ls.cpp @@ -1,17 +1,97 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include static int do_dir(const char* path); +static int do_dir_short(const char* path); + +static bool flag_colorize = true; +static bool flag_long = false; +static bool flag_show_dotfiles = false; int main(int argc, char** argv) { - if (argc == 2) { - return do_dir(argv[1]); + int opt; + while ((opt = getopt(argc, argv, "laG")) != -1) { + switch (opt) { + case 'a': + flag_show_dotfiles = true; + break; + case 'l': + flag_long = true; + break; + case 'G': + flag_colorize = false; + break; + default: + fprintf(stderr, "usage: ls [-lvG] [path]\n"); + return 1; + } } - return do_dir("."); + + const char* path; + + if (optind >= argc) + path = "."; + else + path = argv[optind]; + + if (flag_long) + return do_dir(path); + return do_dir_short(path); +} + +void get_geometry(unsigned& rows, unsigned& columns) +{ + struct winsize ws; + ioctl(0, TIOCGWINSZ, &ws); + rows = ws.ws_row; + columns = ws.ws_col; +} + +int print_name(struct stat& st, const char* name, const char* path_for_link_resolution = nullptr) +{ + int nprinted = strlen(name); + if (!flag_colorize) { + printf("%s", name); + } else { + const char* begin_color = ""; + const char* end_color = "\033[0m"; + + if (S_ISLNK(st.st_mode)) + begin_color = "\033[36;1m"; + else if (S_ISDIR(st.st_mode)) + begin_color = "\033[34;1m"; + else if (st.st_mode & 0111) + begin_color = "\033[32;1m"; + else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) + begin_color = "\033[33;1m"; + printf("%s%s%s", begin_color, name, end_color); + } + if (S_ISLNK(st.st_mode)) { + if (path_for_link_resolution) { + char linkbuf[256]; + ssize_t nread = readlink(path_for_link_resolution, linkbuf, sizeof(linkbuf)); + if (nread < 0) { + perror("readlink failed"); + } else { + nprinted += printf(" -> %s", linkbuf); + } + } else { + nprinted += printf("@"); + } + } else if (S_ISDIR(st.st_mode)) { + nprinted += printf("/"); + } else if (st.st_mode & 0111) { + nprinted += printf("*"); + } + return nprinted; } int do_dir(const char* path) @@ -21,9 +101,11 @@ int do_dir(const char* path) perror("opendir"); return 1; } - bool colorize = true; char pathbuf[256]; + while (auto* de = readdir(dirp)) { + if (de->d_name[0] == '.' && !flag_show_dotfiles) + continue; sprintf(pathbuf, "%s/%s", path, de->d_name); struct stat st; @@ -70,37 +152,63 @@ int do_dir(const char* path) printf(" %10u ", st.st_size); - const char* beginColor = ""; - const char* endColor = ""; + print_name(st, de->d_name, pathbuf); - if (colorize) { - if (S_ISLNK(st.st_mode)) - beginColor = "\033[36;1m"; - else if (S_ISDIR(st.st_mode)) - beginColor = "\033[34;1m"; - else if (st.st_mode & 0111) - beginColor = "\033[32;1m"; - else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) - beginColor = "\033[33;1m"; - endColor = "\033[0m"; - } - - printf("%s%s%s", beginColor, de->d_name, endColor); - - if (S_ISLNK(st.st_mode)) { - char linkbuf[256]; - ssize_t nread = readlink(pathbuf, linkbuf, sizeof(linkbuf)); - if (nread < 0) { - perror("readlink failed"); - } else { - printf(" -> %s", linkbuf); - } - } else if (S_ISDIR(st.st_mode)) { - printf("/"); - } else if (st.st_mode & 0111) { - printf("*"); - } printf("\n"); } + closedir(dirp); + return 0; +} + +int do_dir_short(const char* path) +{ + unsigned rows; + unsigned columns; + get_geometry(rows, columns); + + DIR* dirp = opendir(path); + if (!dirp) { + perror("opendir"); + return 1; + } + + Vector names; + size_t longest_name = 0; + while (auto* de = readdir(dirp)) { + if (de->d_name[0] == '.' && !flag_show_dotfiles) + continue; + names.append(de->d_name); + if (names.last().length() > longest_name) + longest_name = names.last().length(); + } + closedir(dirp); + + int printed_on_row = 0; + + for (size_t i = 0; i < names.size(); ++i) { + auto& name = names[i]; + struct stat st; + char pathbuf[256]; + sprintf(pathbuf, "%s/%s", path, name.characters()); + int rc = lstat(pathbuf, &st); + if (rc == -1) { + printf("lstat(%s) failed: %s\n", pathbuf, strerror(errno)); + return 2; + } + + unsigned nprinted = print_name(st, name.characters()); + unsigned column_width = 14; + printed_on_row += column_width; + + for (unsigned i = nprinted; i < column_width; ++i) + printf(" "); + if ((printed_on_row + column_width) >= columns) { + if (i != names.size() - 1) + printf("\n"); + printed_on_row = 0; + } + } + printf("\n"); + return 0; } diff --git a/Userland/tst.cpp b/Userland/tst.cpp index 9659a20aabf..14463f691ff 100644 --- a/Userland/tst.cpp +++ b/Userland/tst.cpp @@ -1,9 +1,15 @@ -#include +#include +#include int main(int argc, char** argv) { (void) argc; (void) argv; + + struct winsize ws; + ioctl(0, TIOCGWINSZ, &ws); + printf("Terminal is %ux%u\n", ws.ws_col, ws.ws_row); + printf("Counting to 100000: \033[s"); for (unsigned i = 0; i <= 100000; ++i) { printf("\033[u\033[s%u", i); diff --git a/VirtualFileSystem/UnixTypes.h b/VirtualFileSystem/UnixTypes.h index b594cc7a911..f834038f337 100644 --- a/VirtualFileSystem/UnixTypes.h +++ b/VirtualFileSystem/UnixTypes.h @@ -208,6 +208,10 @@ namespace Unix { #define TCSADRAIN 1 #define TCSAFLUSH 2 +struct winsize { + unsigned short ws_row; + unsigned short ws_col; +}; typedef dword dev_t; typedef dword ino_t;