From 7ce6b0651c90479534ee9c0d6d802784b326adbc Mon Sep 17 00:00:00 2001 From: Philippe Pittoli Date: Sun, 1 May 2022 00:37:18 +0200 Subject: [PATCH] ls: print rights. --- src/ls.zig | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 8 deletions(-) diff --git a/src/ls.zig b/src/ls.zig index 57e3d6a..fd1aaa2 100644 --- a/src/ls.zig +++ b/src/ls.zig @@ -1,23 +1,215 @@ const std = @import("std"); +const os = std.os; +const builtin = @import("builtin"); +const path = std.fs.path; const fs = std.fs; const lib = @import("./lib.zig"); +const Kind = std.fs.File.Kind; +const PermissionsUnix = std.fs.File.PermissionsUnix; + +// // /absolute/path/file => fpath + sep + entry.name +// std.mem.copy (u8, fullname[0..], fpath); +// fullname[fpath.len] = path.sep; // '/' on Unix-like systems, '\' on Windows. +// std.mem.copy (u8, fullname[fpath.len+1..], entry.name); +// try print_file(fullname[0..fpath.len+1+entry.name.len]); + +// Stats +// .size = @bitCast(u64, info.StandardInformation.EndOfFile), +// .mode = 0, +// .atime = LastAccessTime +// .mtime = LastWriteTime +// .ctime = CreationTime + + // WON'T: human output => use human command instead // TODO: error management. // TODO: verbose output (-l). pub const cwd = fs.cwd(); -// Either print directory's content or file. -fn print_element(path: []const u8) !void { - var dir: fs.Dir = cwd.openDir(path, .{.iterate = true}) catch |err| switch (err) { - error.NotDir => return lib.print("{s}\n", .{path}), +const Sorting = enum { + None, + DateCreation, // TODO + DateLastAccess, // TODO + DateLastWrite, // TODO + Size, // TODO +}; + +const Options = struct { + sorting: Sorting = Sorting.None, + verbose: bool = false, +}; + +var options: Options = Options {}; + +fn print_stats(fpath: []const u8, stats: std.fs.File.Stat) void { + // print kind (directory, file, pipe, etc.) + switch (stats.kind) { + .File => lib.print("-", .{}), + .Directory => lib.print("d", .{}), + .CharacterDevice => lib.print("c", .{}), + .BlockDevice => lib.print("b", .{}), + .NamedPipe => lib.print("p", .{}), + .SymLink => lib.print("s", .{}), + .UnixDomainSocket => lib.print("s", .{}), + else => { + if (builtin.os.tag == .solaris) switch (stats.kind) { + .Door => lib.print("D", .{}), // TODO: what to print here? + .EventPort => lib.print("e", .{}), // TODO: what to print here? + else => lib.print("?", .{}), + }; + }, + } + + // 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 => lib.print("r", .{}), + .write => lib.print("w", .{}), + .execute => lib.print("x", .{}), + } + } + else { + lib.print("-", .{}); + } + } + } + + // TODO: print user & group + // TODO: print size + // TODO: print date last write + + // lib.print("{s}: {}\n", .{fpath, stats}); + + 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(); + + const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) { + .BLOCK_DEVICE => Kind.BlockDevice, + .CHARACTER_DEVICE => Kind.CharacterDevice, + .DIRECTORY => Kind.Directory, + .SYMBOLIC_LINK => Kind.SymLink, + .REGULAR_FILE => Kind.File, + .SOCKET_STREAM, .SOCKET_DGRAM => Kind.UnixDomainSocket, + else => Kind.Unknown, + } else blk: { + const m = st.mode & os.S.IFMT; + switch (m) { + os.S.IFBLK => break :blk Kind.BlockDevice, + os.S.IFCHR => break :blk Kind.CharacterDevice, + os.S.IFDIR => break :blk Kind.Directory, + os.S.IFIFO => break :blk Kind.NamedPipe, + os.S.IFLNK => break :blk Kind.SymLink, + os.S.IFREG => break :blk Kind.File, + os.S.IFSOCK => break :blk Kind.UnixDomainSocket, + else => {}, + } + if (builtin.os.tag == .solaris) switch (m) { + os.S.IFDOOR => break :blk Kind.Door, + os.S.IFPORT => break :blk Kind.EventPort, + else => {}, + }; + break :blk .Unknown; + }; + + const stats = std.fs.File.Stat{ + .inode = st.ino, + .size = @bitCast(u64, st.size), + .mode = st.mode, + .kind = kind, + .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, + .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, + .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, + }; + + // lib.print("{}\n", .{stats}); + return stats; +} + +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; + }, else => return err, }; - var dir_it = dir.iterate(); + 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 { + if (options.verbose == false) { + return lib.print("{s}\n", .{entryname}); + } + + const stats = statFile(dir, entryname); + return print_stats (entryname, try linuxstat_to_fsstat(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; + 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]; + _ = 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. + + 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 => { + return try print_file(fpath); + }, + else => return err, + }; + + // fpath is a directory. + var dir_it = dir.iterate(); while (try dir_it.next()) |entry| { - lib.print("{s}\n", .{entry.name}); + try print_file_in_dir (dir, entry.name); } } @@ -27,6 +219,7 @@ pub fn ls() !void { // Skipping the executable binary name. var arg_idx: usize = 1; + var nb_parameters: i32 = 0; // case there are parameters while(arg_idx < cli.args.len) { @@ -34,11 +227,17 @@ pub fn ls() !void { lib.warn("Null argument\n", .{}); return error.InvalidArgs; }; - try print_element(element); + if (std.mem.eql(u8, element, "-l")) { + options.verbose = true; + } + else { + nb_parameters += 1; + try print_element(element); + } } // case there were no parameter, print current directory - if (cli.args.len == 1) { + if (nb_parameters == 0) { try print_element("."); } }