zig-toybox/src/ls.zig

248 lines
7.5 KiB
Zig
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

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();
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,
};
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| {
try print_file_in_dir (dir, entry.name);
}
}
pub fn ls() !void {
var cli = try lib.CLI.init();
defer cli.deinit();
// 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) {
const element = cli.nextArg(&arg_idx) orelse {
lib.warn("Null argument\n", .{});
return error.InvalidArgs;
};
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 (nb_parameters == 0) {
try print_element(".");
}
}
pub fn main() !void {
try ls();
}