From f25404f2f2ebf75faef2523a7be28ecf437b7f5c Mon Sep 17 00:00:00 2001
From: Philippe Pittoli <karchnu@karchnu.fr>
Date: Sun, 1 May 2022 15:31:23 +0200
Subject: [PATCH] ls: fewer syscalls

---
 src/ls.zig | 158 ++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 125 insertions(+), 33 deletions(-)

diff --git a/src/ls.zig b/src/ls.zig
index fd1aaa2..8a66424 100644
--- a/src/ls.zig
+++ b/src/ls.zig
@@ -5,6 +5,9 @@ const path = std.fs.path;
 const fs = std.fs;
 const lib = @import("./lib.zig");
 
+const fmt = std.fmt;
+const depth = 3;
+
 const Kind = std.fs.File.Kind;
 const PermissionsUnix = std.fs.File.PermissionsUnix;
 
@@ -44,6 +47,7 @@ const Options = struct {
 var options: Options = Options {};
 
 fn print_stats(fpath: []const u8, stats: std.fs.File.Stat) void {
+    // lib.print("FS STATS: {}\n", .{stats});
     // print kind (directory, file, pipe, etc.)
     switch (stats.kind) {
         .File             => lib.print("-", .{}),
@@ -98,12 +102,7 @@ fn print_stats(fpath: []const u8, stats: std.fs.File.Stat) void {
     return lib.print(" {s}\n", .{fpath});
 }
 
-// mode = type (directory, file, symlink, etc.) + rights
-pub fn linuxstat_to_fsstat(st: std.os.linux.Stat) std.fs.File.StatError!std.fs.File.Stat {
-    const atime = st.atime();
-    const mtime = st.mtime();
-    const ctime = st.ctime();
-
+fn fs_kind_from_linux_stat (st: std.os.linux.Stat) Kind {
     const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) {
         .BLOCK_DEVICE => Kind.BlockDevice,
         .CHARACTER_DEVICE => Kind.CharacterDevice,
@@ -131,6 +130,18 @@ pub fn linuxstat_to_fsstat(st: std.os.linux.Stat) std.fs.File.StatError!std.fs.F
         };
         break :blk .Unknown;
     };
+    return kind;
+}
+
+
+// mode = type (directory, file, symlink, etc.) + rights
+pub fn linuxstat_to_fsstat(st: std.os.linux.Stat) std.fs.File.StatError!std.fs.File.Stat {
+    // lib.print("LINUX STATS: {}\n", .{st});
+    const atime = st.atime();
+    const mtime = st.mtime();
+    const ctime = st.ctime();
+
+    const kind = fs_kind_from_linux_stat (st);
 
     const stats = std.fs.File.Stat{
         .inode = st.ino,
@@ -146,28 +157,113 @@ pub fn linuxstat_to_fsstat(st: std.os.linux.Stat) std.fs.File.StatError!std.fs.F
     return stats;
 }
 
+fn print_linux_stats (fpath: []const u8, stats: std.os.linux.Stat) !void {
+    var buffer = [_]u8 {0} ** (std.fs.MAX_PATH_BYTES + 100);
+    // var it: u16 = 0;
+    var fbs = std.io.fixedBufferStream(&buffer);
+    var writer = fbs.writer();
+
+    const fmtopts = fmt.FormatOptions{};
+    // try std.testing.expect(mem.eql(u8, fbs.getWritten(), "1234"));
+    // fbs.reset();
+
+    // print kind (directory, file, pipe, etc.)
+    const kind = fs_kind_from_linux_stat(stats);
+    switch (kind) {
+        .File             => try fmt.formatType('-', "c", fmtopts, writer, depth),
+        .Directory        => try fmt.formatType('d', "c", fmtopts, writer, depth),
+        .CharacterDevice  => try fmt.formatType('c', "c", fmtopts, writer, depth),
+        .BlockDevice      => try fmt.formatType('b', "c", fmtopts, writer, depth),
+        .NamedPipe        => try fmt.formatType('p', "c", fmtopts, writer, depth),
+        .SymLink          => try fmt.formatType('s', "c", fmtopts, writer, depth),
+        .UnixDomainSocket => try fmt.formatType('s', "c", fmtopts, writer, depth),
+        else              => {
+            if (builtin.os.tag == .solaris) switch (kind) {
+                .Door      => try fmt.formatType('D', "c", fmtopts, writer, depth), // TODO: what to print here?
+                .EventPort => try fmt.formatType('e', "c", fmtopts, writer, depth), // TODO: what to print here?
+                else       => try fmt.formatType('?', "c", fmtopts, writer, depth),
+            };
+        },
+    }
+
+    // print rights
+    const perms = PermissionsUnix.unixNew(stats.mode);
+    const classes = [_]PermissionsUnix.Class {
+        PermissionsUnix.Class.user,
+        PermissionsUnix.Class.group,
+        PermissionsUnix.Class.other
+    };
+    const rights = [_]PermissionsUnix.Permission {
+        PermissionsUnix.Permission.read,
+        PermissionsUnix.Permission.write,
+        PermissionsUnix.Permission.execute
+    };
+    for (classes) |c| {
+        for (rights) |r| {
+             if (perms.unixHas(c,r)) {
+                 switch(r) {
+                     .read    => try fmt.formatType('r', "c", fmtopts, writer, depth),
+                     .write   => try fmt.formatType('w', "c", fmtopts, writer, depth),
+                     .execute => try fmt.formatType('x', "c", fmtopts, writer, depth),
+                 }
+             }
+             else {
+                 try fmt.formatType('-', "c", fmtopts, writer, depth);
+             }
+        }
+    }
+
+    // TODO: print user & group
+    try fmt.formatType('\t',       "c", fmtopts, writer, depth);
+    try fmt.formatType(stats.uid,  "",  fmtopts, writer, depth);
+    try fmt.formatType('\t',       "c", fmtopts, writer, depth);
+    try fmt.formatType(stats.gid,  "",  fmtopts, writer, depth);
+
+    // TODO: print size
+    // TODO: print date last write
+
+    // lib.print("{s}: {}\n", .{fpath, stats});
+
+    // std.mem.copy (u8, buffer[it..], fpath);
+    try fmt.formatType('\t',  "c", fmtopts, writer, depth);
+    try fmt.formatType(fpath, "s", fmtopts, writer, depth);
+    try fmt.formatType('\n',  "c", fmtopts, writer, depth);
+
+    return lib.print("{s}", .{fbs.getWritten()});
+}
+
+
 fn print_file (fpath: []const u8) !void {
     if (options.verbose == false) {
         return lib.print("{s}\n", .{fpath});
     }
 
-    // First, try to open the file to stat it.
-    var f: std.fs.File = undefined;
-    f = switch (path.isAbsolute(fpath)) {
-        true => std.fs.openFileAbsolute(fpath, .{.mode = .read_only}),
-        else => cwd.openFile(fpath, .{.mode = .read_only}),
-    } catch |err| switch(err) {
-        error.AccessDenied => {
-            lib.print("access denied\n", .{});
-            // try to read the content of the parent directory
-            return err;
-        },
+    const dname = path.dirname(fpath);
+    var dir: fs.Dir = cwd.openDir(dname.?, .{}) catch |err| switch (err) {
+        error.AccessDenied => return lib.print("{s}: access denied\n", .{dname}),
         else => return err,
     };
-    defer f.close();
 
-    const stats = try f.stat();
-    return print_stats (fpath, stats);
+    const bname = path.basename(fpath);
+    try print_file_in_dir (dir, bname);
+
+//    // First, try to open the file to stat it.
+//    var f: std.fs.File = undefined;
+//    f = switch (path.isAbsolute(fpath)) {
+//        true => std.fs.openFileAbsolute(fpath, .{.mode = .read_only}),
+//        else => cwd.openFile(fpath, .{.mode = .read_only}),
+//    } catch |err| switch(err) {
+//        error.AccessDenied => {
+//            lib.print("access denied\n", .{});
+//            // try to read the content of the parent directory
+//            return err;
+//        },
+//        else => return err,
+//    };
+//    defer f.close();
+//
+//    const stats = try f.stat();
+//    return print_stats (fpath, stats);
 }
 
 fn print_file_in_dir (dir: fs.Dir, entryname: []const u8) !void {
@@ -176,37 +272,33 @@ fn print_file_in_dir (dir: fs.Dir, entryname: []const u8) !void {
     }
 
     const stats = statFile(dir, entryname);
-    return print_stats (entryname, try linuxstat_to_fsstat(stats));
+    // return print_stats (entryname, try linuxstat_to_fsstat(stats));
+    return try print_linux_stats (entryname, stats);
 }
 
 
 // TODO: improve this to use the fstatat syscall instead of making 2 syscalls here.
 pub fn statFile(self: fs.Dir, sub_path: []const u8) std.os.linux.Stat {
     var stat: std.os.linux.Stat = undefined;
-    var t = [_:0]u8{0} ** 1000;
+    var t = [_:0]u8{0} ** std.fs.MAX_PATH_BYTES;
     std.mem.copy (u8, t[0..], sub_path);
-    var p = t[0..sub_path.len+1:0];
-    // var p = sub_path[0..sub_path.len :0];
+    var p = t[0..sub_path.len+1:0]; // add a final \0 at the end of the string, and make the type checker happy
     _ = std.os.linux.fstatat(self.fd, p, &stat, 0); // returns usize
     return stat;
 }
 
 // Either print directory's content or file.
-fn print_element(fpath: []const u8) !void {
-
-    // First:
-    //   if directory:  try to open the directory.
-    //   else:          try to open the parent directory.
-
+fn print_directory(fpath: []const u8) !void {
     var dir: fs.Dir = cwd.openDir(fpath, .{.iterate = true}) catch |err| switch (err) {
         error.AccessDenied => return lib.print("{s}: access denied\n", .{fpath}),
         error.NotDir => {
+            // fpath is a simple file, just print the file infos
             return try print_file(fpath);
         },
         else => return err,
     };
 
-    // fpath is a directory.
+    // fpath is a directory: print info on each file
     var dir_it = dir.iterate();
     while (try dir_it.next()) |entry| {
         try print_file_in_dir (dir, entry.name);
@@ -232,13 +324,13 @@ pub fn ls() !void {
         }
         else {
             nb_parameters += 1;
-            try print_element(element);
+            try print_directory(element);
         }
     }
 
     // case there were no parameter, print current directory
     if (nb_parameters == 0) {
-        try print_element(".");
+        try print_directory(".");
     }
 }