Browse Source

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".
Andreas Kling 6 years ago
parent
commit
ac7a60225e
11 changed files with 206 additions and 61 deletions
  1. 14 0
      Kernel/TTY.cpp
  2. 6 0
      Kernel/TTY.h
  3. 20 19
      Kernel/VirtualConsole.cpp
  4. 0 2
      Kernel/VirtualConsole.h
  5. 3 3
      Kernel/run
  6. 2 0
      Kernel/sync.sh
  7. 5 0
      LibC/sys/ioctl.h
  8. 1 0
      LibC/sys/ioctl_numbers.h
  9. 144 36
      Userland/ls.cpp
  10. 7 1
      Userland/tst.cpp
  11. 4 0
      VirtualFileSystem/UnixTypes.h

+ 14 - 0
Kernel/TTY.cpp

@@ -109,6 +109,7 @@ int TTY::ioctl(Process& process, unsigned request, unsigned arg)
 {
 {
     pid_t pgid;
     pid_t pgid;
     Unix::termios* tp;
     Unix::termios* tp;
+    Unix::winsize* ws;
 
 
     if (process.tty() != this)
     if (process.tty() != this)
         return -ENOTTY;
         return -ENOTTY;
@@ -136,7 +137,20 @@ int TTY::ioctl(Process& process, unsigned request, unsigned arg)
             return -EFAULT;
             return -EFAULT;
         set_termios(*tp);
         set_termios(*tp);
         return 0;
         return 0;
+    case TIOCGWINSZ:
+        ws = reinterpret_cast<Unix::winsize*>(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();
     ASSERT_NOT_REACHED();
     return -EINVAL;
     return -EINVAL;
 }
 }
+
+void TTY::set_size(unsigned short columns, unsigned short rows)
+{
+    m_rows = rows;
+    m_columns = columns;
+}

+ 6 - 0
Kernel/TTY.h

@@ -39,6 +39,9 @@ public:
 
 
     virtual String ttyName() const = 0;
     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; }
     void set_pgid(pid_t pgid) { m_pgid = pgid; }
     pid_t pgid() const { return m_pgid; }
     pid_t pgid() const { return m_pgid; }
 
 
@@ -50,6 +53,7 @@ public:
 
 
 protected:
 protected:
     virtual void onTTYWrite(const byte*, size_t) = 0;
     virtual void onTTYWrite(const byte*, size_t) = 0;
+    void set_size(unsigned short columns, unsigned short rows);
 
 
     TTY(unsigned major, unsigned minor);
     TTY(unsigned major, unsigned minor);
     void emit(byte);
     void emit(byte);
@@ -63,5 +67,7 @@ private:
     DoubleBuffer m_buffer;
     DoubleBuffer m_buffer;
     pid_t m_pgid { 0 };
     pid_t m_pgid { 0 };
     Unix::termios m_termios;
     Unix::termios m_termios;
+    unsigned short m_rows { 0 };
+    unsigned short m_columns { 0 };
 };
 };
 
 

+ 20 - 19
Kernel/VirtualConsole.cpp

@@ -17,13 +17,13 @@ void VirtualConsole::get_vga_cursor(byte& row, byte& column)
     value = IO::in8(0x3d5) << 8;
     value = IO::in8(0x3d5) << 8;
     IO::out8(0x3d4, 0x0f);
     IO::out8(0x3d4, 0x0f);
     value |= IO::in8(0x3d5);
     value |= IO::in8(0x3d5);
-    row = value / 80;
-    column = value % 80;
+    row = value / columns();
+    column = value % columns();
 }
 }
 
 
 void VirtualConsole::flush_vga_cursor()
 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(0x3d4, 0x0e);
     IO::out8(0x3d5, MSB(value));
     IO::out8(0x3d5, MSB(value));
     IO::out8(0x3d4, 0x0f);
     IO::out8(0x3d4, 0x0f);
@@ -41,14 +41,15 @@ VirtualConsole::VirtualConsole(unsigned index, InitialContents initial_contents)
     : TTY(4, index)
     : TTY(4, index)
     , m_index(index)
     , m_index(index)
 {
 {
+    set_size(80, 25);
     s_consoles[index] = this;
     s_consoles[index] = this;
-    m_buffer = (byte*)kmalloc_eternal(80 * 25 * 2);
+    m_buffer = (byte*)kmalloc_eternal(rows() * columns() * 2);
     if (initial_contents == AdoptCurrentVGABuffer) {
     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);
         get_vga_cursor(m_cursor_row, m_cursor_column);
     } else {
     } else {
         word* line_mem = reinterpret_cast<word*>(m_buffer);
         word* line_mem = reinterpret_cast<word*>(m_buffer);
-        for (word i = 0; i < 80 * 25; ++i)
+        for (word i = 0; i < rows() * columns(); ++i)
             line_mem[i] = 0x0720;
             line_mem[i] = 0x0720;
     }
     }
 }
 }
@@ -60,7 +61,7 @@ VirtualConsole::~VirtualConsole()
 void VirtualConsole::clear()
 void VirtualConsole::clear()
 {
 {
     word* linemem = m_active ? (word*)s_vga_buffer : (word*)m_buffer;
     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;
         linemem[i] = 0x0720;
     if (m_active)
     if (m_active)
         set_vga_start_row(0);
         set_vga_start_row(0);
@@ -91,11 +92,11 @@ void VirtualConsole::set_active(bool b)
 
 
     m_active = b;
     m_active = b;
     if (!m_active) {
     if (!m_active) {
-        memcpy(m_buffer, m_current_vga_window, 80 * 25 * 2);
+        memcpy(m_buffer, m_current_vga_window, rows() * columns() * 2);
         return;
         return;
     }
     }
 
 
-    memcpy(s_vga_buffer, m_buffer, 80 * 25 * 2);
+    memcpy(s_vga_buffer, m_buffer, rows() * columns() * 2);
     set_vga_start_row(0);
     set_vga_start_row(0);
     flush_vga_cursor();
     flush_vga_cursor();
 
 
@@ -325,16 +326,16 @@ void VirtualConsole::execute_escape_sequence(byte final)
 void VirtualConsole::clear_vga_row(word row)
 void VirtualConsole::clear_vga_row(word row)
 {
 {
     word* linemem = (word*)&m_current_vga_window[row * 160];
     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;
         linemem[i] = 0x0720;
 }
 }
 
 
 void VirtualConsole::scroll_up()
 void VirtualConsole::scroll_up()
 {
 {
-    if (m_cursor_row == (m_rows - 1)) {
+    if (m_cursor_row == (rows() - 1)) {
         if (m_active) {
         if (m_active) {
             if (m_vga_start_row >= 160) {
             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);
                 set_vga_start_row(0);
                 clear_vga_row(24);
                 clear_vga_row(24);
             } else {
             } else {
@@ -344,7 +345,7 @@ void VirtualConsole::scroll_up()
         } else {
         } else {
             memcpy(m_buffer, m_buffer + 160, 160 * 24);
             memcpy(m_buffer, m_buffer + 160, 160 * 24);
             word* linemem = (word*)&m_buffer[24 * 160];
             word* linemem = (word*)&m_buffer[24 * 160];
-            for (word i = 0; i < 80; ++i)
+            for (word i = 0; i < columns(); ++i)
                 linemem[i] = 0x0720;
                 linemem[i] = 0x0720;
         }
         }
     } else {
     } else {
@@ -355,8 +356,8 @@ void VirtualConsole::scroll_up()
 
 
 void VirtualConsole::set_cursor(unsigned row, unsigned column)
 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_row = row;
     m_cursor_column = column;
     m_cursor_column = column;
     if (m_active)
     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)
 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);
     word cur = (row * 160) + (column * 2);
     if (m_active) {
     if (m_active) {
         word cur = (row * 160) + (column * 2);
         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);
     put_character_at(m_cursor_row, m_cursor_column, ch);
 
 
     ++m_cursor_column;
     ++m_cursor_column;
-    if (m_cursor_column >= m_columns)
+    if (m_cursor_column >= columns())
         scroll_up();
         scroll_up();
     set_cursor(m_cursor_row, m_cursor_column);
     set_cursor(m_cursor_row, m_cursor_column);
 }
 }
@@ -480,7 +481,7 @@ String VirtualConsole::ttyName() const
 void VirtualConsole::set_vga_start_row(word row)
 void VirtualConsole::set_vga_start_row(word row)
 {
 {
     m_vga_start_row = 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;
     m_current_vga_window = s_vga_buffer + row * 160;
     IO::out8(0x3d4, 0x0c);
     IO::out8(0x3d4, 0x0c);
     IO::out8(0x3d5, MSB(m_current_vga_start_address));
     IO::out8(0x3d5, MSB(m_current_vga_start_address));

+ 0 - 2
Kernel/VirtualConsole.h

@@ -49,8 +49,6 @@ private:
 
 
     void clear();
     void clear();
 
 
-    const byte m_rows { 25 };
-    const byte m_columns { 80 };
     byte m_cursor_row { 0 };
     byte m_cursor_row { 0 };
     byte m_cursor_column { 0 };
     byte m_cursor_column { 0 };
     byte m_saved_cursor_row { 0 };
     byte m_saved_cursor_row { 0 };

+ 3 - 3
Kernel/run

@@ -1,8 +1,8 @@
 #!/bin/sh
 #!/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 #$@
     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
 fi
 
 

+ 2 - 0
Kernel/sync.sh

@@ -32,5 +32,7 @@ cp -v ../Userland/strsignal mnt/bin/strsignal
 cp -v ../Userland/mkdir mnt/bin/mkdir
 cp -v ../Userland/mkdir mnt/bin/mkdir
 sh sync-local.sh
 sh sync-local.sh
 cp -v kernel.map mnt/
 cp -v kernel.map mnt/
+ln -s dir_a mnt/dir_cur
+ln -s nowhere mnt/bad_link
 umount mnt
 umount mnt
 sync
 sync

+ 5 - 0
LibC/sys/ioctl.h

@@ -5,6 +5,11 @@
 
 
 __BEGIN_DECLS
 __BEGIN_DECLS
 
 
+struct winsize {
+    unsigned short ws_row;
+    unsigned short ws_col;
+};
+
 int ioctl(int fd, unsigned request, ...);
 int ioctl(int fd, unsigned request, ...);
 
 
 __END_DECLS
 __END_DECLS

+ 1 - 0
LibC/sys/ioctl_numbers.h

@@ -7,5 +7,6 @@ enum IOCtlNumber {
     TCSETS,
     TCSETS,
     TCSETSW,
     TCSETSW,
     TCSETSF,
     TCSETSF,
+    TIOCGWINSZ,
 };
 };
 
 

+ 144 - 36
Userland/ls.cpp

@@ -1,17 +1,97 @@
-#include <LibC/stdio.h>
-#include <LibC/unistd.h>
-#include <LibC/dirent.h>
-#include <LibC/errno.h>
-#include <LibC/string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
 
 
 static int do_dir(const char* path);
 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)
 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;
+        }
+    }
+
+    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);
     }
     }
-    return do_dir(".");
+    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)
 int do_dir(const char* path)
@@ -21,9 +101,11 @@ int do_dir(const char* path)
         perror("opendir");
         perror("opendir");
         return 1;
         return 1;
     }
     }
-    bool colorize = true;
     char pathbuf[256];
     char pathbuf[256];
+
     while (auto* de = readdir(dirp)) {
     while (auto* de = readdir(dirp)) {
+        if (de->d_name[0] == '.' && !flag_show_dotfiles)
+            continue;
         sprintf(pathbuf, "%s/%s", path, de->d_name);
         sprintf(pathbuf, "%s/%s", path, de->d_name);
 
 
         struct stat st;
         struct stat st;
@@ -70,37 +152,63 @@ int do_dir(const char* path)
 
 
         printf(" %10u  ", st.st_size);
         printf(" %10u  ", st.st_size);
 
 
-        const char* beginColor = "";
-        const char* endColor = "";
-
-        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";
+        print_name(st, de->d_name, pathbuf);
+
+        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<String> 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;
         }
         }
 
 
-        printf("%s%s%s", beginColor, de->d_name, endColor);
+        unsigned nprinted = print_name(st, name.characters());
+        unsigned column_width = 14;
+        printed_on_row += column_width;
 
 
-        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("*");
+        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");
     }
     }
+    printf("\n");
+
     return 0;
     return 0;
 }
 }

+ 7 - 1
Userland/tst.cpp

@@ -1,9 +1,15 @@
-#include <LibC/stdio.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
 
 
 int main(int argc, char** argv)
 int main(int argc, char** argv)
 {
 {
     (void) argc;
     (void) argc;
     (void) argv;
     (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");
     printf("Counting to 100000: \033[s");
     for (unsigned i = 0; i <= 100000; ++i) {
     for (unsigned i = 0; i <= 100000; ++i) {
         printf("\033[u\033[s%u", i);
         printf("\033[u\033[s%u", i);

+ 4 - 0
VirtualFileSystem/UnixTypes.h

@@ -208,6 +208,10 @@ namespace Unix {
 #define	TCSADRAIN	1
 #define	TCSADRAIN	1
 #define	TCSAFLUSH	2
 #define	TCSAFLUSH	2
 
 
+struct winsize {
+    unsigned short ws_row;
+    unsigned short ws_col;
+};
 
 
 typedef dword dev_t;
 typedef dword dev_t;
 typedef dword ino_t;
 typedef dword ino_t;