training

Code I wrote during training
git clone git://git.bitsmanent.org/training
Log | Files | Refs | README

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 }