1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include "LaunchContext.hpp" |
4 | |
5 | #include "mossh.hpp" |
6 | #include "parser.hpp" |
7 | |
8 | #include <bits/posix/posix_string.h> |
9 | #include <cassert> |
10 | #include <cstring> |
11 | #include <fcntl.h> |
12 | #include <iostream> |
13 | #include <memory> |
14 | #include <ranges> |
15 | #include <sys/stat.h> |
16 | #include <sys/wait.h> |
17 | #include <unistd.h> |
18 | |
19 | static const auto find_builtin = [](const std::string &name) -> std::optional<command_t> |
20 | { |
21 | for (const auto &builtin : builtin_commands) |
22 | if (builtin.command == name) |
23 | return builtin; |
24 | return std::nullopt; |
25 | }; |
26 | |
27 | static const auto find_alias = [](const std::string &name) -> std::optional<std::string> |
28 | { |
29 | if (aliases.contains(x: name)) |
30 | return aliases[name]; |
31 | return std::nullopt; |
32 | }; |
33 | |
34 | static std::optional<std::filesystem::path> locate_program(const std::string &command) |
35 | { |
36 | const auto try_resolve = [&](const std::filesystem::path &path) -> std::optional<std::filesystem::path> |
37 | { |
38 | struct stat statbuf; |
39 | if (stat(path.c_str(), &statbuf) == 0) |
40 | if (S_ISREG(statbuf.st_mode)) |
41 | return path; |
42 | return std::nullopt; |
43 | }; |
44 | |
45 | // firstly try the command as is |
46 | if (command.starts_with(x: '/') || command.starts_with(x: "./" )) |
47 | return try_resolve(command); |
48 | |
49 | for (const auto &path : get_paths()) |
50 | { |
51 | const auto fullpath = path / command; |
52 | if (auto resolved = try_resolve(fullpath); resolved) |
53 | return resolved; |
54 | } |
55 | |
56 | return std::nullopt; |
57 | } |
58 | |
59 | bool FDRedirection::do_redirect(int fd) |
60 | { |
61 | if (target_fd == -1) |
62 | { |
63 | std::cerr << "FDRedirection: target_fd is not set" << std::endl; |
64 | return false; |
65 | } |
66 | |
67 | if (verbose) |
68 | std::cout << "Redirecting fd " << fd << " to fd " << target_fd << std::endl; |
69 | |
70 | if (dup2(src_fd: target_fd, dest_fd: fd) == -1) |
71 | { |
72 | std::cerr << "Failed to redirect fd " << fd << " to fd " << target_fd << std::endl; |
73 | return false; |
74 | } |
75 | |
76 | return true; |
77 | } |
78 | |
79 | bool FileRedirection::do_redirect(int fd) |
80 | { |
81 | if (file.empty()) |
82 | { |
83 | std::cerr << "FileRedirection: file is not set" << std::endl; |
84 | return false; |
85 | } |
86 | |
87 | if (verbose) |
88 | std::cout << "Redirecting fd " << fd << " to file " << file << std::endl; |
89 | |
90 | int flags = O_CREAT; |
91 | |
92 | if (mode == ReadOnly) |
93 | flags |= O_RDONLY; |
94 | else if (mode == ReadWrite) |
95 | flags |= O_RDWR; |
96 | else if (mode == WriteOnly) |
97 | flags |= O_WRONLY; |
98 | |
99 | if (append) |
100 | flags |= O_APPEND; |
101 | |
102 | const auto file_fd = open(path: file.c_str(), flags, 0644); |
103 | if (file_fd == -1) |
104 | { |
105 | std::cerr << "Failed to open file " << file << "(" << strerror(errno) << ")" << std::endl; |
106 | return false; |
107 | } |
108 | |
109 | if (dup2(src_fd: file_fd, dest_fd: fd) == -1) |
110 | { |
111 | std::cerr << "Failed to redirect fd " << fd << " to file " << file << std::endl; |
112 | return false; |
113 | } |
114 | |
115 | // close the file descriptor |
116 | if (file_fd != fd) |
117 | close(fd: file_fd); |
118 | |
119 | return true; |
120 | } |
121 | |
122 | LaunchContext::LaunchContext(std::unique_ptr<ProgramSpec> &&spec) : program_spec(std::move(spec)) |
123 | { |
124 | assert(program_spec); |
125 | argv = program_spec->argv; |
126 | redirections = std::move(program_spec->redirections); |
127 | |
128 | // get need_wait |
129 | should_wait = !program_spec->background; |
130 | } |
131 | |
132 | LaunchContext::LaunchContext(const std::vector<std::string> &argv) : argv(argv) |
133 | { |
134 | } |
135 | |
136 | bool LaunchContext::resolve_program_path() |
137 | { |
138 | if (m_program_path.empty()) |
139 | { |
140 | if (const auto resolved = locate_program(command: command()); resolved) |
141 | { |
142 | m_program_path = *resolved; |
143 | return true; |
144 | } |
145 | return false; |
146 | } |
147 | return true; |
148 | } |
149 | |
150 | bool LaunchContext::try_start_builtin() |
151 | { |
152 | if (const auto builtin = find_builtin(command()); builtin) |
153 | { |
154 | builtin->action(argv | std::views::drop(1) | std::ranges::to<std::vector>()); |
155 | return true; |
156 | } |
157 | |
158 | // if the command is a directory, run the cd builtin |
159 | struct stat statbuf; |
160 | if (stat(command().c_str(), &statbuf)) |
161 | return false; |
162 | |
163 | if (S_ISDIR(statbuf.st_mode)) |
164 | { |
165 | static const auto cd_builtin = find_builtin("cd" ); |
166 | assert(cd_builtin); |
167 | cd_builtin->action({ command() }); |
168 | return true; |
169 | } |
170 | |
171 | return false; |
172 | } |
173 | |
174 | bool LaunchContext::try_start_alias() |
175 | { |
176 | if (const auto alias = find_alias(command()); alias) |
177 | { |
178 | auto alias_spec = parse_commandline(command: *alias); |
179 | for (const auto &arg : argv | std::views::drop(1)) |
180 | alias_spec->argv.push_back(x: arg); |
181 | |
182 | LaunchContext alias_context{ std::move(alias_spec) }; |
183 | return alias_context.start(); |
184 | } |
185 | |
186 | return false; |
187 | } |
188 | |
189 | bool LaunchContext::try_start_program() |
190 | { |
191 | const pid_t pid = fork(); |
192 | if (pid == -1) |
193 | { |
194 | fprintf(stream: stderr, format: "Failed to execute '%s'\n" , program_path().c_str()); |
195 | return false; |
196 | } |
197 | |
198 | if (pid == 0) |
199 | { |
200 | spawn_in_child(); |
201 | __builtin_unreachable(); |
202 | } |
203 | |
204 | if (should_wait) |
205 | { |
206 | std::tie(args&: exit_code, args&: exit_signal) = wait_for_pid(pid); |
207 | |
208 | if (exit_code != 0) |
209 | { |
210 | std::cerr << "Program '" << command() << "' exited with code " << exit_code << std::endl; |
211 | return true; |
212 | } |
213 | else if (exit_signal != 0) |
214 | { |
215 | std::cerr << "Program '" << command() << "' exited with signal " << exit_signal << std::endl; |
216 | return true; |
217 | } |
218 | } |
219 | else |
220 | { |
221 | std::cout << "Started '" << argv[0] << "' with pid " << pid << std::endl; |
222 | } |
223 | |
224 | return true; |
225 | } |
226 | |
227 | bool LaunchContext::spawn_in_child() const |
228 | { |
229 | const auto argc = argv.size(); |
230 | |
231 | const char **argv_cstr = new const char *[argc + 1]; |
232 | for (size_t i = 0; i < argc; i++) |
233 | argv_cstr[i] = argv[i].c_str(); |
234 | argv_cstr[argc] = NULL; |
235 | |
236 | // perform redirections |
237 | for (const auto &[fd, redir] : redirections) |
238 | { |
239 | if (redir->do_redirect(fd)) |
240 | continue; |
241 | |
242 | fprintf(stream: stderr, format: "Failed to redirect fd %d\n" , fd); |
243 | goto bail; |
244 | } |
245 | |
246 | execv(m_program_path.c_str(), (char *const *) argv_cstr); |
247 | |
248 | bail: |
249 | fprintf(stream: stderr, format: "Failed to execute '%s'\n" , m_program_path.c_str()); |
250 | delete[] argv_cstr; // actually, unreachable |
251 | _exit(status: -1); |
252 | } |
253 | |
254 | bool LaunchContext::start() |
255 | { |
256 | if (!success && launch_type.builtin) |
257 | success = try_start_builtin(); |
258 | |
259 | if (!success && launch_type.alias) |
260 | success = try_start_alias(); |
261 | |
262 | if (!success && launch_type.program) |
263 | { |
264 | if (!resolve_program_path()) |
265 | success = false; |
266 | else |
267 | success = try_start_program(); |
268 | } |
269 | |
270 | if (!success) |
271 | fprintf(stream: stderr, format: "'%s' is not recognized as an internal or external command, operable program or batch file.\n" , command().c_str()); |
272 | |
273 | return success; |
274 | } |
275 | |