/* * Authors: Clément Guérin and Alain Greiner (2015) */ #include #include #include #include #include #include #include #include #include #include #include #define MAX_SIZE (128) // max number of characters in one command #define LOG_DEPTH (128) // max number of commands in log #define MAX_ARGS (32) // max number of arguments in a command #define FIFO_SIZE (1024) // FIFO depth for recursive ls #define __unused(a) (void)a extern char **environ; //////////////////////////////////////////////////////////////////////////////// // Global Variables //////////////////////////////////////////////////////////////////////////////// struct { char buf[MAX_SIZE]; size_t count; } log_entries[LOG_DEPTH]; size_t ptw; // write pointer in log size_t ptr; // read pointer in log struct command_t { char *name; char *desc; void (*fn)(int, char **); }; struct command_t cmd[]; //////////////////////////////////////////////////////////////////////////////// // Shell Commands //////////////////////////////////////////////////////////////////////////////// static void cmd_cat(int argc, char **argv) { char *path, *buf = NULL; struct stat st; size_t size; int fd; if (argc != 2) { printf("usage: cat pathname\n"); return; } path = argv[1]; /* open the file */ fd = open(path, O_RDONLY, 0); if (fd < 0) { printf("error: cannot open %s\n", path); goto exit; } /* get file size */ if (stat(path, &st) == -1) { printf("error: cannot stat %s\n", path); goto exit; } if (S_ISDIR(st.st_mode)) { printf("error: %s is a directory\n", path); goto exit; } size = st.st_size; /* mmap the file */ buf = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); if (buf == NULL || buf == (char *)-1) { printf("error: cannot map %s\n", path); goto exit; } /* set terminating '0' XXX */ buf[size-1] = 0; /* display the file content */ printf("%s", buf); exit: if (buf != NULL) munmap(buf, size); if (fd >= 0) close(fd); } static void cmd_cp(int argc, char **argv) { int src_fd = -1, dst_fd = -1; char *srcpath, *dstpath; struct stat st; size_t size, i; char buf[1024]; if (argc != 3) { printf("usage: cp src_pathname dst_pathname\n"); return; } srcpath = argv[1]; dstpath = argv[2]; /* open the src file */ src_fd = open(srcpath, O_RDONLY, 0); if (src_fd < 0) { printf("error: cannot open %s / err = %d\n", srcpath, errno); goto exit; } /* get file size */ if (stat(srcpath, &st) == -1) { printf("error: cannot stat %s\n", srcpath); goto exit; } if (S_ISDIR(st.st_mode)) { printf("error: %s is a directory\n", srcpath); goto exit; } size = st.st_size; /* open the dst file */ dst_fd = open(dstpath, O_CREAT|O_TRUNC|O_RDWR, 0); if (dst_fd < 0) { printf("error: cannot open %s / err = %d\n", dstpath, errno); goto exit; } if (stat(dstpath, &st) == -1) { printf("error: cannot stat %s\n", dstpath); goto exit; } if (S_ISDIR(st.st_mode)) { printf("error: %s is a directory\n", dstpath); goto exit; } i = 0; while (i < size) { size_t rlen = (size - i < 1024 ? size - i : 1024); size_t wlen; ssize_t ret; /* read the source */ ret = read(src_fd, buf, rlen); if (ret == -1) { printf("error: cannot read from file %s\n", srcpath); goto exit; } rlen = (size_t)ret; /* write to the destination */ ret = write(dst_fd, buf, rlen); if (ret == -1) { printf("error: cannot write to file %s\n", dstpath); goto exit; } wlen = (size_t)ret; /* check */ if (wlen != rlen) { printf("error: cannot write on device\n"); goto exit; } i += rlen; } exit: if (src_fd >= 0) close(src_fd); if (dst_fd >= 0) close(dst_fd); } static void cmd_exec(int argc, char **argv) { pid_t pid, wpid; int status; char *path; if (argc != 2) { printf("usage: %s prog\n", argv[0]); return; } path = argv[1]; argv++; pid = fork(); if (pid == 0) { /* child process */ if (execve(path, argv, environ) == -1) { printf("error: unable to execve %s\n", path); } exit(EXIT_FAILURE); } else if (pid < 0) { /* error forking */ printf("error: unable to fork\n"); } else { /* parent process */ do { wpid = waitpid(pid, &status, WUNTRACED); } while (!WIFEXITED(status) && !WIFSIGNALED(status)); } } static void cmd_help(int argc, char **argv) { size_t i; __unused(argc); __unused(argv); printf("available commands:\n"); for (i = 0; cmd[i].name; i++) { printf("\t%s\t : %s\n", cmd[i].name , cmd[i].desc); } } static void cmd_kill(int argc, char **argv) { pid_t pid; if (argc != 2) { printf("usage: %s pid\n", argv[0]); return; } pid = (pid_t)atoi(argv[1]); if (kill(pid, SIGKILL) == -1) { printf("error: unable to kill %d\n", (int)pid); } } static void cmd_log(int argc, char **argv) { size_t i; __unused(argc); __unused(argv); printf("--- registered commands ---\n"); for (i = 0; i < LOG_DEPTH; i++) { printf(" - %zu\t: %s\n", i, &log_entries[i].buf); } } static void cmd_ls(int argc, char **argv) { struct dirent *file; char *path; DIR *dir; if (argc == 1) { path = "."; } else if (argc == 2) { path = argv[1]; } else { printf("usage: ls [path]\n"); return; } dir = opendir(path); while ((file = readdir(dir)) != NULL) { printf(" %s\n", file->d_name); } closedir(dir); } static void cmd_mkdir(int argc, char **argv) { char *path; if (argc != 2) { printf("usage: mkdir pathname\n"); return; } path = argv[1]; if (mkdir(path, 0700) == -1) { printf("error: cannot create directory %s\n", path); } } static void cmd_mv(int argc, char **argv) { #if 0 if (argc < 3) { printf(" usage : %s src_pathname dst_pathname\n", argv[0]); return; } int ret = giet_fat_rename(argv[1], argv[2]); if (ret < 0) { printf("error : cannot move %s to %s / err = %d\n", argv[1], argv[2], ret ); } #endif } static void cmd_rm(int argc, char **argv) { char *path; if (argc != 2) { printf("usage: rm pathname\n"); return; } path = argv[1]; if (remove(path) == -1) { printf("error: cannot remove %s\n", path); } } static void cmd_rmdir(int argc, char **argv) { cmd_rm(argc, argv); } static void cmd_cd(int argc, char **argv) { char *path; if (argc != 2) { printf("usage: cd pathname\n"); return; } path = argv[1]; if (chdir(path) == -1) { printf("error: cannot cd to %s\n", path); } } static void cmd_pwd(int argc, char **argv) { char buf[1024]; if (argc != 1) { printf("usage: pwd\n"); return; } if (getcwd(buf, 1024) == NULL) { printf("error: unable to get current directory\n"); } else { printf("%s\n", buf); } } struct command_t cmd[] = { { "cat", "display file content", cmd_cat }, { "cd", "change current directory", cmd_cd }, { "cp", "replicate a file in file system", cmd_cp }, { "exec", "start an application", cmd_exec }, { "help", "list available commands", cmd_help }, { "kill", "kill an application (all threads)", cmd_kill }, { "log", "list registered commands", cmd_log }, { "ls", "list directory entries", cmd_ls }, { "mkdir", "create a new directory", cmd_mkdir }, { "mv", "move a file in file system", cmd_mv }, { "pwd", "print current working directory", cmd_pwd }, { "rm", "remove a file from file system", cmd_rm }, { "rmdir", "remove a directory from file system", cmd_rmdir }, { NULL, NULL, NULL } }; /////////////////////////////////////////////////////////////////// // This function analyses one command (with arguments) /////////////////////////////////////////////////////////////////// static void parse(char *buf) { int argc = 0; char *argv[MAX_ARGS]; int i; int len = strlen(buf); // build argc/argv for (i = 0; i < len; i++) { if (buf[i] == ' ') { buf[i] = '\0'; } else if (i == 0 || buf[i - 1] == '\0') { if (argc < MAX_ARGS) { argv[argc] = &buf[i]; argc++; } } } if (argc > 0) { int found = 0; argv[argc] = NULL; // try to match typed command with built-ins for (i = 0; cmd[i].name; i++) { if (strcmp(argv[0], cmd[i].name) == 0) { cmd[i].fn(argc, argv); found = 1; break; } } if (!found) { printf("\n undefined command %s\n", argv[0]); } } } int main(int argc, char *argv[]) { char c; // read character char buf[MAX_SIZE]; // buffer for one command size_t count = 0; // pointer in buf size_t i, j; // indexes for loops __unused(argc); __unused(argv); enum fsm_states { NORMAL, ESCAPE, BRAKET, }; memset(&log_entries, 0, sizeof(log_entries)); ptw = 0; ptr = 0; printf( "~~~ shell ~~~\n\n" ); // command buffer initialisation memset(buf, 0x20, sizeof(buf)); count = 0; // display first prompt printf("# "); // This lexical analyser writes one command line in the buf buffer. // It is implemented as a 3 states FSM to handle the following sequences: // - ESC [ A : up arrow // - ESC [ B : down arrow // - ESC [ C : right arrow // - ESC [ D : left arrow // The thee states have the following semantic: // - NORMAL : no (ESC) character has been found // - ESCAPE : the character (ESC) has been found // - BRAKET : the wo characters (ESC,[) have been found unsigned int state = NORMAL; while (1) { /* * Certainly not a good idea to use getchar. The escape sequences won't * be handled correctly. */ c = getchar(); switch (state) { case NORMAL: { if ((c == '\b') || (c == 0x7F)) // backspace => remove one character { if (count > 0) { printf("\b \b"); count--; } } else if (c == '\n') // new line => call parser to execute command { if (count > 0) { // complete command buf[count] = '\0'; // register command in log arrays strcpy(log_entries[ptw].buf, buf); log_entries[ptw].count = count; ptw = (ptw + 1) % LOG_DEPTH; ptr = ptw; // execute command printf("\n"); parse((char *)&buf); // reinitialise buffer and display prompt for ( i = 0 ; i < sizeof(buf) ; i++ ) buf[i] = 0x20; count = 0; printf("# "); } } else if (c == '\t') // tabulation => do nothing { } else if (c == 0x1B) // ESC => start an escape sequence { state = ESCAPE; } else if (c == 0x03) // ^C => cancel current command { for (i = 0; i < count; i++) printf("\b \b"); for (i = 0; i < sizeof(buf); i++) buf[i] = 0x20; count = 0; } else // register character in command buffer { if (count < sizeof(buf) - 1) { printf("%c", c); buf[count] = c; count++; } } break; } case ESCAPE: { if (c == '[') // valid sequence => continue { state = BRAKET; } else // invalid sequence => do nothing { state = NORMAL; } break; } case BRAKET: { if (c == 'D') // valid LEFT sequence => move buf pointer left { if (count > 0) { printf("\b"); count--; } // get next user char state = NORMAL; } else if (c == 'C') // valid RIGHT sequence => move buf pointer right { if (count < sizeof(buf) - 1) { printf("%c", buf[count]); count++; } // get next user char state = NORMAL; } else if (c == 'A') // valid UP sequence => move log pointer backward { // cancel current command for (i = 0; i < count; i++) printf("\b \b"); count = 0; // copy log command into buf ptr = (ptr - 1) % LOG_DEPTH; strcpy(buf, log_entries[ptr].buf); count = log_entries[ptr].count; // display log command printf("%s", buf); // get next user char state = NORMAL; } else if (c == 'B') // valid DOWN sequence => move log pointer forward { // cancel current command for (i = 0 ; i < count; i++) printf("\b \b"); count = 0; // copy log command into buf ptr = (ptr + 1) % LOG_DEPTH; strcpy(buf, log_entries[ptr].buf); count = log_entries[ptr].count; // display log command printf("%s", buf); // get next user char state = NORMAL; } else // other character => do nothing { // get next user char state = NORMAL; } break; } } } }