A few things:
[simpleshell.git] / ss.c
blob6fc96ad8c5873ccec00423b04f3f305cfce67caa
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 <pwd.h>
30 #include <stdio.h>
31 #include <readline/readline.h>
32 #include <readline/history.h>
33 #include <signal.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/time.h>
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <unistd.h>
41 #define IS_LAST_BUILTIN(builtin) ((builtin).name == NULL && \
42 (builtin).command == NULL && \
43 (builtin).help == NULL)
45 static const struct SH_BUILTIN builtins[] = {
46 {"cd", cd_cmd, "Usage: cd <DIR>\nChanges the current working directory to DIR.\n"},
47 {"exit", exit_cmd, "Usage: exit\nExits this shell.\n"},
48 {"help", help_cmd, "Usage: help [BUILTIN]\nShows how to use BUILTIN.\nIf no BUILTIN was given, shows a list of builtins.\n"},
49 {"kill", kill_cmd, "Usage: kill <PID> ...\nSends the SIGTERM signal to all the PIDs.\n"},
50 {"status", status_cmd, "Usage: status\nPrints the exit status of the last command.\n"},
51 {":", true_cmd, "Usage: :\nDoes absolutely nothing.\n"},
52 {NULL, NULL, NULL}
55 static int last_status = 0;
57 /**
58 * Counts the number of tokens in a string.
59 * Note that tokens are delimited by spaces, unless the space is escaped.
60 * @param input the string to check for tokens
61 * @return the number of tokens in the input string
63 int count_tokens(char *input) {
64 int tokens = 0;
65 if (input[0] == '\0') {
66 return tokens;
67 } else {
68 tokens++;
69 while (*input == ' ') ++input;
70 for (; *input != '\0'; ++input) {
71 if (*input == '\\' && *(input + 1) == ' ') {
72 ++input;
73 } else if (*input == ' ') {
74 tokens++;
75 for (; *(input + 1) == ' '; ++input);
80 return tokens;
83 /**
84 * Splits the input into an array of words.
85 * Also implements a simple mechanism for escaping spaces. You basically use a
86 * single backslash to escape a space (or another backslash). In the event that
87 * a backslash is followed by a character other than a space or a backslash, it
88 * is interpreted as-is.\n
89 * Note that the allocated memory for the returned array
90 * may exceed what is required in order to store all words.
91 * This does not in any way pose a problem.\n
92 * Every element in the returned array (before the terminating NULL)
93 * should be freed, along with the array itself.
94 * @param input the string to be split into words
95 * @return an array of words
97 char **split_input(char *input) {
98 int words = count_tokens(input);
99 int i = 0, j = 0;
100 char *single_word = malloc(sizeof(char) * strlen(input) + 1);
101 if (single_word == NULL) {
102 fprintf(stderr, "Could not allocate memory: %s\nExiting...\n", strerror(errno));
103 exit(EXIT_FAILURE);
105 char **split = malloc(sizeof(char *) * (words+1));
106 if (split == NULL) {
107 fprintf(stderr, "Could not allocate memory: %s\nExiting...\n", strerror(errno));
108 exit(EXIT_FAILURE);
111 /* skip first spaces */
112 while (*input == ' ') ++input;
113 for (; *input != '\0'; input++) {
114 if (*input == '\\' && (
115 *(input + 1) == ' ' ||
116 *(input + 1) == '\\'
117 )) {
118 single_word[i++] = *(++input);
119 } else if (*input == ' ') {
120 single_word[i] = '\0';
121 split[j++] = strdup(single_word);
122 for (; *(input+1) == ' '; ++input);
123 i = 0;
124 } else {
125 single_word[i++] = *input;
129 if (i != 0) {
130 single_word[i] = '\0';
131 split[j++] = strdup(single_word);
134 split[j] = NULL;
136 free(single_word);
138 return split;
141 int help_cmd(char **argv) {
142 int i;
143 if (argv[0] == NULL) {
144 printf("Available builtins:\n");
145 for (i = 0; !IS_LAST_BUILTIN(builtins[i]); i++) {
146 printf(" %s\n", builtins[i].name);
148 printf("Type 'help <BUILTIN>' for more information on BUILTIN.\n");
149 } else if (argv[1] != NULL) {
150 fprintf(stderr, "The 'help' builtin takes at most one argument. See 'help help'.\n");
151 } else {
152 for (i = 0; !IS_LAST_BUILTIN(builtins[i]); i++) {
153 if (!strcmp(argv[0], builtins[i].name)) {
154 printf("%s", builtins[i].help);
155 break;
158 if (IS_LAST_BUILTIN(builtins[i])) {
159 printf("Unknown builtin '%s'. Type 'help' to see a list of builtins.\n", argv[0]);
162 return 0;
166 * Exits the shell.
167 * @return non-zero on success (no arguments were passed), zero otherwise.
169 int exit_cmd(char **argv) {
170 if (argv[0] != NULL) {
171 fprintf(stderr, "The 'exit' builtin does not take any arguments.\n");
172 return 0;
173 } else {
174 return 1;
179 * Sends a signal to a list of PIDs.
180 * In this implementation, the SIGTERM (15) signal is sent. If an error
181 * occurs whilst trying to send said signal, a message is printed on stderr.
182 * Inversely, when sending the signal succeeds, a message is printed on stdout
183 * to notify the user.
184 * @return always zero
186 int kill_cmd(char **argv) {
187 int i;
188 pid_t pid;
189 if (argv[0] == NULL) {
190 printf("The 'kill' builtin takes at least one argument (a PID).\n");
191 } else {
192 for (i = 0; argv[i] != NULL; i++) {
193 if (sscanf(argv[i], "%d", &pid)) {
194 if (kill(pid, SIGTERM)) {
195 fprintf(stderr, "Couldn't kill the process with PID %d: %s\n", pid, strerror(errno));
196 } else {
197 fprintf(stderr, "Killed (or sent signal %d) to PID %d.\n", SIGTERM, pid);
199 } else {
200 printf("%s is not a valid PID. Skipping.\n", argv[i]);
205 return 0;
208 int true_cmd(char **argv) {
209 return 0;
212 int status_cmd(char **argv) {
213 if (argv[0] != NULL) {
214 fprintf(stderr, "The 'status' builtin doesn't take any arguments.");
215 } else {
216 printf("%d\n", last_status);
219 return 0;
223 * Changes the current working directory.
224 * Note that the actual changing of the current working directory may fail,
225 * in which case an error is printed on stderr. This command takes a single
226 * argument: the directory to change to.
227 * @return always zero
229 int cd_cmd(char **argv) {
230 char *todir;
231 if (argv[0] == NULL) {
232 struct passwd *passwd = getpwuid(getuid());
233 if (passwd == NULL) {
234 fprintf(stderr, "Could not get the current user's home directory: %s\n", strerror(errno));
235 } else {
236 todir = passwd->pw_dir;
238 } else if (argv[1] == NULL) {
239 todir = argv[0];
240 } else {
241 fprintf(stderr, "The 'cd' builtin takes at most one argument (the directory to move to).\n");
242 return 0;
245 if (chdir(todir)) {
246 fprintf(stderr, "Could not change the current working directory to '%s': %s.\n", todir, strerror(errno));
249 return 0;
253 * Handles a line of input.
254 * This function handles a line of input, which may
255 * result in two things:
256 * - a builtin command is executed
257 * - the process forks and launches the application
258 * specified in the input string
259 * @param input a string representing a single line of input
260 * @return zero to indicate the main program should continue,
261 * non-zero otherwise.
263 int handle_input(char *input) {
264 int return_value = 0;
265 pid_t pid;
266 char **argv;
267 int wait_status;
268 int i;
269 if (input == NULL) {
270 printf("exit\n");
271 return_value = 1;
272 } else if (input[0] == '\0') {
273 /* do nothing */
274 free(input);
275 } else {
276 argv = split_input(input);
277 if (history_length == 0 || history_search_pos(input, 1, 0) == -1) {
278 add_history(input);
280 free(input);
281 for (i = 0; !IS_LAST_BUILTIN(builtins[i]); i++) {
282 if (!strcmp(argv[0], builtins[i].name)) {
283 last_status = return_value = builtins[i].command(&argv[1]);
284 break;
287 if (IS_LAST_BUILTIN(builtins[i])) {
288 pid = fork();
289 if (pid == 0) {
290 if (execvp(argv[0], argv)) {
291 fprintf(stderr, "Could not execute '%s': %s\n", argv[0], strerror(errno));
293 return_value = 1;
294 } else {
295 while (!waitpid(pid, &wait_status, 0) && WIFEXITED(wait_status));
296 last_status = WEXITSTATUS(wait_status);
299 for (i = 0; argv[i] != NULL; i++) {
300 free(argv[i]);
302 free(argv);
305 return return_value;
309 * Reads a line of input from stdin.
310 * This function is mainly here for modularity,
311 * meaning that if I need or want to change the way
312 * I get input, I would do it here once.
313 * @param prompt the prompt string to display
314 * @return a string containing a line of input
316 char *get_input(char *prompt) {
317 char *line;
318 char *cwd;
319 char *local_prompt;
321 if (prompt == NULL) {
322 cwd = get_current_dir_name();
323 if (cwd == NULL) {
324 cwd = "?";
327 struct passwd *passwd = getpwuid(getuid());
328 char *username = "?";
329 if (passwd != NULL) {
330 username = passwd->pw_name;
333 local_prompt = malloc(sizeof(char) * (strlen(cwd) + strlen(username) + 4));
334 if (local_prompt == NULL) {
335 fprintf(stderr, "Could not allocate memory: %s\nExiting...\n", strerror(errno));
336 exit(EXIT_FAILURE);
338 local_prompt[0] = '\0';
339 strcat(local_prompt, username);
340 strcat(local_prompt, "@");
341 if (passwd != NULL && strstr(cwd, passwd->pw_dir) != NULL) {
342 strcat(local_prompt, "~");
343 strcat(local_prompt, cwd+strlen(passwd->pw_dir));
344 } else {
345 strcat(local_prompt, cwd);
347 strcat(local_prompt, "$ ");
350 line = readline(local_prompt);
352 if (prompt == NULL) {
353 if (strcmp(cwd, "?")) {
354 free(cwd);
356 free(local_prompt);
359 return line;
363 * Prints help.
364 * @param the name of the current program.
366 void print_usage(char *progname) {
367 printf("Usage: %s [OPTIONS]\n", progname);
368 printf(" Options:\n");
369 printf(" -p <prompt> The prompt to be displayed\n");
370 printf(" -h Display this help\n");
373 void handle_interrupt(int signal) {
374 printf("\n");
375 rl_on_new_line();
376 rl_replace_line("", 1);
377 reset_prog_mode();
378 rl_redisplay();
381 int main(int argc, char **argv) {
382 char *prompt = NULL;
383 char *input;
384 char option;
385 while ((option = getopt(argc, argv, ":hp:")) != -1) {
386 switch(option) {
387 case 'h':
388 print_usage(argv[0]);
389 exit(EXIT_SUCCESS);
390 break;
391 case 'p':
392 prompt = optarg;
393 break;
394 case ':':
395 printf("Missing argument for option '%c'.\n", optopt);
396 print_usage(argv[0]);
397 exit(EXIT_FAILURE);
398 break;
399 case '?':
400 printf("Unknown option '%c'.\n", optopt);
401 print_usage(argv[0]);
402 exit(EXIT_FAILURE);
403 default:
404 print_usage(argv[0]);
405 exit(EXIT_SUCCESS);
406 break;
410 sigset_t sigset;
411 struct sigaction action;
412 if (sigemptyset(&sigset)) {
413 fprintf(stderr, "Could not install a signal handler for SIGINT.\n");
414 } else {
415 action.sa_handler = handle_interrupt;
416 action.sa_mask = sigset;
417 action.sa_flags = 0;
418 sigaction(SIGINT, &action, NULL);
420 using_history();
421 rl_catch_signals = 0;
423 do {
424 input = get_input(prompt);
425 } while (!handle_input(input));
427 return 0;