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/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| { // 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 }; }, } }