gsh.c (7570B)
1 /* Simple shell written in C. 2 * Compile with: cc -std=c99 -pedantic -Wall -O0 -o gsh gsh.c -lreadline */ 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <unistd.h> 8 #include <sys/types.h> 9 #include <err.h> 10 #include <errno.h> 11 #include <signal.h> 12 #include <wordexp.h> 13 #include <sys/wait.h> 14 #include <pwd.h> 15 16 /* Avoid some NetBSD obscure build time warning */ 17 #if defined(__NetBSD__) 18 # define _FUNCTION_DEF 19 # define USE_VARARGS 20 # define PREFER_STDARG 21 #endif 22 23 #include <readline/readline.h> 24 #include <readline/history.h> 25 26 #ifdef PATH_MAX 27 static int pathmax = PATH_MAX; 28 #else 29 static int pathmax = 0; 30 #endif 31 #define PATH_MAX_GUESS 1024 32 33 #define SH_NAME "gsh" 34 #define SH_VERSION "0.01" 35 36 typedef struct { 37 const char *name; 38 void (*func)(int argc, char *argv[]); 39 const char *desc; 40 const char *args; 41 } keyword; 42 43 /* globals */ 44 typedef void builtin(int, char *[]); 45 builtin 46 do_exit, 47 do_echo, 48 do_putenv, 49 do_setenv, 50 do_unsetenv, 51 do_getenv, 52 do_help, 53 do_cd 54 ; 55 static const keyword tblkeys[] = { 56 /* Command, Function, Description, Arguments */ 57 { "exit", do_exit, "Exit from the shell", "[exit code]" }, 58 { "echo", do_echo, "Show a message", "[message]" }, 59 { "putenv", do_putenv, "Set the enviroment given variable(s)", "<variable>=<value> [...]" }, 60 { "setenv", do_setenv, "Set an environment variable", "<variable> <value>" }, 61 { "unsetenv", do_unsetenv, "Unset the environment given variable(s)", "<variable> [...]" }, 62 { "getenv", do_getenv, "Print the value of the given environment variable(s)", "<variable> [...]" }, 63 { "cd", do_cd, "Change the current working directory", "[pathname | -]" }, 64 { "help", do_help, "Show this help", "[command]" }, 65 { NULL, NULL, NULL, NULL } 66 }; 67 68 /* function declarations */ 69 int sh_init(void); 70 int run_cmd(int argc, char *argv[]); 71 int run_key(int argc, char *argv[]); 72 void remove_comment(char *p); 73 void hdr_sigint(int sig); 74 void cmdhelp(const char *cmd, int descr); 75 void sigint_handler(int signum); 76 void execute_command(char *command); 77 char *strip_spaces(char *string); 78 char *path_alloc(size_t *size); 79 80 /* function implementations */ 81 void 82 remove_comment(char *p) { 83 register int i = 0; 84 while(p[i] && p[i] != '#') 85 ++i; 86 if(p[i]) 87 p[i] = '\0'; 88 } 89 90 void 91 cmdhelp(const char *cmd, int descr) { 92 int i; 93 for(i = 0; tblkeys[i].name; i++) { 94 if(!strncmp(tblkeys[i].name, cmd, strlen(tblkeys[i].name))) { 95 if(descr) 96 printf("%s\n", tblkeys[i].desc); 97 printf("Usage: %s %s\n", tblkeys[i].name, tblkeys[i].args); 98 break; 99 } 100 } 101 if(!tblkeys[i].name) 102 warnx("%s: unknown command", cmd); 103 } 104 105 char * 106 strip_spaces(char *string) { 107 char *s, *e; 108 for(s = string; *s == ' ' && *s; s++) 109 ; 110 if(!*s) 111 return s; 112 for(e = s + strlen(s) - 1; *e == ' '; e--) 113 ; 114 *(e + 1) = '\0'; 115 return s; 116 } 117 118 char * 119 path_alloc(size_t *size) { 120 char *ptr; 121 if(!pathmax) { 122 errno = 0; 123 if((pathmax = pathconf("/", _PC_PATH_MAX)) < 0) { 124 if(!errno) 125 pathmax = PATH_MAX_GUESS; 126 else 127 warnx("pathconf caused error while reading _PC_PATH_MAX"); 128 } 129 else 130 ++pathmax; 131 } 132 if((ptr = malloc(pathmax + 1)) == NULL) 133 warnx("cannot allocate the pathname"); 134 if(size != NULL) 135 *size = pathmax + 1; 136 return(ptr); 137 } 138 void 139 do_exit(int argc, char *argv[]) { 140 if(argc > 2) { 141 cmdhelp(argv[0], 0); 142 return; 143 } 144 exit((argv && argv[1] ? atoi(argv[1]) : EXIT_SUCCESS)); 145 } 146 147 void 148 do_echo(int argc, char *argv[]) { 149 int i = 0; 150 while(argv[++i]) { 151 printf("%s", argv[i]); 152 if(i == argc) 153 break; 154 printf(" "); 155 } 156 printf("\n"); 157 } 158 159 void 160 do_putenv(int argc, char *argv[]) { 161 size_t envsz; 162 int i; 163 char *envstr; 164 if(argc == 1) { 165 cmdhelp(argv[0], 0); 166 return; 167 } 168 for(i = 0; i < argc; i++) { 169 envsz = strlen(argv[i]) + 1; 170 if((envstr = malloc(envsz)) == NULL) 171 return; 172 strncpy(envstr, argv[i], envsz); 173 if(putenv(envstr)) 174 free(envstr); 175 } 176 } 177 178 void 179 do_setenv(int argc, char *argv[]) { 180 if(argc != 3) { 181 cmdhelp(argv[0], 0); 182 return; 183 } 184 setenv(argv[1], argv[2], 1); 185 } 186 187 void 188 do_unsetenv(int argc, char *argv[]) { 189 if(argc == 1) { 190 cmdhelp(argv[0], 0); 191 return; 192 } 193 while( --argc && ! unsetenv(argv[argc]) ) 194 ; 195 } 196 197 void 198 do_getenv(int argc, char *argv[]) { 199 int i; 200 if(argc == 1) { 201 cmdhelp(argv[0], 0); 202 return; 203 } 204 for(i = 1; i < argc; i++) 205 printf("%s\n", getenv(argv[i])); 206 } 207 208 void 209 do_cd(int argc, char *argv[]) { 210 struct passwd *pwd; 211 size_t tplen; 212 char *tp, *path; 213 if(argc > 2) { 214 cmdhelp(argv[0], 0); 215 return; 216 } 217 if(!(path = argv[1])) { 218 if(!(path = getenv("HOME"))) { 219 if((pwd = getpwuid(getuid())) != NULL) 220 path = pwd->pw_dir; 221 222 else { 223 /* This should never happen */ 224 warnx("%s: cannot get the user home directory", argv[0]); 225 return; 226 } 227 } 228 } 229 if(*path == '-' && !path[1]) { 230 if(!(path = getenv("OLDPWD"))) 231 return; 232 printf("%s\n", path); /* TODO: An option should toggle this call */ 233 } 234 tp = path_alloc(&tplen); 235 getcwd(tp, tplen); 236 if(chdir(path)) { 237 warn("cd: %s", path); 238 return; 239 } 240 setenv("OLDPWD", tp, 1); 241 } 242 243 void 244 do_help(int argc, char *argv[]) { 245 int i; 246 if(argc >= 2) { 247 cmdhelp(argv[1], argc == 2); 248 return; 249 } 250 printf("\n%s v.%s\n\n", SH_NAME, SH_VERSION); 251 for(i = 0; tblkeys[i].name; i++) 252 printf(" %-9s%s\n", tblkeys[i].name, tblkeys[i].desc); 253 printf("\n"); 254 } 255 256 int 257 run_key(int argc, char *argv[]) { 258 register int i; 259 for(i = 0; tblkeys[i].name; i++) { 260 if(!strncmp(tblkeys[i].name, argv[0], strlen(tblkeys[i].name))) { 261 tblkeys[i].func(argc, argv); 262 return 0; 263 } 264 } 265 return 1; 266 } 267 268 int 269 run_cmd(int argc, char *argv[]) { 270 register pid_t pid; 271 if((pid = fork()) == -1) 272 return -1; 273 else if(pid == 0) { 274 execvp(argv[0], argv); 275 276 /* Exit status 277 * 126: The program is not executable 278 * 127: The program was not found 279 * 1: any other error */ 280 if(errno == ENOENT) 281 errx(127, "%s: command not found", argv[0]); 282 else 283 err((errno == EACCES ? 126 : 1), "%s", argv[0]); 284 } 285 else { 286 while(waitpid(pid, NULL, 0) == -1) { 287 if(errno != EINTR) 288 return -1; 289 } 290 } 291 return 0; 292 } 293 294 /* XXX clean things up here */ 295 void 296 sigint_handler(int signum) { 297 char *p; 298 if(!*(p = strip_spaces(rl_line_buffer))) 299 return; 300 add_history(p); /* Store it anyway */ 301 *rl_line_buffer = '\0'; /* Clear the buffer */ 302 rl_point =rl_end = 0; /* Reset the offsets */ 303 rl_redisplay(); /* Update the line */ 304 printf("(interrupt)\n"); /* Show a message */ 305 rl_initialize(); /* (Re)Initialize the state */ 306 rl_redisplay(); /* Update the line */ 307 } 308 309 int 310 sh_init() { 311 signal(SIGINT, sigint_handler); 312 signal(SIGTSTP, SIG_IGN); 313 signal(SIGQUIT, SIG_IGN); 314 315 putenv((char *)"PS1=$ "); 316 putenv((char *)"IFS= \n\t"); 317 318 /* Set the default PATH, if unset (LFS 1.2) */ 319 if(!getenv("PATH")) 320 putenv((char *)"PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"); 321 322 /* XXX startup file? */ 323 324 return 0; 325 } 326 327 void 328 execute_command(char *command) { 329 wordexp_t wordv; 330 register int wordc, wret; 331 if((wret = wordexp(command, &wordv, 0))) { 332 switch(wret) { 333 case WRDE_BADCHAR: 334 warnx("illegal character"); 335 break; 336 case WRDE_NOSPACE: 337 warnx("out of memory"); 338 break; 339 case WRDE_SYNTAX: 340 warnx("syntax error"); 341 break; 342 } 343 344 return; 345 } 346 if(!wordv.we_wordv[0]) { 347 wordfree(&wordv); 348 return; 349 } 350 for(wordc = 0; wordv.we_wordv[wordc]; wordc++) 351 ; 352 if(run_key(wordc, wordv.we_wordv)) { 353 if(run_cmd(wordc, wordv.we_wordv)) 354 warn("run_cmd"); 355 } 356 wordfree(&wordv); 357 } 358 359 int 360 main(void) { 361 char *buf, *p; 362 if(sh_init()) 363 return 1; 364 while(1) { 365 if((buf = readline(getenv("PS1"))) == NULL) 366 break; 367 remove_comment(p); 368 if(*(p = strip_spaces(buf))) { 369 add_history(p); 370 execute_command(p); 371 } 372 free(buf); 373 } 374 printf("\n"); 375 return 0; 376 }