summaryrefslogtreecommitdiff
path: root/src/cmd/dir.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/dir.zig')
-rw-r--r--src/cmd/dir.zig157
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 };
+ }
+};