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 | |
17 | std::map<std::string, std::string> aliases = {}; |
18 | static void greet(void) |
19 | { |
20 | puts(string: "MOS Shell Version 2" ); |
21 | } |
22 | |
23 | void 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 | |
65 | void 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 | |
89 | void do_clear(const std::vector<std::string> &argv) |
90 | { |
91 | MOS_UNUSED(argv); |
92 | printf(format: "\033[2J\033[H" ); |
93 | } |
94 | |
95 | void 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 | |
129 | void 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 | |
138 | void 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 | |
149 | void 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 | |
168 | void do_pid(const std::vector<std::string> &argv) |
169 | { |
170 | MOS_UNUSED(argv); |
171 | std::cout << "pid: " << getpid() << std::endl; |
172 | } |
173 | |
174 | void 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 | |
213 | void 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 | |
222 | void 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 | |
241 | void 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 | |
253 | void do_version(const std::vector<std::string> &argv) |
254 | { |
255 | MOS_UNUSED(argv); |
256 | greet(); |
257 | } |
258 | |
259 | void 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 | |
288 | const 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 | |