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