1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "LaunchContext.hpp"
4#include "mossh.hpp"
5
6#include <errno.h>
7#include <iostream>
8#include <map>
9#include <mos/types.h>
10#include <ranges>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <string>
15#include <unistd.h>
16
17std::map<std::string, std::string> aliases = {};
18static void greet(void)
19{
20 puts(string: "MOS Shell Version 2");
21}
22
23void do_alias(const std::vector<std::string> &argv)
24{
25 if (argv.empty())
26 {
27 for (const auto &[name, value] : aliases)
28 std::cerr << "alias: '" << name << "' -> '" << value << "'" << std::endl;
29 return;
30 }
31
32 if (argv.size() != 2)
33 {
34 std::cerr << "alias: wrong number of arguments" << std::endl;
35 std::cerr << "Usage: alias <name> <value>" << std::endl;
36 return;
37 }
38
39 const auto &[name, value] = std::tie(args: argv[0], args: argv[1]);
40 if (name == "-c")
41 {
42 // remove 'value' alias if it exists
43 if (aliases.contains(x: value))
44 {
45 aliases.erase(x: value);
46 return;
47 }
48 }
49
50 if (aliases.contains(x: name))
51 {
52 if (aliases[name] == value)
53 return; // no change
54
55 std::cout << "alias: replace alias '" << name << "': '" << aliases[name] << "' -> '" << value << "'" << std::endl;
56 aliases[name] = value;
57 return;
58 }
59
60 if (verbose)
61 std::cout << "alias: '" << name << "' -> '" << value << "'\n";
62 aliases[name] = value;
63}
64
65void do_cd(const std::vector<std::string> &argv)
66{
67 switch (argv.size())
68 {
69 case 0:
70 {
71 if (chdir(path: "/"))
72 std::cerr << "cd: /: Unexpected error: " << strerror(errno) << std::endl;
73 break;
74 }
75 case 1:
76 {
77 if (chdir(path: argv[0].c_str()))
78 std::cerr << "cd: " << argv[0] << ": " << strerror(errno) << std::endl;
79 break;
80 }
81 default:
82 {
83 printf(format: "cd: too many arguments\n");
84 break;
85 }
86 }
87}
88
89void do_clear(const std::vector<std::string> &argv)
90{
91 MOS_UNUSED(argv);
92 printf(format: "\033[2J\033[H");
93}
94
95void do_export(const std::vector<std::string> &argv)
96{
97 if (argv.size() == 0)
98 {
99 puts(string: "export: wrong number of arguments\n");
100 puts(string: "Usage: export <name=value> ...\n");
101 return;
102 }
103
104 for (const auto &arg : argv)
105 {
106 const auto pos = arg.find(c: '=');
107 if (pos == std::string::npos)
108 {
109 std::cout << "export: invalid argument: '" << arg << "'" << std::endl;
110 continue;
111 }
112
113 const auto name = arg.substr(pos: 0, n: pos);
114 auto value = arg.substr(pos: pos + 1);
115
116 if ((value.starts_with(x: '\'') && value.ends_with(x: '\'')) || (value.starts_with(x: '"') && value.ends_with(x: '"')))
117 value = value.substr(pos: 1, n: value.size() - 2);
118
119 if (verbose)
120 std::cout << "export: '" << name << "' -> '" << value << "'" << std::endl;
121
122 setenv(name.c_str(), value.c_str(), 1);
123
124 if (name == "PATH")
125 get_paths(force: true); // clear the cache
126 }
127}
128
129void do_exit(const std::vector<std::string> &argv)
130{
131 if (argv.size() == 0)
132 exit(status: 0);
133
134 const int exit_code = atoi(string: argv[0].c_str());
135 exit(status: exit_code);
136}
137
138void do_help(const std::vector<std::string> &argv)
139{
140 MOS_UNUSED(argv);
141 greet();
142 puts(string: "Type 'help' to see this help\n");
143 puts(string: "The following commands are built-in:\n");
144 for (int i = 0; builtin_commands[i].command; i++)
145 printf(format: " %-10s %s\n", builtin_commands[i].command, builtin_commands[i].description);
146 puts(string: "Happy hacking!\n");
147}
148
149void do_msleep(const std::vector<std::string> &argv)
150{
151 if (argv.size() != 1)
152 {
153 printf(format: "msleep: wrong number of arguments\n");
154 printf(format: "Usage: msleep <ms>\n");
155 return;
156 }
157
158 u64 ms = atoi(string: argv[0].c_str());
159 if (ms <= 0)
160 {
161 printf(format: "msleep: invalid argument: '%s'\n", argv[0].c_str());
162 return;
163 }
164
165 usleep(ms * 1000); // usleep takes microseconds, we take milliseconds
166}
167
168void do_pid(const std::vector<std::string> &argv)
169{
170 MOS_UNUSED(argv);
171 std::cout << "pid: " << getpid() << std::endl;
172}
173
174void do_repeat(const std::vector<std::string> &argv)
175{
176 switch (argv.size())
177 {
178 case 0:
179 case 1:
180 {
181 puts(string: "usage: repeat <count> <command> [args...]\n");
182 break;
183 }
184 default:
185 {
186 int count = atoi(string: argv[0].c_str());
187 if (count <= 0)
188 {
189 std::cerr << "repeat: invalid count: '" << argv[0] << "'" << std::endl;
190 break;
191 }
192
193 std::cout << "FIXME: repeat: " << argv[1] << std::endl;
194 return;
195
196 for (int i = 0; i < count; i++)
197 {
198 const auto program = argv[1];
199 const auto new_argv = argv | std::views::drop(2) | std::ranges::to<std::vector>();
200
201 LaunchContext context{ new_argv };
202 if (!context.start())
203 {
204 std::cerr << "repeat: failed to start '" << program << "'" << std::endl;
205 break;
206 }
207 }
208 break;
209 }
210 }
211}
212
213void do_show_path(const std::vector<std::string> &argv)
214{
215 MOS_UNUSED(argv);
216
217 puts(string: "Program search path:");
218 for (const auto &path : get_paths())
219 printf(format: " %s\n", path.c_str());
220}
221
222void do_sleep(const std::vector<std::string> &argv)
223{
224 if (argv.size() != 1)
225 {
226 printf(format: "sleep: wrong number of arguments\n");
227 printf(format: "Usage: sleep <seconds>\n");
228 return;
229 }
230
231 int seconds = atoi(string: argv[0].c_str());
232 if (seconds <= 0)
233 {
234 printf(format: "sleep: invalid argument: '%s'\n", argv[0].c_str());
235 return;
236 }
237
238 sleep(seconds);
239}
240
241void do_source(const std::vector<std::string> &argv)
242{
243 if (argv.size() != 1)
244 {
245 printf(format: "source: wrong number of arguments\n");
246 printf(format: "Usage: source <file>\n");
247 return;
248 }
249
250 do_interpret_script(path: argv[0]);
251}
252
253void do_version(const std::vector<std::string> &argv)
254{
255 MOS_UNUSED(argv);
256 greet();
257}
258
259void do_which(const std::vector<std::string> &argv)
260{
261 switch (argv.size())
262 {
263 case 0:
264 {
265 puts(string: "which: missing argument\n");
266 break;
267 }
268 case 1:
269 {
270 LaunchContext context({ argv[0] });
271 if (!context.resolve_program_path())
272 {
273 printf(format: "which: %s: command not found\n", argv[0].c_str());
274 break;
275 }
276
277 printf(format: "%s\n", context.program_path().c_str());
278 break;
279 }
280 default:
281 {
282 printf(format: "which: too many arguments\n");
283 break;
284 }
285 }
286}
287
288const std::vector<command_t> builtin_commands = {
289 { .command = "alias", .action = do_alias, .description = "Create an alias" },
290 { .command = "cd", .action = do_cd, .description = "Change the current directory" },
291 { .command = "clear", .action = do_clear, .description = "Clear the screen" },
292 { .command = "exit", .action = do_exit, .description = "Exit the shell" },
293 { .command = "export", .action = do_export, .description = "Export a variable" },
294 { .command = "help", .action = do_help, .description = "Show this help" },
295 { .command = "msleep", .action = do_msleep, .description = "Sleep for a number of milliseconds" },
296 { .command = "pid", .action = do_pid, .description = "Show the current process ID" },
297 { .command = "repeat", .action = do_repeat, .description = "Repeat a command a number of times" },
298 { .command = "show-path", .action = do_show_path, .description = "Show the search path for programs" },
299 { .command = "sleep", .action = do_sleep, .description = "Sleep for a number of seconds" },
300 { .command = "source", .action = do_source, .description = "Execute a script" },
301 { .command = "version", .action = do_version, .description = "Show version information" },
302 { .command = "which", .action = do_which, .description = "Show the full path of a command" },
303};
304