const std = @import("std"); const print = std.debug.print; const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; const Thread = std.Thread; const Mutex = std.Thread.Mutex; const syntax = @import("syntax.zig"); const Command = syntax.Command; const BuiltinCommand = syntax.BuiltinCommand; const FileSpec = syntax.FileSpec; const RedirectType = syntax.RedirectType; const Redirect = syntax.Redirect; const paths = @import("paths.zig"); const formatDosPath = paths.formatDosPath; const convertTo83 = paths.convertTo83; const STDOUT_BUFFER_SIZE: usize = 1024; const STDERR_BUFFER_SIZE: usize = 1024; pub const CommandStatus = union(enum) { Code: u16, ExitShell, }; const OutputCapture = struct { buffer: ArrayList(u8), pub fn init(allocator: Allocator) OutputCapture { return OutputCapture{ .buffer = ArrayList(u8).init(allocator), }; } pub fn deinit(self: *OutputCapture) void { self.buffer.deinit(); } pub fn write(self: *OutputCapture, data: []const u8) !void { try self.buffer.appendSlice(data); } pub fn getContents(self: *const OutputCapture) []const u8 { return self.buffer.items; } }; const InputSource = struct { data: []const u8, position: usize, pub fn init(data: []const u8) InputSource { return InputSource{ .data = data, .position = 0, }; } pub fn readLine(self: *InputSource, allocator: Allocator) !?[]const u8 { if (self.position >= self.data.len) { return null; // EOF } var line_end = self.position; while (line_end < self.data.len and self.data[line_end] != '\n') { line_end += 1; } const line = self.data[self.position..line_end]; self.position = if (line_end < self.data.len) line_end + 1 else self.data.len; // Remove trailing \r if present (DOS line endings) if (line.len > 0 and line[line.len - 1] == '\r') { return try allocator.dupe(u8, line[0 .. line.len - 1]); } else { return try allocator.dupe(u8, line); } } }; 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 }); } pub fn executeCommand(command: Command, allocator: Allocator) !CommandStatus { return executeCommandWithOutput(command, allocator, null, null); } fn executeCommandWithOutput(command: Command, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus { switch (command) { .Empty => return CommandStatus{ .Code = 0 }, .Builtin => |builtin_cmd| { switch (builtin_cmd) { .EchoText => |echo| { const output = try std.fmt.allocPrint(allocator, "{s}\n", .{echo.message}); defer allocator.free(output); if (output_capture) |capture| { try capture.write(output); } else { print("{s}", .{output}); } return CommandStatus{ .Code = 0 }; }, .Cls => { if (output_capture == null) { // Clear screen - only works when not redirected print("\x1B[2J\x1B[H", .{}); } return CommandStatus{ .Code = 0 }; }, .Exit => { return CommandStatus.ExitShell; }, .EchoPlain => { const output = "ECHO is on\n"; if (output_capture) |capture| { try capture.write(output); } else { print("{s}", .{output}); } return CommandStatus{ .Code = 0 }; }, .EchoOn => { const output = "ECHO is on\n"; if (output_capture) |capture| { try capture.write(output); } else { print("{s}", .{output}); } return CommandStatus{ .Code = 0 }; }, .EchoOff => { return CommandStatus{ .Code = 0 }; }, .Ver => { const output = "MB-DOSE Version 6.22\n"; if (output_capture) |capture| { try capture.write(output); } else { print("{s}", .{output}); } return CommandStatus{ .Code = 0 }; }, .Date => { const timestamp = std.time.timestamp(); const epoch_seconds = @as(u64, @intCast(timestamp)); const epoch_day = @divFloor(epoch_seconds, std.time.s_per_day); // Calculate days since Unix epoch (1970-01-01) // Unix epoch is 719163 days since year 1 AD const days_since_year_1 = epoch_day + 719163; // Simple algorithm to convert days to year/month/day var year: u32 = 1; var remaining_days = days_since_year_1; // Find the year while (true) { 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; } // Days in each month (non-leap year) const days_in_month = [_]u32{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; var month: u32 = 1; for (days_in_month, 1..) |days, m| { var month_days = days; // Adjust February for leap years if (m == 2 and isLeapYear(year)) { month_days = 29; } if (remaining_days < month_days) { month = @intCast(m); break; } remaining_days -= month_days; } const day = remaining_days + 1; // Days are 1-indexed const output = try std.fmt.allocPrint(allocator, "Current date is {d:0>2}/{d:0>2}/{d}\n", .{ month, day, year }); defer allocator.free(output); if (output_capture) |capture| { try capture.write(output); } else { print("{s}", .{output}); } return CommandStatus{ .Code = 0 }; }, .Time => { const timestamp = std.time.timestamp(); const epoch_seconds = @as(u64, @intCast(timestamp)); const day_seconds = epoch_seconds % std.time.s_per_day; const hours = day_seconds / std.time.s_per_hour; const minutes = (day_seconds % std.time.s_per_hour) / std.time.s_per_min; const seconds = day_seconds % std.time.s_per_min; const output = try std.fmt.allocPrint(allocator, "Current time is {d:0>2}:{d:0>2}:{d:0>2}\n", .{ hours, minutes, seconds }); defer allocator.free(output); if (output_capture) |capture| { try capture.write(output); } else { print("{s}", .{output}); } return CommandStatus{ .Code = 0 }; }, .Dir => |dir| { 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} {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 (simplified - just use a placeholder) const bytes_free: u64 = 1024 * 1024 * 1024; // 1GB placeholder 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 }; }, .Type => |type_cmd| { const file_path = switch (type_cmd.file) { .Con => { const error_msg = "Cannot TYPE from CON\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { const error_msg = "Cannot TYPE from device\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, .Path => |path| path, }; const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { const error_msg = switch (err) { error.FileNotFound => "The system cannot find the file specified.\n", error.IsDir => "Access is denied.\n", error.AccessDenied => "Access is denied.\n", else => "Cannot access file.\n", }; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; defer file.close(); // Read and display file contents var buffer: [4096]u8 = undefined; while (true) { const bytes_read = file.readAll(&buffer) catch |err| { const error_msg = switch (err) { error.AccessDenied => "Access is denied.\n", else => "Error reading file.\n", }; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; if (bytes_read == 0) break; // Process buffer contents for output var processed_output = ArrayList(u8).init(allocator); defer processed_output.deinit(); for (buffer[0..bytes_read]) |byte| { // Convert to printable characters, similar to DOS TYPE behavior if (byte >= 32 and byte <= 126) { try processed_output.append(byte); } else if (byte == '\n') { try processed_output.append('\n'); } else if (byte == '\r') { // Skip carriage return in DOS-style line endings continue; } else if (byte == '\t') { try processed_output.append('\t'); } else { // Replace non-printable characters with '?' try processed_output.append('?'); } } if (output_capture) |capture| { try capture.write(processed_output.items); } else { print("{s}", .{processed_output.items}); } // If we read less than the buffer size, we're done if (bytes_read < buffer.len) break; } return CommandStatus{ .Code = 0 }; }, .Sort => { var lines = ArrayList([]const u8).init(allocator); defer { for (lines.items) |line| { allocator.free(line); } lines.deinit(); } // Read input lines if (input_source) |source| { // Read from input redirection while (try source.readLine(allocator)) |line| { try lines.append(line); } } else { // Read from stdin (simplified - just show message) const msg = "SORT: Use input redirection (< file.txt) to sort file contents\n"; if (output_capture) |capture| { try capture.write(msg); } else { print("{s}", .{msg}); } return CommandStatus{ .Code = 0 }; } // Sort the lines std.mem.sort([]const u8, lines.items, {}, struct { fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { return std.mem.order(u8, lhs, rhs) == .lt; } }.lessThan); // Output sorted lines var output_buffer = ArrayList(u8).init(allocator); defer output_buffer.deinit(); for (lines.items) |line| { try output_buffer.writer().print("{s}\n", .{line}); } if (output_capture) |capture| { try capture.write(output_buffer.items); } else { print("{s}", .{output_buffer.items}); } return CommandStatus{ .Code = 0 }; }, .Chdir => |chdir| { if (chdir.path.len == 0) { // No arguments - display current directory const cwd = std.fs.cwd().realpathAlloc(allocator, ".") catch { const error_msg = "Unable to determine current directory\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; defer allocator.free(cwd); const formatted_path = try formatDosPath(allocator, cwd); defer allocator.free(formatted_path); const output = try std.fmt.allocPrint(allocator, "{s}\n", .{formatted_path}); defer allocator.free(output); if (output_capture) |capture| { try capture.write(output); } else { print("{s}", .{output}); } return CommandStatus{ .Code = 0 }; } else { // Change directory const target_path = chdir.path; // Handle special cases if (std.mem.eql(u8, target_path, "..")) { // Go to parent directory std.process.changeCurDir("..") catch { const error_msg = "The system cannot find the path specified.\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; } else if (std.mem.eql(u8, target_path, "\\") or std.mem.eql(u8, target_path, "/")) { // Go to root directory - simplified to just go to "/" std.process.changeCurDir("/") catch { const error_msg = "The system cannot find the path specified.\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; } else { // Regular directory change // Make sure the path doesn't contain null bytes for (target_path) |ch| { if (ch == 0) { const error_msg = "Invalid path: contains null character\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; } } std.process.changeCurDir(target_path) catch { const error_msg = "The system cannot find the path specified.\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; } return CommandStatus{ .Code = 0 }; } }, .Copy => |copy| { // Handle source file const source_path = switch (copy.from) { .Con => { // COPY CON - copy from console to file const dest_path = switch (copy.to) { .Con => { const error_msg = "Cannot copy from CON to CON\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { const error_msg = "Cannot copy to device\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, .Path => |path| path, }; // Create the destination file const dest_file = std.fs.cwd().createFile(dest_path, .{}) catch |err| { const error_msg = switch (err) { error.AccessDenied => "Access denied\n", error.PathAlreadyExists => "File already exists - use different name\n", else => "Cannot create file\n", }; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; defer dest_file.close(); // Read from stdin and write to file until EOF (Ctrl+Z on DOS) const stdin = std.io.getStdIn().reader(); var line_count: u32 = 0; // Skip output redirection since we're doing interactive input if (output_capture == null) { // In interactive mode, show no prompt (DOS behavior) } while (true) { if (stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 4096)) |maybe_line| { if (maybe_line) |line| { defer allocator.free(line); // Check for Ctrl+Z (EOF marker) if (line.len == 1 and line[0] == 26) { // ASCII 26 = Ctrl+Z break; } // Remove trailing \r if present (Windows line endings) const clean_line = if (line.len > 0 and line[line.len - 1] == '\r') line[0 .. line.len - 1] else line; // Write line to file with DOS line ending try dest_file.writeAll(clean_line); try dest_file.writeAll("\r\n"); line_count += 1; } else { // EOF reached break; } } else |_| { // Error reading input break; } } const msg = try std.fmt.allocPrint(allocator, " 1 File(s) copied\n", .{}); defer allocator.free(msg); if (output_capture) |capture| { try capture.write(msg); } else { print("{s}", .{msg}); } return CommandStatus{ .Code = 0 }; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { const error_msg = "Cannot copy from device\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, .Path => |path| path, }; // Handle destination const dest_path = switch (copy.to) { .Con => { // Copy to console (display file contents) const source_file = std.fs.cwd().openFile(source_path, .{}) catch |err| { const error_msg = switch (err) { error.FileNotFound => "File not found\n", error.AccessDenied => "Access denied\n", else => "Cannot access source file\n", }; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; defer source_file.close(); // Read and display file contents var buffer: [4096]u8 = undefined; while (true) { const bytes_read = source_file.readAll(&buffer) catch { const error_msg = "Error reading file\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; if (bytes_read == 0) break; if (output_capture) |capture| { try capture.write(buffer[0..bytes_read]); } else { print("{s}", .{buffer[0..bytes_read]}); } if (bytes_read < buffer.len) break; } const msg = " 1 File(s) copied\n"; if (output_capture) |capture| { try capture.write(msg); } else { print("{s}", .{msg}); } return CommandStatus{ .Code = 0 }; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { const error_msg = "Cannot copy to device\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, .Path => |path| path, }; // Regular file-to-file copy std.fs.cwd().copyFile(source_path, std.fs.cwd(), dest_path, .{}) catch |err| { const error_msg = switch (err) { error.FileNotFound => "File not found\n", error.AccessDenied => "Access denied\n", error.PathAlreadyExists => "File already exists\n", else => "Cannot copy file\n", }; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; const msg = " 1 File(s) copied\n"; if (output_capture) |capture| { try capture.write(msg); } else { print("{s}", .{msg}); } return CommandStatus{ .Code = 0 }; }, .Remove => |remove| { const file_path = remove.path; // Check for wildcards (basic support) if (std.mem.indexOf(u8, file_path, "*") != null or std.mem.indexOf(u8, file_path, "?") != null) { // Simple wildcard deletion - just show error for now const error_msg = "Wildcard deletion not yet implemented\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; } // Delete single file std.fs.cwd().deleteFile(file_path) catch |err| { const error_msg = switch (err) { error.FileNotFound => "File not found\n", error.AccessDenied => "Access denied\n", error.IsDir => "Access denied - cannot delete directory\n", else => "Cannot delete file\n", }; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; // No output for successful deletion (DOS style) return CommandStatus{ .Code = 0 }; }, .Mkdir => |mkdir| { const dir_path = mkdir.path; std.fs.cwd().makeDir(dir_path) catch |err| { const error_msg = switch (err) { error.PathAlreadyExists => "A subdirectory or file already exists\n", error.AccessDenied => "Access denied\n", error.FileNotFound => "The system cannot find the path specified\n", error.NotDir => "The system cannot find the path specified\n", else => "Unable to create directory\n", }; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; // No output for successful creation (DOS style) return CommandStatus{ .Code = 0 }; }, else => { const error_msg = try std.fmt.allocPrint(allocator, "Command not implemented: {any}\n", .{builtin_cmd}); defer allocator.free(error_msg); if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, } }, .External => |external| { // Try to execute external command var child_args = ArrayList([]const u8).init(allocator); defer child_args.deinit(); try child_args.append(external.program); for (external.args.items) |arg| { try child_args.append(arg); } var child = std.process.Child.init(child_args.items, allocator); // Set up pipes for capturing output child.stdin_behavior = if (input_source != null) .Pipe else .Inherit; child.stdout_behavior = if (output_capture != null) .Pipe else .Inherit; child.stderr_behavior = if (output_capture != null) .Pipe else .Inherit; const spawn_result = child.spawn(); if (spawn_result) |_| { // Spawn succeeded, continue with execution } else |err| switch (err) { error.FileNotFound => { const error_msg = try std.fmt.allocPrint(allocator, "'{s}' is not recognized as an internal or external command,\noperable program or batch file.\n", .{external.program}); defer allocator.free(error_msg); if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, error.AccessDenied => { const error_msg = "Access is denied.\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, else => { const error_msg = try std.fmt.allocPrint(allocator, "Cannot execute '{s}': {}\n", .{ external.program, err }); defer allocator.free(error_msg); if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, } // Handle input redirection if (input_source) |source| { if (child.stdin) |stdin| { const writer = stdin.writer(); // Reset source position for reading var temp_source = source.*; temp_source.position = 0; while (try temp_source.readLine(allocator)) |line| { defer allocator.free(line); try writer.print("{s}\n", .{line}); } child.stdin.?.close(); child.stdin = null; } } // Handle output capture if (output_capture) |capture| { // Read stdout if (child.stdout) |stdout| { var buffer: [4096]u8 = undefined; while (true) { const bytes_read = stdout.read(&buffer) catch break; if (bytes_read == 0) break; try capture.write(buffer[0..bytes_read]); } } // Read stderr if (child.stderr) |stderr| { var buffer: [4096]u8 = undefined; while (true) { const bytes_read = stderr.read(&buffer) catch break; if (bytes_read == 0) break; try capture.write(buffer[0..bytes_read]); } } } // Wait for process to complete const term = child.wait() catch |err| { const error_msg = switch (err) { error.FileNotFound => try std.fmt.allocPrint(allocator, "'{s}' is not recognized as an internal or external command,\noperable program or batch file.\n", .{external.program}), else => try std.fmt.allocPrint(allocator, "Error waiting for command: {}\n", .{err}), }; defer allocator.free(error_msg); if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }; // Return exit code switch (term) { .Exited => |code| return CommandStatus{ .Code = @intCast(code) }, .Signal => |_| return CommandStatus{ .Code = 1 }, .Stopped => |_| return CommandStatus{ .Code = 1 }, .Unknown => |_| return CommandStatus{ .Code = 1 }, } }, .Redirect => |redirect| { // Check if we have any output redirections var has_output_redirect = false; for (redirect.redirects.items) |redir| { if (redir.redirect_type == .OutputOverwrite or redir.redirect_type == .OutputAppend) { has_output_redirect = true; break; } } var captured_output = OutputCapture.init(allocator); defer captured_output.deinit(); // Prepare input redirection if needed var input_data: ?[]const u8 = null; var redirect_input_source: ?InputSource = null; defer if (input_data) |data| allocator.free(data); // Process input redirections first for (redirect.redirects.items) |redir| { if (redir.redirect_type == .InputFrom) { const file_path = switch (redir.target) { .Con => { print("Input redirection from CON not supported\n", .{}); return CommandStatus{ .Code = 1 }; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { print("Cannot redirect input from device\n", .{}); return CommandStatus{ .Code = 1 }; }, .Path => |path| path, }; // Read input file const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { switch (err) { error.FileNotFound => print("The system cannot find the file specified.\n", .{}), error.AccessDenied => print("Access is denied.\n", .{}), else => print("Cannot open input file.\n", .{}), } return CommandStatus{ .Code = 1 }; }; defer file.close(); input_data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { switch (err) { error.AccessDenied => print("Access is denied.\n", .{}), else => print("Cannot read input file.\n", .{}), } return CommandStatus{ .Code = 1 }; }; redirect_input_source = InputSource.init(input_data.?); break; // Only handle first input redirection } } // Execute the command with input and output capture (only capture output if needed) const status = try executeCommandWithOutput(redirect.command.*, allocator, if (has_output_redirect) &captured_output else null, if (redirect_input_source) |*source| source else null); // Handle output redirections for (redirect.redirects.items) |redir| { if (redir.redirect_type == .InputFrom) continue; // Already handled const file_path = switch (redir.target) { .Con => { // Redirect to console - just print normally print("{s}", .{captured_output.getContents()}); continue; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { print("Cannot redirect to device\n", .{}); return CommandStatus{ .Code = 1 }; }, .Path => |path| path, }; // Handle different redirect types switch (redir.redirect_type) { .OutputOverwrite => { // Write to file, overwriting existing content const file = std.fs.cwd().createFile(file_path, .{}) catch |err| { switch (err) { error.AccessDenied => print("Access is denied.\n", .{}), else => print("Cannot create file.\n", .{}), } return CommandStatus{ .Code = 1 }; }; defer file.close(); file.writeAll(captured_output.getContents()) catch |err| { switch (err) { error.AccessDenied => print("Access is denied.\n", .{}), else => print("Cannot write to file.\n", .{}), } return CommandStatus{ .Code = 1 }; }; }, .OutputAppend => { // Append to file const file = std.fs.cwd().openFile(file_path, .{ .mode = .write_only }) catch |err| { switch (err) { error.FileNotFound => { // Create new file if it doesn't exist const new_file = std.fs.cwd().createFile(file_path, .{}) catch |create_err| { switch (create_err) { error.AccessDenied => print("Access is denied.\n", .{}), else => print("Cannot create file.\n", .{}), } return CommandStatus{ .Code = 1 }; }; defer new_file.close(); new_file.writeAll(captured_output.getContents()) catch { print("Cannot write to file.\n", .{}); return CommandStatus{ .Code = 1 }; }; continue; }, error.AccessDenied => { print("Access is denied.\n", .{}); return CommandStatus{ .Code = 1 }; }, else => { print("Cannot open file.\n", .{}); return CommandStatus{ .Code = 1 }; }, } }; defer file.close(); // Seek to end for append file.seekFromEnd(0) catch { print("Cannot seek to end of file.\n", .{}); return CommandStatus{ .Code = 1 }; }; file.writeAll(captured_output.getContents()) catch |err| { switch (err) { error.AccessDenied => print("Access is denied.\n", .{}), else => print("Cannot write to file.\n", .{}), } return CommandStatus{ .Code = 1 }; }; }, .InputFrom => { // Input redirection already handled above continue; }, } } return status; }, else => { const error_msg = "Command type not implemented\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, } } fn isLeapYear(year: u32) bool { return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0); }