1/*
2 * Copyright 2016, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(D61_BSD)
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
15#include <assert.h>
16#include <string.h>
17#include <time.h>
18#include <refos/refos.h>
19#include <refos-util/init.h>
20#include <refos-io/stdio.h>
21#include <refos-rpc/proc_client.h>
22#include <refos-rpc/proc_client_helper.h>
23#include <unistd.h>
24#include <autoconf.h>
25
26#include "print_time.h"
27
28/*! @file
29    @brief A simple terminal program for RefOS.
30
31    This is a rather minimalistic and hacky terminal app for RefOS. A better console such as bash,
32    or ash, may be ported in the future. The terminal app runs as the first userland program, and
33    presents an interactive shell. It's possible to launcher another instance of the terminal
34    program using the terminal program.
35*/
36
37#define TERMINAL_INPUT_ARG_LENGTH 10
38#define TERMINAL_INPUT_ARG_COUNT 10
39#define TERMINAL_INPUT_BUFFER_SIZE 512
40#define TERMINAL_NEW_LINE_CHAR '\r'
41#define TERMINAL_DELETE_CHAR 127
42#define TERMINAL_CLEAR_SCREEN "\e[2J\e[1;1H"
43
44static char *args[TERMINAL_INPUT_ARG_COUNT];
45static char exitProgram = 0;
46extern char **__environ; /*! < POSIX standard, from MUSLC lib. */
47
48/*! @brief Print a heart-warming welcome banner. */
49static void
50terminal_startup_message(void)
51{
52    printf(" ______     ______     ______   ______     ______    \n"
53           "/\\  == \\   /\\  ___\\   /\\  ___\\ /\\  __ \\   /\\  ___\\   \n"
54           "\\ \\  __<   \\ \\  __\\   \\ \\  __\\ \\ \\ \\/\\ \\  \\ \\___  \\  \n"
55           " \\ \\_\\ \\_\\  \\ \\_____\\  \\ \\_\\    \\ \\_____\\  \\/\\_____\\ \n"
56           "  \\/_/ /_/   \\/_____/   \\/_/     \\/_____/   \\/_____/ \n\n"
57           "-----------------------------------------------------\n"
58           "    Built on the seL4 microkernel.\n"
59           "    (c) Copyright 2016 Data61, CSIRO\n"
60           "-----------------------------------------------------\n");
61}
62
63/*! @brief Print some quick help. */
64static void
65terminal_printhelp(void)
66{
67    printf("RefOS Terminal Help:\n"
68           "    clear - Clear the screen.\n"
69           "    help - Display this help screen.\n"
70           "    exec - Run an executable.\n"
71           #if CONFIG_APP_TETRIS
72           "    exec fileserv/tetris - Run tetris game.\n"
73           #endif
74           #if CONFIG_APP_SNAKE
75           "    exec fileserv/snake - Run snake game.\n"
76           #endif
77           #if CONFIG_APP_NETHACK
78           "    exec fileserv/nethack - Run Nethack 3.4.3 game.\n"
79           #endif
80           #if CONFIG_APP_TEST_USER
81           "    exec fileserv/test_user - Run RefOS userland tests.\n"
82           #endif
83           "    exec fileserv/terminal - Run another instance of RefOS terminal.\n"
84           "    cd /fileserv/ - Change current working directory.\n"
85           "    printenv - Print all environment variables.\n"
86           "    setenv - Set an environment variable.\n"
87           "    time - Display the current system time.\n"
88           "    exit - Exit RefOS terminal.\n");
89}
90
91/*! @brief Execute a program. */
92static void
93terminal_exec(void)
94{
95    int status;
96    if (!args[1]) {
97        printf("exec: missing parameter.\n");
98        return;
99    }
100    char tempBuffer[1024];
101    snprintf(tempBuffer, 1024, "%s%s", getenv("PWD"), args[1]);
102    proc_new_proc(tempBuffer, "", true, 71, &status);
103
104    if (status == EFILENOTFOUND) {
105        printf("-terminal: %s: application not found\n", args[1]);
106    }
107}
108
109/*! @brief Evaluate a command. */
110static void
111terminal_evaluate_command(char *inputBuffer)
112{
113    if (!args[0]) return;
114
115    /* Crazy string parsing can go here. */
116    if (!strcmp(args[0], "exec")) {
117        terminal_exec();
118    } else if (!strcmp(args[0], "clear") || !strcmp(args[0], "cls") || !strcmp(args[0], "reset")) {
119        printf("%s", TERMINAL_CLEAR_SCREEN);
120    } else if (!strcmp(args[0], "exit") || !strcmp(args[0], "quit")) {
121        exitProgram = 1;
122    } else if (!strcmp(args[0], "help")) {
123        terminal_printhelp();
124    } else if (!strcmp(args[0], "time")) {
125        time_t rawTime = time(NULL);
126        struct tm *gmtTime = gmtime(&rawTime);
127        struct tm *localTime = localtime(&rawTime);
128        printf("Raw epoch time is %llu\n", (uint64_t) rawTime);
129        printf("Current GMT time is %s", refos_print_time(gmtTime));
130        printf("Current local time (%s) is %s", getenv("TZ"), refos_print_time(localTime));
131    } else if (!strcmp(args[0], "printenv")) {
132        for (int i = 0; __environ[i]; i++) {
133            printf("%s\n", __environ[i]);
134        }
135    } else if (!strcmp(args[0], "setenv")) {
136        if (args[1] && args[2]) {
137            setenv(args[1], args[2], true);
138        }
139        else {
140            printf("setenv: usage: setenv <VARIABLE> <VALUE>\n");
141        }
142    } else if (!strcmp(args[0], "cd") && args[1]) {
143        setenv("PWD", args[1], true);
144    } else {
145        printf("-terminal: %s: command not found\n", inputBuffer);
146    }
147}
148
149/*! @brief Input a new command. */
150static void
151terminal_get_input(char *inputBuffer)
152{
153    printf("refos:%s$ ", getenv("PWD"));
154    fflush(stdout);
155
156    int i = 0; inputBuffer[0] = '\0';
157    for (i = 0; i < TERMINAL_INPUT_BUFFER_SIZE;) {
158        inputBuffer[i] = getchar();
159        if (inputBuffer[i] == TERMINAL_NEW_LINE_CHAR) {
160            printf("\n");
161            inputBuffer[i] = '\0';
162            return;
163        } else if (inputBuffer[i] == TERMINAL_DELETE_CHAR) {
164            if (i > 0) {
165                printf("\b \b");
166                fflush(stdout);
167                inputBuffer[--i] = '\0';
168            }
169        } else {
170            printf("%c", inputBuffer[i]);
171            fflush(stdout);
172            i++;
173        }
174    }
175    inputBuffer[TERMINAL_INPUT_BUFFER_SIZE] = '\0';
176    printf("\n");
177}
178
179/*! @brief Reset the input buffer. */
180static inline void
181terminal_reset_input(void)
182{
183    for (int i = 0; i < TERMINAL_INPUT_ARG_COUNT; i++) {
184        args[i] = NULL;
185    }
186}
187
188/*! @brief Main terminal loop. */
189void
190terminal_mainloop(void)
191{
192    char inputBuffer[TERMINAL_INPUT_BUFFER_SIZE + 1];
193    char *ptr;
194    int i;
195
196    while (!exitProgram) {
197        terminal_reset_input();
198        terminal_get_input(inputBuffer);
199
200        ptr = strtok(inputBuffer, " ");
201        i = 0;
202        while (ptr != NULL) {
203            args[i] = ptr;
204            ptr = strtok(NULL, " ");
205            i++;
206        }
207
208        terminal_evaluate_command(inputBuffer);
209    }
210}
211
212/*! @brief Main terminal entry point. */
213int main()
214{
215    /* Future Work 3:
216       How the selfloader bootstraps user processes needs to be modified further. Changes were
217       made to accomodate the new way that muslc expects process's stacks to be set up when
218       processes start, but the one part of this that still needs to changed is how user processes
219       find their system call table. Currently the selfloader sets up user processes so that
220       the selfloader's system call table is used by user processes by passing the address of the
221       selfloader's system call table to the user processes via the user process's environment
222       variables. Ideally, user processes would use their own system call table.
223    */
224
225    uintptr_t address = strtoll(getenv("SYSTABLE"), NULL, 16);
226    refos_init_selfload_child(address);
227    refos_initialise();
228    terminal_startup_message();
229    terminal_mainloop();
230    printf("\n");
231    return 0;
232}
233