1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include "LaunchContext.hpp" |
4 | #include "mossh.hpp" |
5 | #include "parser.hpp" |
6 | |
7 | #include <argparse/libargparse.h> |
8 | #include <cassert> |
9 | #include <fcntl.h> |
10 | #include <filesystem> |
11 | #include <fstream> |
12 | #include <iostream> |
13 | #include <mos/tasks/signal_types.h> |
14 | #include <readline/libreadline.h> |
15 | #include <signal.h> |
16 | #include <stdio.h> |
17 | #include <stdlib.h> |
18 | #include <string.h> |
19 | #include <sys/stat.h> |
20 | #include <sys/wait.h> |
21 | #include <unistd.h> |
22 | |
23 | bool verbose = false; |
24 | |
25 | bool execute_line(const std::string &in) |
26 | { |
27 | auto line = string_trim(in); |
28 | |
29 | // split the programs |
30 | auto spec = parse_commandline(command: line); |
31 | if (!spec) |
32 | return false; |
33 | |
34 | LaunchContext ctx{ std::move(spec) }; |
35 | return ctx.start(); |
36 | } |
37 | |
38 | static void sigchld_handler(int signal) |
39 | { |
40 | MOS_UNUSED(signal); |
41 | if (verbose) |
42 | printf(format: "collecting zombies..." ); |
43 | pid_t pid; |
44 | int status = 0; |
45 | while ((pid = waitpid(pid: -1, status: &status, WNOHANG)) > 0) |
46 | { |
47 | if (verbose) |
48 | printf(format: " %d" , pid); |
49 | |
50 | if (WIFEXITED(status)) |
51 | { |
52 | if (verbose && WEXITSTATUS(status) != 0) |
53 | printf(format: " exited with status %d" , WEXITSTATUS(status)); |
54 | } |
55 | else if (WIFSIGNALED(status)) |
56 | { |
57 | if (verbose) |
58 | printf(format: " killed by signal %d" , WTERMSIG(status)); |
59 | } |
60 | else if (WIFSTOPPED(status)) |
61 | { |
62 | if (verbose) |
63 | printf(format: " stopped by signal %d" , WSTOPSIG(status)); |
64 | } |
65 | else if (WIFCONTINUED(status)) |
66 | { |
67 | if (verbose) |
68 | printf(format: " continued" ); |
69 | } |
70 | } |
71 | if (verbose) |
72 | puts(string: " done." ); |
73 | } |
74 | |
75 | static void sigint_handler(int signal) |
76 | { |
77 | MOS_UNUSED(signal); |
78 | } |
79 | |
80 | bool do_interpret_script(const std::filesystem::path &path) |
81 | { |
82 | std::fstream f(path); |
83 | if (!f.is_open()) |
84 | { |
85 | std::cerr << "Failed to open '" << path << "'" << std::endl; |
86 | return false; |
87 | } |
88 | |
89 | std::string line; |
90 | while (std::getline(is&: f, str&: line)) |
91 | { |
92 | if (verbose) |
93 | std::cout << "<script>: " << line << std::endl; |
94 | execute_line(in: line); |
95 | } |
96 | |
97 | return true; |
98 | } |
99 | |
100 | static const argparse_arg_t mossh_options[] = { |
101 | { NULL, .abbr: 'c', .argtype: ARGPARSE_REQUIRED, .help: "MOS shell script file" }, // |
102 | { .full: "help" , .abbr: 'h', .argtype: ARGPARSE_NONE, .help: "Show this help message" }, // |
103 | { .full: "init" , .abbr: 'i', .argtype: ARGPARSE_REQUIRED, .help: "The initial script to execute" }, // |
104 | { .full: "no-init" , .abbr: 'I', .argtype: ARGPARSE_NONE, .help: "Do not execute the initial script" }, // |
105 | { .full: "verbose" , .abbr: 'V', .argtype: ARGPARSE_NONE, .help: "Enable verbose output" }, // |
106 | { .full: "version" , .abbr: 'v', .argtype: ARGPARSE_NONE, .help: "Show the version" }, // |
107 | { .full: "jsonrpc" , .abbr: 'j', .argtype: ARGPARSE_NONE, .help: "Enable JSON-RPC mode" }, // |
108 | {}, // |
109 | }; |
110 | |
111 | int main(int argc, const char **argv) |
112 | { |
113 | MOS_UNUSED(argc); |
114 | |
115 | struct sigaction sa; |
116 | sa.sa_flags = SA_RESTART; |
117 | sigemptyset(&sa.sa_mask); |
118 | sa.sa_handler = sigchld_handler; |
119 | sigaction(SIGCHLD, &sa, NULL); |
120 | |
121 | sa.sa_handler = sigint_handler; |
122 | sigaction(SIGINT, &sa, NULL); |
123 | |
124 | argparse_state_t state; |
125 | argparse_init(options: &state, argv); |
126 | |
127 | std::filesystem::path init_script = "/initrd/assets/init.msh" ; |
128 | |
129 | bool json_mode = false; |
130 | |
131 | while (true) |
132 | { |
133 | const int option = argparse_long(options: &state, longopts: mossh_options, NULL); |
134 | if (option == -1) |
135 | break; |
136 | |
137 | switch (option) |
138 | { |
139 | case 'i': init_script = state.optarg; break; |
140 | case 'I': init_script.clear(); break; |
141 | case 'c': return do_interpret_script(path: argv[2]) ? 0 : 1; |
142 | case 'V': verbose = true; break; |
143 | case 'v': execute_line(in: strdup(string: "version" )); return 0; |
144 | case 'j': json_mode = true; break; |
145 | case 'h': argparse_usage(options: &state, args: mossh_options, usage: "the MOS shell" ); return 0; |
146 | default: argparse_usage(options: &state, args: mossh_options, usage: "the MOS shell" ); return 1; |
147 | } |
148 | } |
149 | |
150 | if (!init_script.empty()) |
151 | { |
152 | if (!do_interpret_script(path: init_script)) |
153 | { |
154 | std::cout << "Failed to execute '" << init_script << "'" << std::endl; |
155 | return 1; |
156 | } |
157 | } |
158 | |
159 | std::cout << "Welcome to MOS-sh!" << std::endl; |
160 | |
161 | if (json_mode) |
162 | { |
163 | std::cout << "JSON-RPC mode enabled." << std::endl; |
164 | return do_jsonrpc(); |
165 | } |
166 | |
167 | char cwdbuf[1024] = { 0 }; |
168 | |
169 | while (1) |
170 | { |
171 | if (!getcwd(cwdbuf, sizeof(cwdbuf))) |
172 | { |
173 | std::cerr << "Failed to get current working directory." << std::endl; |
174 | cwdbuf[0] = '?'; |
175 | cwdbuf[1] = '\0'; |
176 | } |
177 | |
178 | const auto prompt = cwdbuf + " > "s ; |
179 | char *const line = readline(prompt: prompt.c_str()); |
180 | const std::string line_str = line; |
181 | free(pointer: line); |
182 | execute_line(in: line_str); |
183 | } |
184 | |
185 | return 0; |
186 | } |
187 | |