summaryrefslogtreecommitdiff
path: root/src/cmd/redirect.zig
blob: 3921ef0d451167cbc62fc50b1a7d466387712f40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;

const types = @import("./lib/types.zig");
const CommandStatus = types.CommandStatus;
const CommandContext = types.CommandContext;
const OutputCapture = types.OutputCapture;
const InputSource = types.InputSource;

const syntax = @import("../syntax.zig");
const RedirectType = syntax.RedirectType;
const Redirect = syntax.Redirect;

const cmd = @import("../cmd.zig");
const Command = cmd.Command;

pub const RedirectCommand = struct {
    command: *Command,
    redirects: ArrayList(Redirect),

    pub fn eval(redirect: RedirectCommand, ctx: CommandContext) !CommandStatus {
        const allocator = ctx.allocator;
        var stdout_buffer: [1024]u8 = undefined;
        var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
        const stdout = &stdout_writer.interface;
        var stderr_buffer: [1024]u8 = undefined;
        var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
        const stderr = &stderr_writer.interface;
        // Extract output_capture from the output_writer for redirection logic
        const output_capture = switch (ctx.output_writer) {
            .capture => |capture| capture,
            .stdout => null,
        };
        const execute_command = ctx.execute_command;

        // 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 => {
                        try stderr.writeAll("Input redirection from CON not supported\n");
                        return CommandStatus{ .Code = 1 };
                    },
                    .Lpt1, .Lpt2, .Lpt3, .Prn => {
                        try stderr.writeAll("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 => stderr.writeAll("The system cannot find the file specified.\n") catch {},
                        error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {},
                        else => stderr.writeAll("Cannot open input file.\n") catch {},
                    }
                    return CommandStatus{ .Code = 1 };
                };
                defer file.close();

                input_data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
                    switch (err) {
                        error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {},
                        else => stderr.writeAll("Cannot read input file.\n") catch {},
                    }
                    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 execute_command(redirect.command.*, allocator, if (has_output_redirect) &captured_output else output_capture, 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
                    try stdout.writeAll(captured_output.getContents());
                    continue;
                },
                .Lpt1, .Lpt2, .Lpt3, .Prn => {
                    try stderr.writeAll("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 => stderr.writeAll("Access is denied.\n") catch {},
                            else => stderr.writeAll("Cannot create file.\n") catch {},
                        }
                        return CommandStatus{ .Code = 1 };
                    };
                    defer file.close();

                    file.writeAll(captured_output.getContents()) catch |err| {
                        switch (err) {
                            error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {},
                            else => stderr.writeAll("Cannot write to file.\n") catch {},
                        }
                        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 => stderr.writeAll("Access is denied.\n") catch {},
                                        else => stderr.writeAll("Cannot create file.\n") catch {},
                                    }
                                    return CommandStatus{ .Code = 1 };
                                };
                                defer new_file.close();

                                new_file.writeAll(captured_output.getContents()) catch {
                                    stderr.writeAll("Cannot write to file.\n") catch {};
                                    return CommandStatus{ .Code = 1 };
                                };
                                continue;
                            },
                            error.AccessDenied => {
                                stderr.writeAll("Access is denied.\n") catch {};
                                return CommandStatus{ .Code = 1 };
                            },
                            else => {
                                stderr.writeAll("Cannot open file.\n") catch {};
                                return CommandStatus{ .Code = 1 };
                            },
                        }
                    };
                    defer file.close();

                    // Seek to end for append
                    file.seekFromEnd(0) catch {
                        stderr.writeAll("Cannot seek to end of file.\n") catch {};
                        return CommandStatus{ .Code = 1 };
                    };

                    file.writeAll(captured_output.getContents()) catch |err| {
                        switch (err) {
                            error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {},
                            else => stderr.writeAll("Cannot write to file.\n") catch {},
                        }
                        return CommandStatus{ .Code = 1 };
                    };
                },
                .InputFrom => {
                    // Input redirection already handled above
                    continue;
                },
            }
        }

        return status;
    }
};