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
19static 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
27static 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
34static 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
59bool 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
79bool 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
122LaunchContext::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
132LaunchContext::LaunchContext(const std::vector<std::string> &argv) : argv(argv)
133{
134}
135
136bool 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
150bool 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
174bool 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
189bool 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
227bool 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
248bail:
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
254bool 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