1 /******************************************************************************
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
23 *****************************************************************************/
32 #include <readline/readline.h>
33 #include <readline/history.h>
38 #include <sys/types.h>
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)
49 * Returns true if last_status indicates that the shell is done reading input.
51 #define IS_DONE ((last_status >> 8) & 1)
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"},
68 static int last_status
= 0;
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
) {
78 while (*input
== ' ') ++input
;
79 if (*input
== '\0' || *input
== '#') {
83 for (; *input
!= '\0'; ++input
) {
84 if (*input
== '\\' && *(input
+ 1) == ' ') {
86 } else if (*input
== ' ') {
87 for (; *(input
+ 1) == ' '; ++input
);
88 if (*(input
+ 1) != '\0' && *(input
+ 1) != '#') {
91 } else if (*input
== '#') {
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
);
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
));
122 char **split
= malloc(sizeof(char *) * (tokens
+1));
124 fprintf(stderr
, "Could not allocate memory: %s\nExiting...\n", strerror(errno
));
128 /* skip first spaces */
129 while (*input
== ' ') ++input
;
130 for (; *input
!= '\0'; input
++) {
131 if (*input
== '\\' && (
132 *(input
+ 1) == ' ' ||
133 *(input
+ 1) == '\\' ||
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
);
142 } else if (*input
== '#') {
145 single_word
[i
++] = *input
;
150 single_word
[i
] = '\0';
151 split
[j
++] = strdup(single_word
);
162 * Prints help about this shell or one of its builtins.
163 * @return always zero
165 int help_cmd(char **argv
) {
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");
176 for (i
= 0; !IS_LAST_BUILTIN(builtins
[i
]); i
++) {
177 if (!strcmp(argv
[0], builtins
[i
].name
)) {
178 printf("%s", builtins
[i
].help
);
182 if (IS_LAST_BUILTIN(builtins
[i
])) {
183 printf("Unknown builtin '%s'. Type 'help' to see a list of builtins.\n", argv
[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
) {
199 if (argv
[0] == NULL
) {
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]);
206 return (3 << 8) | (exit_status
& 0xFF);
209 fprintf(stderr
, "The 'exit' builtin takes at most one argument. See 'help exit' for details.\n");
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
) {
226 if (argv
[0] == NULL
) {
227 printf("The 'kill' builtin takes at least one argument (a PID).\n");
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
));
234 fprintf(stderr
, "Killed (or sent signal %d) to PID %d.\n", SIGTERM
, pid
);
237 printf("%s is not a valid PID. Skipping.\n", argv
[i
]);
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
) {
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.");
265 printf("%d\n", last_status
);
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
) {
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
));
285 todir
= passwd
->pw_dir
;
287 } else if (argv
[1] == NULL
) {
290 fprintf(stderr
, "The 'cd' builtin takes at most one argument (the directory to move to).\n");
295 fprintf(stderr
, "Could not change the current working directory to '%s': %s.\n", todir
, strerror(errno
));
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
) {
320 last_status
= (1 << 8);
322 last_status
= (1 << 8) | (last_status
& 0xFF);
324 } else if (input
[0] == '\0') {
328 argv
= split_input(input
);
330 if (history_length
== 0 || history_search_pos(input
, 1, 0) == -1) {
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]);
340 if (IS_LAST_BUILTIN(builtins
[i
])) {
343 if (execvp(argv
[0], argv
)) {
344 fprintf(stderr
, "Could not execute '%s': %s\n", argv
[0], strerror(errno
));
346 last_status
= (1 << 8);
348 while (!waitpid(pid
, &wait_status
, 0) && WIFEXITED(wait_status
));
349 last_status
= WEXITSTATUS(wait_status
);
352 for (i
= 0; argv
[i
] != NULL
; i
++) {
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.
366 int source_cmd(char **argv
) {
369 if (argv
[0] == NULL
|| argv
[1] != NULL
) {
370 fprintf(stderr
, "The '.' builtin takes exactly one argument. See 'help .' for details.\n");
372 input
= fopen(argv
[0], "r");
374 fprintf(stderr
, "Could not open '%s' for sourcing: %s\n", argv
[0], strerror(errno
));
377 line
= mystring_new(50, 50);
378 if (mystring_getline(line
, input
) != -1) {
379 handle_input(line
->str
, 0);
383 handle_input(NULL
, 0);
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
) {
410 if (prompt
== NULL
) {
411 cwd
= get_current_dir_name();
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
));
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
));
434 strcat(local_prompt
, cwd
);
436 strcat(local_prompt
, "$ ");
438 local_prompt
= prompt
;
441 line
= readline(local_prompt
);
443 if (prompt
== NULL
) {
444 if (strcmp(cwd
, "?")) {
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");
466 * Discards the current readline input and redisplays the prompt.
468 void handle_interrupt(int signal
) {
471 rl_replace_line("", 1);
476 int main(int argc
, char **argv
) {
480 while ((option
= getopt(argc
, argv
, ":hp:")) != -1) {
483 print_usage(argv
[0]);
490 printf("Missing argument for option '%c'.\n", optopt
);
491 print_usage(argv
[0]);
495 printf("Unknown option '%c'.\n", optopt
);
496 print_usage(argv
[0]);
499 print_usage(argv
[0]);
506 struct sigaction action
;
507 if (sigemptyset(&sigset
)) {
508 fprintf(stderr
, "Could not install a signal handler for SIGINT.\n");
510 action
.sa_handler
= handle_interrupt
;
511 action
.sa_mask
= sigset
;
513 sigaction(SIGINT
, &action
, NULL
);
516 rl_catch_signals
= 0;
519 input
= get_input(prompt
);
520 handle_input(input
, 1);
523 return last_status
& 0xFF;