Changed every call to sscanf to include the %n directive.
[simpleshell.git] / ss.c
blob91b9393c3bf256c2c5005c4b7009a4e634c3e797
1 /******************************************************************************
2 * The MIT License
4 * Copyright (c) 2010 Stefan 'psYchotic' Zwanenburg
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 *****************************************************************************/
25 #define _GNU_SOURCE
26 #include "builtins.h"
27 #include <curses.h>
28 #include <errno.h>
29 #include "mystring.h"
30 #include <pwd.h>
31 #include <stdio.h>
32 #include <readline/readline.h>
33 #include <readline/history.h>
34 #include <signal.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/time.h>
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 #include <unistd.h>
42 /**
43 * Returns true if the argument is an empty SH_BUILTIN struct.
45 #define IS_LAST_BUILTIN(builtin) ((builtin).name == NULL && \
46 (builtin).command == NULL && \
47 (builtin).help == NULL)
48 /**
49 * Returns true if last_status indicates that the shell is done reading input.
51 #define IS_DONE ((last_status >> 8) & 1)
52 /**
53 * Returns true if last_status indicates that the shell should exit.
55 #define IS_EXIT ((last_status >> 9) & 1)
57 static const struct SH_BUILTIN builtins[] = {
58 {"cd", cd_cmd, "Usage: cd <DIR>\nChanges the current working directory to DIR.\n"},
59 {"exit", exit_cmd, "Usage: exit\nExits this shell.\n"},
60 {"help", help_cmd, "Usage: help [BUILTIN]\nShows how to use BUILTIN.\nIf no BUILTIN was given, shows a list of builtins.\n"},
61 {"kill", kill_cmd, "Usage: kill <PID> ...\nSends the SIGTERM signal to all the PIDs.\n"},
62 {"status", status_cmd, "Usage: status\nPrints the exit status of the last command.\n"},
63 {":", true_cmd, "Usage: :\nDoes absolutely nothing.\n"},
64 {".", source_cmd, "Usage: . <FILENAME>\nReads a file and executes whatever is inside it.\n"},
65 {NULL, NULL, NULL}
68 static int last_status = 0;
70 /**
71 * Counts the number of tokens in a string.
72 * Note that tokens are delimited by spaces, unless the space is escaped.
73 * @param input the string to check for tokens
74 * @return the number of tokens in the input string
76 int count_tokens(char *input) {
77 int tokens = 0;
78 while (*input == ' ') ++input;
79 if (*input == '\0' || *input == '#') {
80 return tokens;
81 } else {
82 tokens++;
83 for (; *input != '\0'; ++input) {
84 if (*input == '\\' && *(input + 1) == ' ') {
85 ++input;
86 } else if (*input == ' ') {
87 for (; *(input + 1) == ' '; ++input);
88 if (*(input + 1) != '\0' && *(input + 1) != '#') {
89 tokens++;
91 } else if (*input == '#') {
92 break;
97 return tokens;
101 * Splits the input into an array tokens.
102 * Also implements a simple mechanism for escaping spaces. You basically use a
103 * single backslash to escape a space (or another backslash). In the event that
104 * a backslash is followed by a character other than a space or a backslash, it
105 * is interpreted as-is.\n
106 * Note that the allocated memory for the returned array
107 * may exceed what is required in order to store all words.
108 * This does not in any way pose a problem.\n
109 * Every element in the returned array (before the terminating NULL)
110 * should be freed, along with the array itself.
111 * @param input the string to be split into words
112 * @return an array of words
114 char **split_input(char *input) {
115 int tokens = count_tokens(input);
116 int i = 0, j = 0;
117 char *single_word = malloc(sizeof(char) * strlen(input) + 1);
118 if (single_word == NULL) {
119 fprintf(stderr, "Could not allocate memory: %s\nExiting...\n", strerror(errno));
120 exit(EXIT_FAILURE);
122 char **split = malloc(sizeof(char *) * (tokens+1));
123 if (split == NULL) {
124 fprintf(stderr, "Could not allocate memory: %s\nExiting...\n", strerror(errno));
125 exit(EXIT_FAILURE);
128 /* skip first spaces */
129 while (*input == ' ') ++input;
130 for (; *input != '\0'; input++) {
131 if (*input == '\\' && (
132 *(input + 1) == ' ' ||
133 *(input + 1) == '\\' ||
134 *(input + 1) == '#'
135 )) {
136 single_word[i++] = *(++input);
137 } else if (*input == ' ') {
138 single_word[i] = '\0';
139 split[j++] = strdup(single_word);
140 for (; *(input+1) == ' '; ++input);
141 i = 0;
142 } else if (*input == '#') {
143 break;
144 } else {
145 single_word[i++] = *input;
149 if (i != 0) {
150 single_word[i] = '\0';
151 split[j++] = strdup(single_word);
154 split[j] = NULL;
156 free(single_word);
158 return split;
162 * Prints help about this shell or one of its builtins.
163 * @return always zero
165 int help_cmd(char **argv) {
166 int i;
167 if (argv[0] == NULL) {
168 printf("Available builtins:\n");
169 for (i = 0; !IS_LAST_BUILTIN(builtins[i]); i++) {
170 printf(" %s\n", builtins[i].name);
172 printf("Type 'help <BUILTIN>' for more information on BUILTIN.\n");
173 } else if (argv[1] != NULL) {
174 fprintf(stderr, "The 'help' builtin takes at most one argument. See 'help help'.\n");
175 } else {
176 for (i = 0; !IS_LAST_BUILTIN(builtins[i]); i++) {
177 if (!strcmp(argv[0], builtins[i].name)) {
178 printf("%s", builtins[i].help);
179 break;
182 if (IS_LAST_BUILTIN(builtins[i])) {
183 printf("Unknown builtin '%s'. Type 'help' to see a list of builtins.\n", argv[0]);
186 return 0;
190 * Exits the shell, unless an incorrect argument or too many arguments specified.
191 * @return zero if no argument was specified,
192 * the argument & 0xFF (see man 3 exit for details) if specified,
193 * one if there were more than one arguments or if the one argument could not
194 * be parsed, in which two cases no actual exiting occurs.
196 int exit_cmd(char **argv) {
197 int exit_status;
198 int read_chars;
199 if (argv[0] == NULL) {
200 return (3 << 8);
201 } else if (argv[0] != NULL && argv[1] == NULL) {
202 if (!sscanf(argv[0], "%d%n", &exit_status, &read_chars) || read_chars != strlen(argv[0])) {
203 fprintf(stderr, "Could not parse the argument ('%s') as an integer.\n", argv[0]);
204 return 1;
205 } else {
206 return (3 << 8) | (exit_status & 0xFF);
208 } else {
209 fprintf(stderr, "The 'exit' builtin takes at most one argument. See 'help exit' for details.\n");
210 return 1;
215 * Sends a signal to a list of PIDs.
216 * In this implementation, the SIGTERM (15) signal is sent. If an error
217 * occurs whilst trying to send said signal, a message is printed on stderr.
218 * Inversely, when sending the signal succeeds, a message is printed on stdout
219 * to notify the user.
220 * @return always zero
222 int kill_cmd(char **argv) {
223 int i;
224 int read_chars;
225 pid_t pid;
226 if (argv[0] == NULL) {
227 printf("The 'kill' builtin takes at least one argument (a PID).\n");
228 } else {
229 for (i = 0; argv[i] != NULL; i++) {
230 if (sscanf(argv[i], "%d%n", &pid, &read_chars) && read_chars == strlen(argv[i])) {
231 if (kill(pid, SIGTERM)) {
232 fprintf(stderr, "Couldn't kill the process with PID %d: %s\n", pid, strerror(errno));
233 } else {
234 fprintf(stderr, "Killed (or sent signal %d) to PID %d.\n", SIGTERM, pid);
236 } else {
237 printf("%s is not a valid PID. Skipping.\n", argv[i]);
242 return 0;
246 * Does nothing, and always succeeds, even if it has arguments.
247 * Note that the arguments *may* be executed if the shell implements some
248 * sort of expansion. Use '#' to create comments.
249 * @return always zero
251 int true_cmd(char **argv) {
252 return 0;
256 * Prints the exit status of the last command.
257 * @return nonzero if an argument was specified (in which case it doesn't
258 * actually print the last exit status), zero otherwise.
260 int status_cmd(char **argv) {
261 if (argv[0] != NULL) {
262 fprintf(stderr, "The 'status' builtin doesn't take any arguments.");
263 return 1;
264 } else {
265 printf("%d\n", last_status);
268 return 0;
272 * Changes the current working directory.
273 * Note that the actual changing of the current working directory may fail,
274 * in which case an error is printed on stderr. This command takes a single
275 * argument: the directory to change to.
276 * @return always zero
278 int cd_cmd(char **argv) {
279 char *todir;
280 if (argv[0] == NULL) {
281 struct passwd *passwd = getpwuid(getuid());
282 if (passwd == NULL) {
283 fprintf(stderr, "Could not get the current user's home directory: %s\n", strerror(errno));
284 } else {
285 todir = passwd->pw_dir;
287 } else if (argv[1] == NULL) {
288 todir = argv[0];
289 } else {
290 fprintf(stderr, "The 'cd' builtin takes at most one argument (the directory to move to).\n");
291 return 0;
294 if (chdir(todir)) {
295 fprintf(stderr, "Could not change the current working directory to '%s': %s.\n", todir, strerror(errno));
298 return 0;
302 * Handles a line of input.
303 * This function handles a line of input, which may
304 * result in two things:
305 * - a builtin command is executed
306 * - the process forks and launches the application
307 * specified in the input string
308 * @param input a string representing a single line of input
309 * @param interactive if nonzero, prints "exit" followed by a newline if input
310 * equals NULL, otherwise doesn't print anything.
312 void handle_input(char *input, int interactive) {
313 pid_t pid;
314 char **argv;
315 int wait_status;
316 int i;
317 if (input == NULL) {
318 if (interactive) {
319 printf("exit\n");
320 last_status = (1 << 8);
321 } else {
322 last_status = (1 << 8) | (last_status & 0xFF);
324 } else if (input[0] == '\0') {
325 /* do nothing */
326 free(input);
327 } else {
328 argv = split_input(input);
329 if (*argv != NULL) {
330 if (history_length == 0 || history_search_pos(input, 1, 0) == -1) {
331 add_history(input);
333 free(input);
334 for (i = 0; !IS_LAST_BUILTIN(builtins[i]); i++) {
335 if (!strcmp(argv[0], builtins[i].name)) {
336 last_status = builtins[i].command(&argv[1]);
337 break;
340 if (IS_LAST_BUILTIN(builtins[i])) {
341 pid = fork();
342 if (pid == 0) {
343 if (execvp(argv[0], argv)) {
344 fprintf(stderr, "Could not execute '%s': %s\n", argv[0], strerror(errno));
346 last_status = (1 << 8);
347 } else {
348 while (!waitpid(pid, &wait_status, 0) && WIFEXITED(wait_status));
349 last_status = WEXITSTATUS(wait_status);
352 for (i = 0; argv[i] != NULL; i++) {
353 free(argv[i]);
356 free(argv);
361 * Executes the contents of the argument file in the current shell.
362 * Note that the 'exit' builtin within these files does not currently
363 * actually exit the current shell, it merely stops the parsing of the file.
364 * @return
366 int source_cmd(char **argv) {
367 FILE *input;
368 MString line;
369 if (argv[0] == NULL || argv[1] != NULL) {
370 fprintf(stderr, "The '.' builtin takes exactly one argument. See 'help .' for details.\n");
371 } else {
372 input = fopen(argv[0], "r");
373 if (input == NULL) {
374 fprintf(stderr, "Could not open '%s' for sourcing: %s\n", argv[0], strerror(errno));
375 } else {
376 do {
377 line = mystring_new(50, 50);
378 if (mystring_getline(line, input) != -1) {
379 handle_input(line->str, 0);
380 free(line);
381 } else {
382 mystring_free(line);
383 handle_input(NULL, 0);
385 } while (!IS_DONE);
386 fclose(input);
390 if (IS_EXIT) {
391 return last_status;
392 } else {
393 return last_status & 0xFF;
398 * Reads a line of input from stdin.
399 * This function is mainly here for modularity,
400 * meaning that if I need or want to change the way
401 * I get input, I would do it here once.
402 * @param prompt the prompt string to display
403 * @return a string containing a line of input
405 char *get_input(char *prompt) {
406 char *line;
407 char *cwd;
408 char *local_prompt;
410 if (prompt == NULL) {
411 cwd = get_current_dir_name();
412 if (cwd == NULL) {
413 cwd = "?";
416 struct passwd *passwd = getpwuid(getuid());
417 char *username = "?";
418 if (passwd != NULL) {
419 username = passwd->pw_name;
422 local_prompt = malloc(sizeof(char) * (strlen(cwd) + strlen(username) + 4));
423 if (local_prompt == NULL) {
424 fprintf(stderr, "Could not allocate memory: %s\nExiting...\n", strerror(errno));
425 exit(EXIT_FAILURE);
427 local_prompt[0] = '\0';
428 strcat(local_prompt, username);
429 strcat(local_prompt, "@");
430 if (passwd != NULL && strstr(cwd, passwd->pw_dir) != NULL) {
431 strcat(local_prompt, "~");
432 strcat(local_prompt, cwd+strlen(passwd->pw_dir));
433 } else {
434 strcat(local_prompt, cwd);
436 strcat(local_prompt, "$ ");
437 } else {
438 local_prompt = prompt;
441 line = readline(local_prompt);
443 if (prompt == NULL) {
444 if (strcmp(cwd, "?")) {
445 free(cwd);
447 free(local_prompt);
450 return line;
454 * Prints help.
455 * @param progname the name of the current program.
457 void print_usage(char *progname) {
458 printf("Usage: %s [OPTIONS]\n", progname);
459 printf(" Options:\n");
460 printf(" -p <prompt> The prompt to be displayed\n");
461 printf(" -h Display this help\n");
465 * Handle SIGINT.
466 * Discards the current readline input and redisplays the prompt.
468 void handle_interrupt(int signal) {
469 printf("\n");
470 rl_on_new_line();
471 rl_replace_line("", 1);
472 reset_prog_mode();
473 rl_redisplay();
476 int main(int argc, char **argv) {
477 char *prompt = NULL;
478 char *input;
479 char option;
480 while ((option = getopt(argc, argv, ":hp:")) != -1) {
481 switch(option) {
482 case 'h':
483 print_usage(argv[0]);
484 exit(EXIT_SUCCESS);
485 break;
486 case 'p':
487 prompt = optarg;
488 break;
489 case ':':
490 printf("Missing argument for option '%c'.\n", optopt);
491 print_usage(argv[0]);
492 exit(EXIT_FAILURE);
493 break;
494 case '?':
495 printf("Unknown option '%c'.\n", optopt);
496 print_usage(argv[0]);
497 exit(EXIT_FAILURE);
498 default:
499 print_usage(argv[0]);
500 exit(EXIT_SUCCESS);
501 break;
505 sigset_t sigset;
506 struct sigaction action;
507 if (sigemptyset(&sigset)) {
508 fprintf(stderr, "Could not install a signal handler for SIGINT.\n");
509 } else {
510 action.sa_handler = handle_interrupt;
511 action.sa_mask = sigset;
512 action.sa_flags = 0;
513 sigaction(SIGINT, &action, NULL);
515 using_history();
516 rl_catch_signals = 0;
518 do {
519 input = get_input(prompt);
520 handle_input(input, 1);
521 } while (!IS_DONE);
523 return last_status & 0xFF;