Skip to content

Instantly share code, notes, and snippets.

@rockorager
Created May 3, 2025 15:46
Show Gist options
  • Save rockorager/f0ab390590274e05c818fffc511f8a5a to your computer and use it in GitHub Desktop.
Save rockorager/f0ab390590274e05c818fffc511f8a5a to your computer and use it in GitHub Desktop.
const std = @import("std");
const builtin = @import("builtin");
const ourio = @import("ourio");
const zeit = @import("zeit");
const posix = std.posix;
const Options = struct {
all: bool = false,
long: bool = false,
};
pub fn main() !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
const gpa, const is_debug = gpa: {
break :gpa switch (builtin.mode) {
.Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true },
.ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false },
};
};
defer if (is_debug) {
_ = debug_allocator.deinit();
};
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
var cmd: Command = .{ .arena = arena.allocator() };
var args = std.process.args();
while (args.next()) |arg| {
switch (optKind(arg)) {
.short => {
const str = arg[1..];
for (str) |b| {
switch (b) {
'a' => cmd.opts.all = true,
'l' => cmd.opts.long = true,
else => {
const w = std.io.getStdErr().writer();
try w.print("Invalid opt: '{c}'", .{b});
std.process.exit(1);
},
}
}
},
.long => {
const opt = arg[2..];
if (eql(opt, "all"))
cmd.opts.all = true
else if (eql(opt, "long"))
cmd.opts.long = true
else {
const w = std.io.getStdErr().writer();
try w.print("Invalid opt: '{s}'", .{opt});
std.process.exit(1);
}
},
.positional => {},
}
}
var ring: ourio.Ring = try .init(arena.allocator(), 64);
defer ring.deinit();
_ = try ring.open(".", .{ .DIRECTORY = true, .CLOEXEC = true }, 0, .{
.ptr = &cmd,
.cb = onCompletion,
.msg = @intFromEnum(Msg.cwd),
});
if (cmd.opts.long) {
// We need to also open /etc/localtime and /etc/passwd
_ = try ring.open("/etc/localtime", .{ .CLOEXEC = true }, 0, .{
.ptr = &cmd,
.cb = onCompletion,
.msg = @intFromEnum(Msg.localtime),
});
_ = try ring.open("/etc/passwd", .{ .CLOEXEC = true }, 0, .{
.ptr = &cmd,
.cb = onCompletion,
.msg = @intFromEnum(Msg.passwd),
});
}
try ring.run(.until_done);
std.sort.insertion(Entry, cmd.entries, cmd.opts, Entry.lessThan);
const stdout = std.io.getStdOut();
var bw = std.io.bufferedWriter(stdout.writer());
if (cmd.opts.long) {
const tz = cmd.tz.?;
for (cmd.entries) |entry| {
const user = cmd.getUser(entry.statx.uid).?;
const ts = @as(i128, entry.statx.mtime.sec) * std.time.ns_per_s;
const inst: zeit.Instant = .{ .timestamp = ts, .timezone = &tz };
const time = inst.time();
try bw.writer().print("{s} {s} {d} {s} {s}\r\n", .{
&entry.modeStr(),
user.name,
time.day,
time.month.shortName(),
entry.name,
});
}
} else {
for (cmd.entries) |entry| {
try bw.writer().print("{s}\r\n", .{entry.name});
}
}
try bw.flush();
}
const Command = struct {
arena: std.mem.Allocator,
opts: Options = .{},
entries: []Entry = &.{},
tz: ?zeit.TimeZone = null,
users: std.ArrayListUnmanaged(User) = .empty,
fn getUser(self: Command, uid: posix.uid_t) ?User {
for (self.users.items) |user| {
if (user.uid == uid) return user;
}
return null;
}
};
const Msg = enum(u16) {
cwd,
localtime,
passwd,
stat,
read_localtime,
read_passwd,
};
const User = struct {
uid: posix.uid_t,
name: []const u8,
fn lessThan(_: void, lhs: User, rhs: User) bool {
return lhs.uid < rhs.uid;
}
};
const Entry = struct {
name: [:0]const u8,
kind: std.fs.File.Kind,
statx: ourio.Statx,
fn lessThan(_: Options, lhs: Entry, rhs: Entry) bool {
return std.ascii.orderIgnoreCase(lhs.name, rhs.name).compare(.lt);
}
fn modeStr(self: Entry) [10]u8 {
var mode = [_]u8{'-'} ** 10;
switch (self.kind) {
.directory => mode[0] = 'd',
else => {},
}
if (self.statx.mode & posix.S.IRUSR != 0) mode[1] = 'r';
if (self.statx.mode & posix.S.IWUSR != 0) mode[2] = 'w';
if (self.statx.mode & posix.S.IXUSR != 0) mode[3] = 'x';
if (self.statx.mode & posix.S.IRGRP != 0) mode[4] = 'r';
if (self.statx.mode & posix.S.IWGRP != 0) mode[5] = 'w';
if (self.statx.mode & posix.S.IXGRP != 0) mode[6] = 'x';
if (self.statx.mode & posix.S.IROTH != 0) mode[7] = 'r';
if (self.statx.mode & posix.S.IWOTH != 0) mode[8] = 'w';
if (self.statx.mode & posix.S.IXOTH != 0) mode[9] = 'x';
return mode;
}
};
fn onCompletion(io: *ourio.Ring, task: ourio.Task) anyerror!void {
const cmd = task.userdataCast(Command);
const msg = task.msgToEnum(Msg);
const result = task.result.?;
switch (msg) {
.cwd => {
const fd = try result.open;
// we are async, no need to defer!
_ = try io.close(fd, .{});
const dir: std.fs.Dir = .{ .fd = fd };
var results: std.ArrayListUnmanaged(Entry) = .empty;
var iter = dir.iterate();
while (try iter.next()) |dirent| {
const nameZ = try cmd.arena.dupeZ(u8, dirent.name);
try results.append(cmd.arena, .{
.name = nameZ,
.kind = dirent.kind,
.statx = undefined,
});
}
cmd.entries = results.items;
for (cmd.entries) |*entry| {
_ = try io.stat(entry.name, &entry.statx, .{
.cb = onCompletion,
.ptr = cmd,
.msg = @intFromEnum(Msg.stat),
});
}
},
.localtime => {
const fd = try result.open;
// Largest TZ file on my system is Asia/Hebron at 4791 bytes. We allocate an amount
// sufficiently more than that to make sure we do this in a single pass
const buffer = try cmd.arena.alloc(u8, 8192);
_ = try io.read(fd, buffer, .{
.cb = onCompletion,
.ptr = cmd,
.msg = @intFromEnum(Msg.read_localtime),
});
},
.read_localtime => {
const n = try result.read;
_ = try io.close(task.req.read.fd, .{});
const bytes = task.req.read.buffer[0..n];
var fbs = std.io.fixedBufferStream(bytes);
const tz = try zeit.timezone.TZInfo.parse(cmd.arena, fbs.reader());
cmd.tz = .{ .tzinfo = tz };
},
.passwd => {
const fd = try result.open;
// TODO: stat this or do multiple reads. We'll never know a good bound unless we go
// really big
const buffer = try cmd.arena.alloc(u8, 8192 * 2);
_ = try io.read(fd, buffer, .{
.cb = onCompletion,
.ptr = cmd,
.msg = @intFromEnum(Msg.read_passwd),
});
},
.read_passwd => {
const n = try result.read;
_ = try io.close(task.req.read.fd, .{});
const bytes = task.req.read.buffer[0..n];
var lines = std.mem.splitScalar(u8, bytes, '\n');
var line_count: usize = 0;
while (lines.next()) |_| {
line_count += 1;
}
try cmd.users.ensureUnusedCapacity(cmd.arena, line_count);
lines.reset();
// <name>:<throwaway>:<uid><...garbage>
while (lines.next()) |line| {
if (line.len == 0) continue;
var iter = std.mem.splitScalar(u8, line, ':');
const name = iter.first();
_ = iter.next();
const uid = iter.next().?;
const user: User = .{
.name = name,
.uid = try std.fmt.parseInt(u32, uid, 10),
};
cmd.users.appendAssumeCapacity(user);
}
std.mem.sort(User, cmd.users.items, {}, User.lessThan);
},
else => {},
}
}
fn eql(a: []const u8, b: []const u8) bool {
return std.mem.eql(u8, a, b);
}
fn optKind(a: []const u8) enum { short, long, positional } {
if (std.mem.startsWith(u8, a, "--")) return .long;
if (std.mem.startsWith(u8, a, "-")) return .short;
return .positional;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment