repos/msh

simple shell implementation
Commits Files Refs README LICENSE
msh.c (308 lines)
   1 #include <stdio.h>
   2 #include <stdlib.h>
   3 #include <stdbool.h>
   4 #include <string.h>
   5 #include <unistd.h>
   6 #include <sys/types.h>
   7 #include <sys/wait.h>
   8 #include <pwd.h>
   9 
  10 #include <ctype.h>
  11 
  12 #include "tui.h"
  13 
  14 #define TAB_SIZE    4
  15 
  16 #define READLINE_BUFFER_INIT_SIZE   128
  17 #define TOKENS_BUFFER_INIT_SIZE     8
  18 #define TOKENS_DELIM                " |"
  19 
  20 static bool run = true;
  21 static size_t buffer_alloc;
  22 static size_t tokens_alloc;
  23 static size_t builtin_index;
  24 
  25 static const char *msh_builtins_name[] = {
  26     "cd"
  27 };
  28 
  29 #define TOTAL_BUILTINS    sizeof(msh_builtins_name)/sizeof(msh_builtins_name[0])
  30 
  31 void buffer_print_slice(const char *buf, size_t from, size_t to) {
  32     for(size_t i = from; i < to; i++)
  33         printf("%c", buf[i]);
  34 }
  35 
  36 void buffer_clear(char *buf) {
  37     size_t len = strlen(buf);
  38     for (size_t i = 0; i < len; i++)
  39         buf[i] = '\0';
  40 }
  41 
  42 char *buffer_read_line(char *s) {
  43     int c;
  44     char *aux;
  45     size_t used_size;
  46 
  47     used_size = 0;
  48     while (1) {
  49         c = getchar();
  50         if((c == '\n') || (c == EOF))
  51             break;
  52 
  53         if(used_size == (buffer_alloc - 1)) {
  54             if(!(aux = realloc(s, buffer_alloc += buffer_alloc))) {
  55                 perror("msh");
  56                 free(s);
  57                 return NULL;
  58             }
  59             s = aux;
  60         }
  61         s[used_size++] = c;
  62     }
  63 
  64     s[used_size] = '\0';
  65     if (c == EOF) run = false;
  66     return s;
  67 }
  68 
  69 char **buffer_split(char *b, char **t) {
  70     char *p, **aux;
  71     size_t tokens_count;
  72 
  73     tokens_count = 0;
  74     for(p = b; (p = strtok(p, TOKENS_DELIM)); p = NULL) {
  75         if((tokens_count + 1) == tokens_alloc) {
  76             if(!(aux = realloc(t, sizeof(char*) * (tokens_alloc *= 2)))) {
  77                 perror("msh");
  78                 for(size_t i = 0; i < tokens_count; i++)
  79                     free(t[i]);
  80                 free(t);
  81                 return NULL;
  82             }
  83             t = aux;
  84         }
  85         t[tokens_count++] = p;
  86     }
  87     t[tokens_count] = NULL;
  88     return t;
  89 }
  90 
  91 char *editor_read_line(char *s) {
  92     int c;
  93     char *aux;
  94     size_t line_len, cursor_pos;
  95 
  96     line_len = cursor_pos = 0;
  97     while(1) {
  98         c = getchar();
  99 
 100         /* Ctrl-D */
 101         if((c == 4) || (c == EOF)) {
 102             run = false;
 103             buffer_clear(s);
 104             break;
 105         }
 106 
 107         if(c == 3) {
 108             buffer_clear(s);
 109             break;
 110         }
 111 
 112         if((c == '\r') || (c == '\n'))
 113             break;
 114 
 115         /* ESC */
 116         if(c == 27) {
 117             getchar(); /* skip '[' */
 118             switch (getchar()) {
 119                 case 'A': /* up */
 120                     break;
 121                 case 'B': /* down */
 122                     break;
 123                 case 'D': /* left */
 124                     if(cursor_pos > 0) {
 125                         printf("\033[1D");
 126                         cursor_pos--;
 127                     }
 128                     break;
 129                 case 'C': /* right */
 130                     if(cursor_pos < line_len) {
 131                         printf("\033[1C");
 132                         cursor_pos++;
 133                     }
 134                     break;
 135                 default:
 136                     break;
 137             }
 138             continue;
 139         }
 140 
 141         /* Backspace */
 142         if(c == 0x7f) {
 143             if (line_len > 0 && cursor_pos > 0) {
 144                 line_len -= 1;
 145                 cursor_pos -= 1;
 146                 printf("\b \b");
 147                 if (cursor_pos < line_len) {
 148                     memmove(s + cursor_pos, s + cursor_pos + 1, line_len - cursor_pos + 1);
 149                     buffer_print_slice(s, cursor_pos, line_len);
 150                     putchar(' ');
 151                     printf("\033[%ldD", line_len - cursor_pos + 1);
 152                 }
 153             }
 154             continue;
 155         }
 156 
 157         /* Delete */
 158         if(c == 0x7e) {
 159             if ((line_len > 0) && (cursor_pos < line_len)) {
 160                 line_len -= 1;
 161                 memmove(s + cursor_pos, s + cursor_pos + 1, line_len - cursor_pos);
 162                 buffer_print_slice(s, cursor_pos, line_len);
 163                 putchar(' ');
 164                 printf("\033[%ldD", line_len - cursor_pos + 1);
 165             }
 166             continue;
 167         }
 168 
 169 
 170         if(c == '\t') {
 171             for(size_t i = 0; i < TAB_SIZE; i++)
 172                 putchar(' ');
 173 
 174             line_len += TAB_SIZE;
 175             continue;
 176         }
 177 
 178         if(line_len == (buffer_alloc - 1)) {
 179             if(!(aux = realloc(s, buffer_alloc += buffer_alloc))) {
 180                 perror("msh");
 181                 free(s);
 182                 return NULL;
 183             }
 184             s = aux;
 185         }
 186 
 187         /* only print printable caracters */
 188         if (isprint(c)) {
 189             if (cursor_pos < line_len) {
 190                 memmove(s + cursor_pos + 1, s + cursor_pos, line_len - cursor_pos);
 191                 putchar(c);
 192                 s[cursor_pos++] = c;
 193                 line_len += 1;
 194 
 195                 buffer_print_slice(s, cursor_pos, line_len);
 196 
 197                 /* move left the amount buffer_print_slice moved the cursor */
 198                 printf("\033[%ldD", line_len - cursor_pos);
 199             } else {
 200                 putchar(c);
 201                 s[line_len++] = c;
 202                 cursor_pos++;
 203             }
 204         }
 205     }
 206     printf("\r\n");
 207     s[line_len] = '\0';
 208     return s;
 209 }
 210 
 211 bool is_builtin(const char *name) {
 212     size_t i;
 213     for(i = builtin_index = 0; i < TOTAL_BUILTINS; i++)
 214         if(!strcmp(name, msh_builtins_name[i]))
 215             return true;
 216 
 217     return false;
 218 }
 219 
 220 void msh_cd(const char **path) {
 221     if(path[1] == NULL) {
 222         /* go ot user home */
 223         struct passwd *pw = getpwuid(getuid());
 224         const char *user_home_dir = pw->pw_dir;
 225         if(chdir(user_home_dir) < 0) {
 226             perror("cd");
 227         }
 228     }
 229     else {
 230         if(chdir(path[1]) < 0)
 231             fprintf(stderr,
 232                     "%s: no such file or directory: %s\n",
 233                     "cd", path[0]);
 234     }
 235 }
 236 
 237 static void (*msh_builtin_handler[])(const char **argv) = {
 238     &msh_cd,
 239 };
 240 
 241 void msh_execute_builtin(char **argv) {
 242     msh_builtin_handler[builtin_index]((const char **)argv);
 243 }
 244 
 245 void msh_execute(char **argv) {
 246     int status;
 247     pid_t pid;
 248 
 249     pid = fork();
 250     if (pid == 0) {
 251         /* child process */
 252         if(execvp(argv[0], argv) < 0)
 253             fprintf(stderr, "%s: command not found\n", argv[0]);
 254 
 255         exit(EXIT_FAILURE);
 256     }
 257     else if (pid < 0) {
 258         perror("msh: could not fork");
 259     }
 260     else {
 261         do {
 262             waitpid(pid, &status, WUNTRACED);
 263         } while (!WIFEXITED(status) && !WIFSIGNALED(status));
 264     }
 265 }
 266 
 267 void msh_loop(void) {
 268     char *buffer, **tokens;
 269 
 270     buffer_alloc = READLINE_BUFFER_INIT_SIZE;
 271     if(!(buffer = calloc(buffer_alloc, sizeof(char))))
 272         exit(EXIT_FAILURE);
 273 
 274     tokens_alloc = TOKENS_BUFFER_INIT_SIZE;
 275     if(!(tokens = malloc(sizeof(char*) * tokens_alloc))) {
 276         free(buffer);
 277         exit(EXIT_FAILURE);
 278     }
 279 
 280     while (run) {
 281         tui_set_input_mode();
 282 
 283         /* make cusor blinking vertical bar */
 284         printf("\r\033[5 q$ ");
 285         buffer = editor_read_line(buffer);
 286 
 287         tui_reset_input_mode();
 288 
 289         tokens = buffer_split(buffer, tokens);
 290 
 291         /* skip execute if buffer is empty */
 292         if(strlen(buffer) > 0) {
 293             if(is_builtin(tokens[0]))
 294                 msh_execute_builtin(tokens);
 295             else
 296                 msh_execute(tokens);
 297         }
 298 
 299         buffer_clear(buffer);
 300     }
 301     free(tokens);
 302     free(buffer);
 303 }
 304 
 305 int main (void) {
 306     msh_loop();
 307     return 0;
 308 }