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
23bool verbose = false;
24
25bool 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
38static 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
75static void sigint_handler(int signal)
76{
77 MOS_UNUSED(signal);
78}
79
80bool 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
100static 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
111int 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