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: name.c_str(), value: value.c_str(), overwrite: 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(usec: 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_rand(const std::vector<std::string> &argv)
175{
176 MOS_UNUSED(argv);
177 std::cout << rand() << std::endl;
178}
179
180void do_repeat(const std::vector<std::string> &argv)
181{
182 switch (argv.size())
183 {
184 case 0:
185 case 1:
186 {
187 puts(string: "usage: repeat <count> <command> [args...]\n");
188 break;
189 }
190 default:
191 {
192 int count = atoi(string: argv[0].c_str());
193 if (count <= 0)
194 {
195 std::cerr << "repeat: invalid count: '" << argv[0] << "'" << std::endl;
196 break;
197 }
198
199 const auto program = argv[1];
200 const auto new_argv = argv | std::views::drop(1) | std::ranges::to<std::vector>();
201
202 for (int i = 0; i < count; i++)
203 {
204 LaunchContext context{ new_argv };
205 if (!context.start())
206 {
207 std::cerr << "repeat: failed to start '" << program << "'" << std::endl;
208 break;
209 }
210 }
211 break;
212 }
213 }
214}
215
216void do_show_path(const std::vector<std::string> &argv)
217{
218 MOS_UNUSED(argv);
219
220 puts(string: "Program search path:");
221 for (const auto &path : get_paths())
222 printf(format: " %s\n", path.c_str());
223}
224
225void do_sleep(const std::vector<std::string> &argv)
226{
227 if (argv.size() != 1)
228 {
229 printf(format: "sleep: wrong number of arguments\n");
230 printf(format: "Usage: sleep <seconds>\n");
231 return;
232 }
233
234 int seconds = atoi(string: argv[0].c_str());
235 if (seconds <= 0)
236 {
237 printf(format: "sleep: invalid argument: '%s'\n", argv[0].c_str());
238 return;
239 }
240
241 sleep(seconds: seconds);
242}
243
244void do_source(const std::vector<std::string> &argv)
245{
246 if (argv.size() != 1)
247 {
248 printf(format: "source: wrong number of arguments\n");
249 printf(format: "Usage: source <file>\n");
250 return;
251 }
252
253 do_interpret_script(path: argv[0]);
254}
255
256void do_version(const std::vector<std::string> &argv)
257{
258 MOS_UNUSED(argv);
259 greet();
260}
261
262void do_which(const std::vector<std::string> &argv)
263{
264 switch (argv.size())
265 {
266 case 0:
267 {
268 puts(string: "which: missing argument\n");
269 break;
270 }
271 case 1:
272 {
273 LaunchContext context({ argv[0] });
274 if (!context.resolve_program_path())
275 {
276 printf(format: "which: %s: command not found\n", argv[0].c_str());
277 break;
278 }
279
280 printf(format: "%s\n", context.program_path().c_str());
281 break;
282 }
283 default:
284 {
285 printf(format: "which: too many arguments\n");
286 break;
287 }
288 }
289}
290
291const std::vector<command_t> builtin_commands = {
292 { .command = "alias", .action = do_alias, .description = "Create an alias" },
293 { .command = "cd", .action = do_cd, .description = "Change the current directory" },
294 { .command = "clear", .action = do_clear, .description = "Clear the screen" },
295 { .command = "exit", .action = do_exit, .description = "Exit the shell" },
296 { .command = "export", .action = do_export, .description = "Export a variable" },
297 { .command = "help", .action = do_help, .description = "Show this help" },
298 { .command = "msleep", .action = do_msleep, .description = "Sleep for a number of milliseconds" },
299 { .command = "pid", .action = do_pid, .description = "Show the current process ID" },
300 { .command = "rand", .action = do_rand, .description = "Generate a random number" },
301 { .command = "repeat", .action = do_repeat, .description = "Repeat a command a number of times" },
302 { .command = "show-path", .action = do_show_path, .description = "Show the search path for programs" },
303 { .command = "sleep", .action = do_sleep, .description = "Sleep for a number of seconds" },
304 { .command = "source", .action = do_source, .description = "Execute a script" },
305 { .command = "version", .action = do_version, .description = "Show version information" },
306 { .command = "which", .action = do_which, .description = "Show the full path of a command" },
307};
308