zig-toybox/src/ls.zig

340 lines
11 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 fmt = std.fmt;
const depth = 3;
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 {
// lib.print("FS STATS: {}\n", .{stats});
// 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});
}
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,
.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;
};
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,
.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_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});
}
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,
};
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 {
if (options.verbose == false) {
return lib.print("{s}\n", .{entryname});
}
const stats = statFile(dir, entryname);
// 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} ** std.fs.MAX_PATH_BYTES;
std.mem.copy (u8, t[0..], sub_path);
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_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: print info on each file
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_directory(element);
}
}
// case there were no parameter, print current directory
if (nb_parameters == 0) {
try print_directory(".");
}
}
pub fn main() !void {
try ls();
}