aboutsummaryrefslogtreecommitdiff
path: root/kernel/test/kshell/kshell.c
blob: a26c42c995b669f747d7a5fad157f936045fce90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
#include "test/kshell/kshell.h"
#include <util/printf.h>

#include "config.h"

#include "command.h"
#include "commands.h"
#include "tokenizer.h"

#ifndef __VFS__

#include "drivers/chardev.h"
#include "drivers/tty/tty.h"

#endif

#include "mm/kmalloc.h"

#include "proc/proc.h"

#ifdef __VFS__

#include "fs/fcntl.h"
#include "fs/open.h"
#include "fs/vfs_syscall.h"

#endif

#include "test/kshell/io.h"

#include "util/debug.h"
#include "util/string.h"

void *kshell_proc_run(long tty, void *arg2)
{
    // Create kernel shell on given TTY
    kshell_t *kshell = kshell_create((uint8_t)tty);
    if (!kshell)
    {
        do_exit(-1);
    }

    while (kshell_execute_next(kshell) > 0)
        ;
    kshell_destroy(kshell);
    return NULL;
}

void kshell_init()
{
    kshell_add_command("help", kshell_help,
                       "prints a list of available commands");
    kshell_add_command("echo", kshell_echo, "display a line of text");
    kshell_add_command("clear", kshell_clear, "clears the screen");
#ifdef __VFS__
    kshell_add_command("cat", kshell_cat,
                       "concatenate files and print on the standard output");
    kshell_add_command("ls", kshell_ls, "list directory contents");
    kshell_add_command("cd", kshell_cd, "change the working directory");
    kshell_add_command("rm", kshell_rm, "remove files");
    kshell_add_command("link", kshell_link,
                       "call the link function to create a link to a file");
    kshell_add_command("rmdir", kshell_rmdir, "remove empty directories");
    kshell_add_command("mkdir", kshell_mkdir, "make directories");
    kshell_add_command("stat", kshell_stat, "display file status");
    kshell_add_command("vfstest", kshell_vfs_test, "runs VFS tests");
#endif

#ifdef __S5FS__
    kshell_add_command("s5fstest", kshell_s5fstest, "runs S5FS tests");
#endif

    kshell_add_command("halt", kshell_halt, "halts the systems");
    kshell_add_command("exit", kshell_exit, "exits the shell");
}

void kshell_add_command(const char *name, kshell_cmd_func_t cmd_func,
                        const char *desc)
{
    kshell_command_t *cmd;

    cmd = kshell_command_create(name, cmd_func, desc);
    KASSERT(NULL != cmd);
    list_insert_tail(&kshell_commands_list, &cmd->kc_commands_link);

    dprintf("Added %s command\n", name);
}

kshell_t *kshell_create(uint8_t ttyid)
{
    kshell_t *ksh;

    ksh = (kshell_t *)kmalloc(sizeof(kshell_t));
    if (NULL == ksh)
    {
        dprintf("Not enough memory to create kshell\n");
        return NULL;
    }

#ifdef __VFS__
    long fd;
    char tty_path[MAXPATHLEN];

    snprintf(tty_path, sizeof(tty_path), "/dev/tty%u", ttyid);
    if ((fd = do_open(tty_path, O_RDWR)) < 0)
    {
        dprintf("Couldn't open %s\n", tty_path);
        kfree(ksh);
        return NULL;
    }
    ksh->ksh_out_fd = ksh->ksh_in_fd = ksh->ksh_fd = (int)fd;
#else
    chardev_t *cd;
    cd = chardev_lookup(MKDEVID(TTY_MAJOR, ttyid));
    if (NULL == cd)
    {
        dprintf("Couldn't find TTY with ID %u\n", ttyid);
        kfree(ksh);
        return NULL;
    }
    ksh->ksh_cd = cd;
#endif

    dprintf("kshell successfully created on TTY %u\n", ttyid);
    return ksh;
}

void kshell_destroy(kshell_t *ksh)
{
    KASSERT(NULL != ksh);
    kprintf(ksh, "Bye!\n");
#ifdef __VFS__
    if (do_close(ksh->ksh_fd) < 0)
    {
        panic("Error closing TTY file descriptor\n");
    }
    dprintf("kshell with file descriptor %d destroyed\n", ksh->ksh_fd);
#else
    dprintf("kshell on byte device %u destroyed\n", ksh->ksh_cd->cd_id);
#endif
    kfree(ksh);
}

/**
 * Removes the token from the input line it came from, replacing it
 * with spaces.
 *
 * @param ksh the kshell
 * @param token the token to scrub
 */
static void kshell_scrub_token(kshell_t *ksh, kshell_token_t *token)
{
    KASSERT(NULL != ksh);
    KASSERT(NULL != token);
    KASSERT(NULL != token->kt_text);

    memset(token->kt_text, ' ', token->kt_textlen);
}

/**
 * Finds the redirection operators ('<' and '>') in the input line,
 * stores the name of the file to redirect stdout in in redirect_out
 * and the name of the file to redirect stdin in redirect_in, and
 * removes any trace of the redirection from the input line.
 *
 * @param ksh the kshell
 * @param line the input line
 * @param redirect_in buffer to store the name of the file to redirect
 * stdin from. Buffer size assumed to be at least MAXPATHLEN
 * @param redirect_out buffer to store the name of the file to stdout
 * to. Buffer size assumed to be at least MAXPATHLEN
 * @param append out parameter containing true if the file stdout is
 * being redirected to should be appeneded to
 * @return 0 on success and <0 on error
 */
static long kshell_find_redirection(kshell_t *ksh, char *line,
                                    char *redirect_in, char *redirect_out,
                                    int *append)
{
    long retval;
    kshell_token_t token;

    while ((retval = kshell_next_token(ksh, line, &token)) > 0)
    {
        KASSERT(token.kt_type != KTT_EOL);
        line += retval;

        if (token.kt_type == KTT_WORD)
        {
            continue;
        }

        char *redirect = NULL;
        if (token.kt_type == KTT_REDIRECT_OUT)
        {
            redirect = redirect_out;
            *append = 0;
        }
        else if (token.kt_type == KTT_REDIRECT_OUT_APPEND)
        {
            redirect = redirect_out;
            *append = 1;
        }
        else if (token.kt_type == KTT_REDIRECT_IN)
        {
            redirect = redirect_in;
        }
        kshell_scrub_token(ksh, &token);

        if ((retval = kshell_next_token(ksh, line, &token)) == 0)
        {
            goto unexpected_token;
        }
        KASSERT(retval > 0);

        if (token.kt_type != KTT_WORD)
        {
            goto unexpected_token;
        }
        strncpy(redirect, token.kt_text, token.kt_textlen);
        redirect[token.kt_textlen] = '\0';
        kshell_scrub_token(ksh, &token);
    }
    return 0;

unexpected_token:
    kprintf(ksh, "kshell: Unexpected token '%s'\n",
            kshell_token_type_str(token.kt_type));
    return -1;
}

/**
 * Ignoring whitespace, finds the next argument from a string.
 *
 * @param ksh the kshell
 * @param line the string to find arguments in
 * @param arg out parameter which should point to the beginning of the
 * next argument if any were found
 * @param arglen the length of the argument if any were found
 * @return 0 if no argument was found, and the number of bytes read
 * otherwise
 */
static long kshell_find_next_arg(kshell_t *ksh, char *line, char **arg,
                                 size_t *arglen)
{
    long retval;
    kshell_token_t token;

    if ((retval = kshell_next_token(ksh, line, &token)) == 0)
    {
        KASSERT(token.kt_type == KTT_EOL);
        return retval;
    }
    KASSERT(token.kt_type == KTT_WORD);
    *arg = token.kt_text;
    *arglen = token.kt_textlen;

    /*
     * This is a little hacky, but not awful.
     *
     * If we find a '\0', there are no more arguments
     * left. However, we still need to return a nonzero value to
     * alert the calling function about the argument we just
     * found. Since there are no more arguments, we aren't
     * overwriting anything by setting the next byte to '\0'. We
     * also know that we aren't writing into invalid memory
     * because in the struct definition for kshell_t, we declared
     * ksh_buf to have KSH_BUF_SIZE + 1 bytes.
     */
    if (line[retval] == '\0')
    {
        line[retval + 1] = '\0';
    }
    else
    {
        line[retval] = '\0';
    }
    return retval;
}

/**
 * Finds the arguments of the command just into a kshell. This should
 * be called directly after returning from a read.
 *
 * @param buf the buffer to extract arguments from
 * @param argv out parameter containing an array of null-terminated
 * strings, one for each argument
 * @param max_args the maximum number of arguments to find
 * @param argc out parameter containing the number of arguments found
 */
static void kshell_get_args(kshell_t *ksh, char *buf, char **argv,
                            size_t max_args, size_t *argc)
{
    size_t arglen;

    KASSERT(NULL != buf);
    KASSERT(NULL != argv);
    KASSERT(max_args > 0);
    KASSERT(NULL != argc);

    *argc = 0;
    while (kshell_find_next_arg(ksh, buf, argv + *argc, &arglen) &&
           *argc < max_args)
    {
        buf = argv[*argc] + arglen + 1;
        ++(*argc);
    }
    if (*argc >= max_args)
    {
        dprintf("Too many arguments\n");
    }
}

kshell_command_t *kshell_lookup_command(const char *name, size_t namelen)
{
    if (namelen > KSH_CMD_NAME_LEN)
    {
        namelen = KSH_CMD_NAME_LEN;
    }

    list_iterate(&kshell_commands_list, cmd, kshell_command_t,
                 kc_commands_link)
    {
        KASSERT(NULL != cmd);
        if ((strncmp(cmd->kc_name, name, namelen) == 0) &&
            (namelen == strnlen(cmd->kc_name, KSH_CMD_NAME_LEN)))
        {
            return cmd;
        }
    }
    return NULL;
}

#ifdef __VFS__

/**
 * If stdin or stdout has been redirected to a file, closes the file
 * and directs I/O back to stdin and stdout.
 *
 * @param the kshell
 */
void kshell_undirect(kshell_t *ksh)
{
    KASSERT(NULL != ksh);

    if (ksh->ksh_in_fd != ksh->ksh_fd)
    {
        if (do_close(ksh->ksh_in_fd) < 0)
        {
            panic("kshell: Error closing file descriptor %d\n", ksh->ksh_in_fd);
        }
        ksh->ksh_in_fd = ksh->ksh_fd;
    }
    if (ksh->ksh_out_fd != ksh->ksh_fd)
    {
        if (do_close(ksh->ksh_out_fd) < 0)
        {
            panic("kshell: Error closing file descriptor %d\n",
                  ksh->ksh_out_fd);
        }
        ksh->ksh_out_fd = ksh->ksh_fd;
    }
}

/**
 * Redirects stdin and stdout.
 *
 * @param ksh the kshell
 * @param redirect_in the name of the file to redirect stdin from
 * @param redirect_out the name of the file to redirect stdout to
 * @param append if true, output will be appended
 * @return 0 on sucess and <0 on error. If returns with <0, no streams
 * will be redirected.
 */
long kshell_redirect(kshell_t *ksh, const char *redirect_in,
                     const char *redirect_out, int append)
{
    long fd;

    KASSERT(NULL != ksh);
    KASSERT(NULL != redirect_in);
    KASSERT(NULL != redirect_out);

    if (redirect_in[0] != '\0')
    {
        if ((fd = do_open(redirect_in, O_RDONLY | O_CREAT)) < 0)
        {
            kprintf(ksh, "kshell: %s: Error opening file\n", redirect_in);
            goto error;
        }
        ksh->ksh_in_fd = (int)fd;
    }
    if (redirect_out[0] != '\0')
    {
        int flags = append ? O_WRONLY | O_CREAT | O_APPEND : O_WRONLY | O_CREAT | O_TRUNC;
        if ((fd = do_open(redirect_out, flags)) < 0)
        {
            kprintf(ksh, "kshell: %s: Error opening file\n", redirect_out);
            goto error;
        }
        ksh->ksh_out_fd = fd;
    }
    return 0;

error:
    kshell_undirect(ksh);
    return fd;
}

#endif

long kshell_execute_next(kshell_t *ksh)
{
    static const char *kshell_prompt = "kshell$";

    long nbytes, retval;
    kshell_command_t *cmd;
    char *args[KSH_MAX_ARGS];
    size_t argc;
    char redirect_in[MAXPATHLEN];
    char redirect_out[MAXPATHLEN];
    int append;

    /*
     * Need that extra byte at the end. See comment in
     * kshell_find_next_arg.
     */
    char buf[KSH_BUF_SIZE + 1];

    KASSERT(NULL != ksh);

    kprintf(ksh, "%s ", kshell_prompt);

    if ((nbytes = kshell_read(ksh, buf, KSH_BUF_SIZE)) <= 0)
    {
        return nbytes;
    }
    if (nbytes == 1)
    {
        return 1;
    }
    if (buf[nbytes - 1] == '\n')
    {
        /* Overwrite the newline with a null terminator */
        buf[--nbytes] = '\0';
    }
    else
    {
        /* Add the null terminator to the end */
        buf[nbytes] = '\0';
    }

    /* Even though we can't redirect I/O to files before VFS, we
     * still want to scrub out any reference to redirection before
     * passing the line off to kshell_get_args */
    redirect_in[0] = redirect_out[0] = '\0';
    if (kshell_find_redirection(ksh, buf, redirect_in, redirect_out, &append) <
        0)
    {
        goto done;
    }
#ifdef __VFS__
    if ((retval = kshell_redirect(ksh, redirect_in, redirect_out, append)) <
        0)
    {
        dprintf("Error redirecting I/O\n");
        goto done;
    }
#endif

    kshell_get_args(ksh, buf, args, KSH_MAX_ARGS, &argc);
    if (argc == 0)
    {
        goto done;
    }

    dprintf("Attempting to execute command '%s'\n", args[0]);

    if (strncmp(args[0], "exit", strlen("exit")) == 0)
    {
        nbytes = 0;
        goto done;
    }

    if ((cmd = kshell_lookup_command(args[0], strlen(args[0]))) == NULL)
    {
        kprintf(ksh, "kshell: %s not a valid command\n", args[0]);
    }
    else
    {
        if ((retval = cmd->kc_cmd_func(ksh, argc, args)) < 0)
        {
            nbytes = retval;
            goto done;
        }
    }
    goto done;

done:
#ifdef __VFS__
    kshell_undirect(ksh);
#endif
    return nbytes;
}