diff options
Diffstat (limited to 'src/cmd/dir.zig')
-rw-r--r-- | src/cmd/dir.zig | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/src/cmd/dir.zig b/src/cmd/dir.zig new file mode 100644 index 0000000..0c57e6f --- /dev/null +++ b/src/cmd/dir.zig @@ -0,0 +1,157 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const print = std.debug.print; + +const c = @cImport({ + @cInclude("errno.h"); + @cInclude("stdio.h"); + if (@import("builtin").os.tag != .windows) { + @cInclude("sys/statvfs.h"); + } +}); + +const paths = @import("../paths.zig"); +const formatDosPath = paths.formatDosPath; +const convertTo83 = paths.convertTo83; + +const types = @import("./types.zig"); +const CommandStatus = types.CommandStatus; +const OutputCapture = types.OutputCapture; +const InputSource = types.InputSource; + +fn formatDosDateTime(allocator: Allocator, timestamp_secs: i64) ![]const u8 { + const epoch_seconds = @as(u64, @intCast(@max(timestamp_secs, 0))); + const epoch_day = @divFloor(epoch_seconds, std.time.s_per_day); + const day_seconds = epoch_seconds % std.time.s_per_day; + + // Calculate date (simplified) + var year: u32 = 1970; // Start from Unix epoch year + var remaining_days = epoch_day; + + // Simple year calculation + while (remaining_days >= 365) { + const days_in_year: u64 = if (isLeapYear(year)) 366 else 365; + if (remaining_days < days_in_year) break; + remaining_days -= days_in_year; + year += 1; + } + + // Simple month/day calculation (approximate) + const month = @min(@divFloor(remaining_days, 30) + 1, 12); + const day = @min(remaining_days % 30 + 1, 31); + + // Calculate time + const hours = day_seconds / std.time.s_per_hour; + const minutes = (day_seconds % std.time.s_per_hour) / std.time.s_per_min; + + // Format as MM-DD-YY HH:MMa (DOS style) + const am_pm = if (hours < 12) "a" else "p"; + const display_hour = if (hours == 0) 12 else if (hours > 12) hours - 12 else hours; + + return try std.fmt.allocPrint(allocator, "{d:0>2}-{d:0>2}-{d:0>2} {d:>2}:{d:0>2}{s}", .{ @as(u32, @intCast(month)), @as(u32, @intCast(day)), @as(u32, @intCast(year % 100)), @as(u32, @intCast(display_hour)), @as(u32, @intCast(minutes)), am_pm }); +} + +fn isLeapYear(year: u32) bool { + return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0); +} + +const GetFreeDiskSpaceError = error{ NotImplemented, AccessDenied }; + +fn getFreeDiskSpace(path: []const u8) GetFreeDiskSpaceError!u64 { + if (@import("builtin").os.tag == .windows) { + return error.NotImplemented; + } + + var stat: c.struct_statvfs = undefined; + if (c.statvfs(path.ptr, &stat) != 0) { + _ = c.perror("statvfs"); + return error.AccessDenied; + } + + return stat.f_bsize * stat.f_bfree; +} + +pub const Dir = struct { + path: []const u8, + + pub fn eval(dir: Dir, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus { + _ = input_source; + + var output_buffer = ArrayList(u8).init(allocator); + defer output_buffer.deinit(); + + // Format path in DOS style with backslashes and uppercase drive letter + const formatted_path = try formatDosPath(allocator, dir.path); + defer allocator.free(formatted_path); + + // Get volume label (simplified - just show drive) + const drive_letter = if (formatted_path.len >= 2 and formatted_path[1] == ':') + formatted_path[0] + else + 'C'; + try output_buffer.writer().print(" Volume in drive {c} has no label\n", .{drive_letter}); + try output_buffer.writer().print(" Volume Serial Number is 1234-5678\n", .{}); + try output_buffer.writer().print("\n Directory of {s}\n\n", .{formatted_path}); + + var dir_iterator = std.fs.cwd().openDir(dir.path, .{ .iterate = true }) catch { + const error_msg = "File not found\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } + return CommandStatus{ .Code = 1 }; + }; + defer dir_iterator.close(); + + var iterator = dir_iterator.iterate(); + var file_count: u32 = 0; + var dir_count: u32 = 0; + var total_file_bytes: u64 = 0; + + while (try iterator.next()) |entry| { + const stat = dir_iterator.statFile(entry.name) catch continue; + + // Convert timestamp to DOS date/time format + const mtime_secs = @divFloor(stat.mtime, std.time.ns_per_s); + const date_time = try formatDosDateTime(allocator, @intCast(mtime_secs)); + defer allocator.free(date_time); + + // Convert filename to 8.3 format + const short_name = try convertTo83(allocator, entry.name); + defer allocator.free(short_name); + + switch (entry.kind) { + .directory => { + try output_buffer.writer().print("{s} <DIR> {s}\n", .{ date_time, short_name }); + dir_count += 1; + }, + .file => { + try output_buffer.writer().print("{s} {d:>14} {s}\n", .{ date_time, stat.size, short_name }); + file_count += 1; + total_file_bytes += stat.size; + }, + else => {}, + } + } + + // Get free disk space using statvfs + const path = try std.fs.cwd().realpathAlloc(allocator, dir.path); + defer allocator.free(path); + const bytes_free = getFreeDiskSpace(path) catch |err| switch (err) { + error.AccessDenied => 0, + error.NotImplemented => 0, + }; + + try output_buffer.writer().print(" {d} File(s) {d:>14} bytes\n", .{ file_count, total_file_bytes }); + try output_buffer.writer().print(" {d} Dir(s) {d:>14} bytes free\n", .{ dir_count, bytes_free }); + + if (output_capture) |capture| { + try capture.write(output_buffer.items); + } else { + print("{s}", .{output_buffer.items}); + } + return CommandStatus{ .Code = 0 }; + } +}; |