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; fn isLeapYear(year: u32) bool { return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0); } 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| { return dir.eval(allocator, output_capture, input_source); }, .Type => |type_cmd| { return type_cmd.eval(allocator, output_capture, input_source); }, .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| { 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 => { const error_msg = "MOVE command not yet implemented\n"; if (output_capture) |capture| { try capture.write(error_msg); } else { print("{s}", .{error_msg}); } return CommandStatus{ .Code = 1 }; }, .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 }; }, } }