source: trunk/user/ksh/ksh.c @ 278

Last change on this file since 278 was 230, checked in by max@…, 7 years ago

Create user/, and a basic shell.

File size: 12.3 KB
Line 
1/*
2 * Authors: Clément Guérin and Alain Greiner (2015)
3 */
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <unistd.h>
8#include <string.h>
9#include <fcntl.h>
10#include <dirent.h>
11#include <sys/stat.h>
12#include <sys/mman.h>
13#include <sys/wait.h>
14#include <signal.h>
15#include <errno.h>
16
17#define MAX_SIZE        (128)  // max number of characters in one command
18#define LOG_DEPTH   (128)  // max number of commands in log
19#define MAX_ARGS        (32)   // max number of arguments in a command
20#define FIFO_SIZE   (1024) // FIFO depth for recursive ls
21
22#define __unused(a)     (void)a
23
24extern char **environ;
25
26////////////////////////////////////////////////////////////////////////////////
27//         Global Variables
28////////////////////////////////////////////////////////////////////////////////
29
30struct {
31        char buf[MAX_SIZE];
32        size_t count;
33} log_entries[LOG_DEPTH];
34
35size_t ptw; // write pointer in log
36size_t ptr; // read pointer in log
37
38struct command_t {
39        char *name;
40        char *desc;
41        void (*fn)(int, char **);
42};
43
44struct command_t cmd[];
45
46////////////////////////////////////////////////////////////////////////////////
47//         Shell  Commands
48////////////////////////////////////////////////////////////////////////////////
49
50static void cmd_cat(int argc, char **argv)
51{
52        char *path, *buf = NULL;
53        struct stat st;
54        size_t size;
55        int fd;
56
57        if (argc != 2) {
58                printf("usage: cat pathname\n");
59                return;
60        }
61        path = argv[1];
62
63        /* open the file */
64        fd = open(path, O_RDONLY, 0);
65        if (fd < 0) {
66                printf("error: cannot open %s\n", path);
67                goto exit;
68        }
69
70        /* get file size */
71        if (stat(path, &st) == -1) {
72                printf("error: cannot stat %s\n", path);
73                goto exit;
74        }
75        if (S_ISDIR(st.st_mode)) {
76                printf("error: %s is a directory\n", path);
77                goto exit;
78        }
79        size = st.st_size;
80
81        /* mmap the file */
82        buf = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
83        if (buf == NULL || buf == (char *)-1) {
84                printf("error: cannot map %s\n", path);
85                goto exit;
86        }
87
88        /* set terminating '0' XXX */
89        buf[size-1] = 0;
90
91        /* display the file content */
92        printf("%s", buf);
93
94exit:
95        if (buf != NULL)
96                munmap(buf, size);
97        if (fd >= 0)
98                close(fd);
99}
100
101static void cmd_cp(int argc, char **argv)
102{
103        int src_fd = -1, dst_fd = -1;
104        char *srcpath, *dstpath;
105        struct stat st;
106        size_t size, i;
107        char buf[1024];
108
109        if (argc != 3) {
110                printf("usage: cp src_pathname dst_pathname\n");
111                return;
112        }
113
114        srcpath = argv[1];
115        dstpath = argv[2];
116
117        /* open the src file */
118        src_fd = open(srcpath, O_RDONLY, 0);
119        if (src_fd < 0) {
120                printf("error: cannot open %s / err = %d\n", srcpath, errno);
121                goto exit;
122        }
123
124        /* get file size */
125        if (stat(srcpath, &st) == -1) {
126                printf("error: cannot stat %s\n", srcpath);
127                goto exit;
128        }
129        if (S_ISDIR(st.st_mode)) {
130                printf("error: %s is a directory\n", srcpath);
131                goto exit;
132        }
133        size = st.st_size;
134
135        /* open the dst file */
136        dst_fd = open(dstpath, O_CREAT|O_TRUNC|O_RDWR, 0);
137        if (dst_fd < 0) {
138                printf("error: cannot open %s / err = %d\n", dstpath, errno);
139                goto exit;
140        }
141        if (stat(dstpath, &st) == -1) {
142                printf("error: cannot stat %s\n", dstpath);
143                goto exit;
144        }
145        if (S_ISDIR(st.st_mode)) {
146                printf("error: %s is a directory\n", dstpath);
147                goto exit;
148        }
149
150        i = 0;
151        while (i < size)
152        {
153                size_t rlen = (size - i < 1024 ? size - i : 1024);
154                size_t wlen;
155                ssize_t ret;
156
157                /* read the source */
158                ret = read(src_fd, buf, rlen);
159                if (ret == -1) {
160                        printf("error: cannot read from file %s\n", srcpath);
161                        goto exit;
162                }
163                rlen = (size_t)ret;
164
165                /* write to the destination */
166                ret = write(dst_fd, buf, rlen);
167                if (ret == -1) {
168                        printf("error: cannot write to file %s\n", dstpath);
169                        goto exit;
170                }
171                wlen = (size_t)ret;
172
173                /* check */
174                if (wlen != rlen) {
175                        printf("error: cannot write on device\n");
176                        goto exit;
177                }
178
179                i += rlen;
180        }
181
182exit:
183        if (src_fd >= 0)
184                close(src_fd);
185        if (dst_fd >= 0)
186                close(dst_fd);
187}
188
189static void cmd_exec(int argc, char **argv)
190{
191        pid_t pid, wpid;
192        int status;
193        char *path;
194
195        if (argc != 2) {
196                printf("usage: %s prog\n", argv[0]);
197                return;
198        }
199
200        path = argv[1];
201        argv++;
202
203        pid = fork();
204        if (pid == 0) {
205                /* child process */
206                if (execve(path, argv, environ) == -1) {
207                        printf("error: unable to execve %s\n", path);
208                }
209                exit(EXIT_FAILURE);
210        } else if (pid < 0) {
211                /* error forking */
212                printf("error: unable to fork\n");
213        } else {
214                /* parent process */
215                do {
216                        wpid = waitpid(pid, &status, WUNTRACED);
217                } while (!WIFEXITED(status) && !WIFSIGNALED(status));
218        }
219}
220
221static void cmd_help(int argc, char **argv)
222{
223        size_t i;
224        __unused(argc);
225        __unused(argv);
226        printf("available commands:\n");
227        for (i = 0; cmd[i].name; i++) {
228                printf("\t%s\t : %s\n", cmd[i].name , cmd[i].desc);
229        }
230}
231
232static void cmd_kill(int argc, char **argv)
233{
234        pid_t pid;
235
236        if (argc != 2) {
237                printf("usage: %s pid\n", argv[0]);
238                return;
239        }
240
241        pid = (pid_t)atoi(argv[1]);
242
243        if (kill(pid, SIGKILL) == -1) {
244                printf("error: unable to kill %d\n", (int)pid);
245        }
246}
247
248static void cmd_log(int argc, char **argv)
249{
250        size_t i;
251        __unused(argc);
252        __unused(argv);
253        printf("--- registered commands ---\n");
254        for (i = 0; i < LOG_DEPTH; i++) {
255                printf(" - %zu\t: %s\n", i, &log_entries[i].buf);
256        }
257}
258
259static void cmd_ls(int argc, char **argv)
260{
261    struct dirent *file;
262        char *path;
263    DIR *dir;
264
265        if (argc == 1) {
266                path = ".";
267        } else if (argc == 2) {
268                path = argv[1];
269        } else {
270                printf("usage: ls [path]\n");
271                return;
272        }
273
274        dir = opendir(path);
275        while ((file = readdir(dir)) != NULL)
276        {
277                printf(" %s\n", file->d_name);
278        }
279        closedir(dir);
280}
281
282static void cmd_mkdir(int argc, char **argv)
283{
284        char *path;
285
286        if (argc != 2) {
287                printf("usage: mkdir pathname\n");
288                return;
289        }
290
291        path = argv[1];
292
293        if (mkdir(path, 0700) == -1) {
294                printf("error: cannot create directory %s\n", path);
295        }
296}
297
298static void cmd_mv(int argc, char **argv)
299{
300#if 0
301        if (argc < 3)
302        {
303                printf("  usage : %s src_pathname dst_pathname\n", argv[0]);
304                return;
305        }
306
307        int ret = giet_fat_rename(argv[1], argv[2]);
308        if (ret < 0)
309        {
310                printf("error : cannot move %s to %s / err = %d\n", argv[1], argv[2], ret );
311        }
312#endif
313}
314
315static void cmd_rm(int argc, char **argv)
316{
317        char *path;
318
319        if (argc != 2) {
320                printf("usage: rm pathname\n");
321                return;
322        }
323
324        path = argv[1];
325
326        if (remove(path) == -1) {
327                printf("error: cannot remove %s\n", path);
328        }
329}
330
331static void cmd_rmdir(int argc, char **argv)
332{
333        cmd_rm(argc, argv);
334}
335
336static void cmd_cd(int argc, char **argv)
337{
338        char *path;
339
340        if (argc != 2) {
341                printf("usage: cd pathname\n");
342                return;
343        }
344
345        path = argv[1];
346
347        if (chdir(path) == -1) {
348                printf("error: cannot cd to %s\n", path);
349        }
350}
351
352static void cmd_pwd(int argc, char **argv)
353{
354        char buf[1024];
355
356        if (argc != 1) {
357                printf("usage: pwd\n");
358                return;
359        }
360
361        if (getcwd(buf, 1024) == NULL) {
362                printf("error: unable to get current directory\n");
363        } else {
364                printf("%s\n", buf);
365        }
366}
367
368struct command_t cmd[] =
369{
370        { "cat",    "display file content",                cmd_cat },
371        { "cd",     "change current directory",            cmd_cd },
372        { "cp",     "replicate a file in file system",     cmd_cp },
373        { "exec",   "start an application",                cmd_exec },
374        { "help",   "list available commands",             cmd_help },
375        { "kill",   "kill an application (all threads)",   cmd_kill },
376        { "log",    "list registered commands",            cmd_log },
377        { "ls",     "list directory entries",              cmd_ls },
378        { "mkdir",  "create a new directory",              cmd_mkdir },
379        { "mv",     "move a file in file system",          cmd_mv },
380        { "pwd",    "print current working directory",     cmd_pwd },
381        { "rm",     "remove a file from file system",      cmd_rm },
382        { "rmdir",  "remove a directory from file system", cmd_rmdir },
383        { NULL,     NULL,                                                                  NULL }
384};
385
386///////////////////////////////////////////////////////////////////
387// This function analyses one command (with arguments)
388///////////////////////////////////////////////////////////////////
389static void parse(char *buf)
390{
391        int argc = 0;
392        char *argv[MAX_ARGS];
393        int i;
394        int len = strlen(buf);
395
396        // build argc/argv
397        for (i = 0; i < len; i++) {
398                if (buf[i] == ' ') {
399                        buf[i] = '\0';
400                } else if (i == 0 || buf[i - 1] == '\0') {
401                        if (argc < MAX_ARGS) {
402                                argv[argc] = &buf[i];
403                                argc++;
404                        }
405                }
406        }
407
408        if (argc > 0) {
409                int found = 0;
410
411                argv[argc] = NULL;
412
413                // try to match typed command with built-ins
414                for (i = 0; cmd[i].name; i++) {
415                        if (strcmp(argv[0], cmd[i].name) == 0) {
416                                cmd[i].fn(argc, argv);
417                                found = 1;
418                                break;
419                        }
420                }
421
422                if (!found) {
423                        printf("\n  undefined command %s\n", argv[0]);
424                }
425        }
426}
427
428int main(int argc, char *argv[])
429{
430        char c;                                                   // read character
431        char buf[MAX_SIZE];                               // buffer for one command
432        size_t count = 0;                                         // pointer in buf
433        size_t i, j;                                              // indexes for loops
434        __unused(argc);
435        __unused(argv);
436
437        enum fsm_states {
438                NORMAL,
439                ESCAPE,
440                BRAKET,
441        };
442
443        memset(&log_entries, 0, sizeof(log_entries));
444        ptw = 0;
445        ptr = 0;
446
447        printf( "~~~ shell ~~~\n\n" );
448
449        // command buffer initialisation
450        memset(buf, 0x20, sizeof(buf));
451        count = 0;
452
453        // display first prompt
454        printf("# ");
455
456        // This lexical analyser writes one command line in the buf buffer.
457        // It is implemented as a 3 states FSM to handle the following sequences:
458        // - ESC [ A : up arrow
459        // - ESC [ B : down arrow
460        // - ESC [ C : right arrow
461        // - ESC [ D : left arrow
462        // The thee states have the following semantic:
463        // - NORMAL : no (ESC) character has been found
464        // - ESCAPE : the character (ESC) has been found
465        // - BRAKET : the wo characters (ESC,[) have been found
466        unsigned int state = NORMAL;
467
468        while (1)
469        {
470                /*
471                 * Certainly not a good idea to use getchar. The escape sequences won't
472                 * be handled correctly.
473                 */
474                c = getchar();
475
476                switch (state)
477                {
478                        case NORMAL:
479                        {
480                                if ((c == '\b') || (c == 0x7F))  // backspace => remove one character
481                                {
482                                        if (count > 0) {
483                                                printf("\b \b");
484                                                count--;
485                                        }
486                                }
487                                else if (c == '\n')      // new line => call parser to execute command
488                                {
489                                        if (count > 0)
490                                        {
491                                                // complete command
492                                                buf[count] = '\0';
493
494                                                // register command in log arrays
495                                                strcpy(log_entries[ptw].buf, buf);
496                                                log_entries[ptw].count = count;
497                                                ptw = (ptw + 1) % LOG_DEPTH;
498                                                ptr = ptw;
499
500                                                // execute command
501                                                printf("\n");
502                                                parse((char *)&buf);
503
504                                                // reinitialise buffer and display prompt
505                                                for ( i = 0 ; i < sizeof(buf) ; i++ ) buf[i] = 0x20;
506                                                count = 0;
507                                                printf("# ");
508                                        }
509                                }
510                                else if (c == '\t')     // tabulation => do nothing
511                                {
512                                }
513                                else if (c == 0x1B)     // ESC => start an escape sequence
514                                {
515                                        state = ESCAPE;
516                                }
517                                else if (c == 0x03)     // ^C  => cancel current command
518                                {
519                                        for (i = 0; i < count; i++) printf("\b \b");
520                                        for (i = 0; i < sizeof(buf); i++) buf[i] = 0x20;
521                                        count = 0;
522                                }
523                                else                                     // register character in command buffer
524                                {
525                                        if (count < sizeof(buf) - 1)
526                                        {
527                                                printf("%c", c);
528                                                buf[count] = c;
529                                                count++;
530                                        }
531                                }
532                                break;
533                        }
534                        case ESCAPE:
535                        {
536                                if (c == '[')           //  valid sequence => continue
537                                {
538                                        state = BRAKET;
539                                }
540                                else                               // invalid sequence => do nothing
541                                {
542                                        state = NORMAL;
543                                }
544                                break;
545                        }
546                        case BRAKET:
547                        {
548                                if (c == 'D')   // valid  LEFT sequence => move buf pointer left
549                                {
550                                        if (count > 0)
551                                        {
552                                                printf("\b");
553                                                count--;
554                                        }
555
556                                        // get next user char
557                                        state = NORMAL;
558                                }
559                                else if (c == 'C')   // valid  RIGHT sequence => move buf pointer right
560                                {
561                                        if (count < sizeof(buf) - 1)
562                                        {
563                                                printf("%c", buf[count]);
564                                                count++;
565                                        }
566
567                                        // get next user char
568                                        state = NORMAL;
569                                }
570                                else if (c == 'A')   // valid  UP sequence => move log pointer backward
571                                {
572                                        // cancel current command
573                                        for (i = 0; i < count; i++) printf("\b \b");
574                                        count = 0;
575
576                                        // copy log command into buf
577                                        ptr = (ptr - 1) % LOG_DEPTH;
578                                        strcpy(buf, log_entries[ptr].buf);
579                                        count = log_entries[ptr].count;
580
581                                        // display log command
582                                        printf("%s", buf);
583
584                                        // get next user char
585                                        state = NORMAL;
586                                }
587                                else if (c == 'B')   // valid  DOWN sequence => move log pointer forward
588                                {
589                                        // cancel current command
590                                        for (i = 0 ; i < count; i++) printf("\b \b");
591                                        count = 0;
592
593                                        // copy log command into buf
594                                        ptr = (ptr + 1) % LOG_DEPTH;
595                                        strcpy(buf, log_entries[ptr].buf);
596                                        count = log_entries[ptr].count;
597
598                                        // display log command
599                                        printf("%s", buf);
600
601                                        // get next user char
602                                        state = NORMAL;
603                                }
604                                else                               // other character => do nothing
605                                {
606                                        // get next user char
607                                        state = NORMAL;
608                                }
609                                break;
610                        }
611                }
612        }
613}
614
Note: See TracBrowser for help on using the repository browser.