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 c = @cImport({ @cInclude("errno.h"); @cInclude("stdio.h"); if (@import("builtin").os.tag != .windows) { @cInclude("sys/statvfs.h"); } }); const cmd = @import("cmd.zig"); const Command = cmd.Command; const BuiltinCommand = cmd.BuiltinCommand; const syntax = @import("syntax.zig"); 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 cmdTypes = @import("cmd/lib/types.zig"); pub const CommandStatus = cmdTypes.CommandStatus; const OutputCapture = cmdTypes.OutputCapture; const InputSource = cmdTypes.InputSource; const STDOUT_BUFFER_SIZE: usize = 1024; const STDERR_BUFFER_SIZE: usize = 1024; 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_text| { return echo_text.eval(allocator, output_capture, input_source); }, .Cls => |cls| { return cls.eval(allocator, output_capture, input_source); }, .Exit => { return CommandStatus.ExitShell; }, .EchoPlain => |echo_plain| { return echo_plain.eval(allocator, output_capture, input_source); }, .EchoOn => |echo_on| { return echo_on.eval(allocator, output_capture, input_source); }, .EchoOff => |echo_off| { return echo_off.eval(allocator, output_capture, input_source); }, .Ver => |ver| { return ver.eval(allocator, output_capture, input_source); }, .Date => |date| { return date.eval(allocator, output_capture, input_source); }, .Time => |time| { return time.eval(allocator, output_capture, input_source); }, .Dir => |dir| { return dir.eval(allocator, output_capture, input_source); }, .Type => |type_cmd| { return type_cmd.eval(allocator, output_capture, input_source); }, .Sort => |sort| { return sort.eval(allocator, output_capture, input_source); }, .Chdir => |chdir| { return chdir.eval(allocator, output_capture, input_source); }, .Copy => |copy| { return copy.eval(allocator, output_capture, input_source); }, .Remove => |remove| { return remove.eval(allocator, output_capture, input_source); }, .Mkdir => |mkdir| { return mkdir.eval(allocator, output_capture, input_source); }, .Rmdir => |rmdir| { return rmdir.eval(allocator, output_capture, input_source); }, .Rename => |rename| { return rename.eval(allocator, output_capture, input_source); }, .Move => |move| { return move.eval(allocator, output_capture, input_source); }, .PathGet => |path_get| { return path_get.eval(allocator, output_capture, input_source); }, .PathSet => |path_set| { return path_set.eval(allocator, output_capture, input_source); }, 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| { return external.eval(allocator, output_capture, input_source); }, .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 }; }, } }