Added a simple escaping mechanism.
[simpleshell.git] / ss.c
blobb52f15495b8dc73d6aff4529060e3b4fdcb45f78
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 <errno.h>
27 #include <pwd.h>
28 #include <stdio.h>
29 #include <readline/readline.h>
30 #include <readline/history.h>
31 #include <signal.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <sys/wait.h>
37 #include <unistd.h>
39 /**
40 * Counts the number of spaces in a string.
41 * @param input the string to check for spaces
42 * @return the number of spaces in the input string
44 int count_spaces(char *input) {
45 int spaces = 0;
46 int i;
47 for (i = 0; input[i] != '\0'; i++) {
48 if (input[i] == ' ')
49 spaces++;
52 return spaces;
55 /**
56 * Splits the input into an array of words.
57 * Also implements a simple mechanism for escaping spaces. You basically use a
58 * single backslash to escape a space (or another backslash). In the event that
59 * a backslash is followed by a character other than a space or a backslash, it
60 * is interpreted as-is.\n
61 * Note that the allocated memory for the returned array
62 * may exceed what is required in order to store all words.
63 * This does not in any way pose a problem.\n
64 * Every element in the returned array (before the terminating NULL)
65 * should be freed, along with the array itself.
66 * @param input the string to be split into words
67 * @return an array of words
69 char **split_input(char *input) {
70 int words = count_spaces(input) + 1;
71 int i, j = 0;
72 char *single_word = malloc(sizeof(char) * strlen(input) + 1);
73 char *offset = input;
74 unsigned short int escaped = 0;
75 unsigned short int previous_was_space = 0;
76 char **split = malloc(sizeof(char *) * (words+1));
77 single_word[0] = '\0';
79 for (i = 0; input[i] != '\0'; i++) {
80 if (escaped && input[i] == '\\') {
81 escaped = 0;
82 strcat(single_word, "\\");
83 offset = input + i + 1;
84 previous_was_space = 0;
85 } else if (escaped && input[i] == ' ') {
86 escaped = 0;
87 strcat(single_word, " ");
88 offset = input + i + 1;
89 previous_was_space = 0;
90 } else if (escaped) {
91 escaped = 0;
92 previous_was_space = 0;
93 } else if (input[i] == '\\') {
94 strncat(single_word, offset, input - offset + i);
95 offset = input + i;
96 escaped = 1;
97 previous_was_space = 0;
98 } else if (input[i] == ' ') {
99 if (!previous_was_space) {
100 strncat(single_word, offset, input - offset + i);
101 split[j++] = strdup(single_word);
102 single_word[0] = '\0';
103 offset = input + i + 1;
104 previous_was_space = 1;
105 } else {
106 offset++;
108 } else {
109 previous_was_space = 0;
113 if (offset - input != strlen(input) || single_word[0] != '\0') {
114 strcat(single_word, offset);
115 split[j++] = strdup(single_word);
118 split[j] = NULL;
120 free(single_word);
122 return split;
126 * Exits the shell.
127 * @return non-zero on success (no arguments were passed), zero otherwise.
129 int exit_cmd(char **argv) {
130 if (argv[0] != NULL) {
131 fprintf(stderr, "The 'exit' builtin does not take any arguments.\n");
132 return 0;
133 } else {
134 return 1;
139 * Sends a signal to a list of PIDs.
140 * In this implementation, the SIGTERM (15) signal is sent. If an error
141 * occurs whilst trying to send said signal, a message is printed on stderr.
142 * Inversely, when sending the signal succeeds, a message is printed on stdout
143 * to notify the user.
144 * @return always zero
146 int kill_cmd(char **argv) {
147 int i;
148 pid_t pid;
149 if (argv[0] == NULL) {
150 printf("The 'kill' builtin takes at least one argument (a PID).\n");
151 } else {
152 for (i = 0; argv[i] != NULL; i++) {
153 if (sscanf(argv[i], "%d", &pid)) {
154 if (kill(pid, SIGTERM)) {
155 fprintf(stderr, "Couldn't kill the process with PID %d: %s\n", pid, strerror(errno));
156 } else {
157 fprintf(stderr, "Killed (or sent signal %d) to PID %d.\n", SIGTERM, pid);
159 } else {
160 printf("%s is not a valid PID. Skipping.\n", argv[i]);
165 return 0;
169 * Changes the current working directory.
170 * Note that the actual changing of the current working directory may fail,
171 * in which case an error is printed on stderr. This command takes a single
172 * argument: the directory to change to.
173 * @return always zero
175 int cd_cmd(char **argv) {
176 char *todir;
177 if (argv[0] == NULL) {
178 struct passwd *passwd = getpwuid(getuid());
179 if (passwd == NULL) {
180 fprintf(stderr, "Could not get the current user's home directory: %s\n", strerror(errno));
181 } else {
182 todir = passwd->pw_dir;
184 } else if (argv[1] == NULL) {
185 todir = argv[0];
186 } else {
187 fprintf(stderr, "The 'cd' builtin takes at most one argument (the directory to move to).\n");
188 return 0;
191 if (chdir(todir)) {
192 fprintf(stderr, "Could not change the current working directory to '%s': %s.\n", todir, strerror(errno));
195 return 0;
200 * Handles a line of input.
201 * This function handles a line of input, which may
202 * result in two things:
203 * - a builtin command is executed
204 * - the process forks and launches the application
205 * specified in the input string
206 * @param input a string representing a single line of input
207 * @return zero to indicate the main program should continue,
208 * non-zero otherwise.
210 int handle_input(char *input) {
211 int return_value = 0;
212 pid_t pid;
213 char **argv;
214 int wait_status;
215 int i;
216 if (input == NULL) {
217 printf("exit\n");
218 return_value = 1;
219 } else if (input[0] == '\0') {
220 /* do nothing */
221 free(input);
222 } else {
223 argv = split_input(input);
224 if (history_length == 0 || history_search_pos(input, 1, 0) == -1) {
225 add_history(input);
227 free(input);
228 if (!strcmp(argv[0], "exit")) {
229 return_value = exit_cmd(&argv[1]);
230 } else if(!strcmp(argv[0], "kill")) {
231 return_value = kill_cmd(&argv[1]);
232 } else if (!strcmp(argv[0], "cd")) {
233 return_value = cd_cmd(&argv[1]);
234 } else {
235 pid = fork();
236 if (pid == 0) {
237 if (execvp(argv[0], argv)) {
238 fprintf(stderr, "Could not execute '%s': %s\n", argv[0], strerror(errno));
240 return_value = 1;
241 } else {
242 while (!waitpid(pid, &wait_status, 0) && WIFEXITED(wait_status));
245 for (i = 0; argv[i] != NULL; i++) {
246 free(argv[i]);
248 free(argv);
251 return return_value;
255 * Reads a line of input from stdin.
256 * This function is mainly here for modularity,
257 * meaning that if I need or want to change the way
258 * I get input, I would do it here once.
259 * @param prompt the prompt string to display
260 * @return a string containing a line of input
262 char *get_input(char *prompt) {
263 char *line;
264 char *cwd;
265 char *local_prompt;
267 if (prompt == NULL) {
268 cwd = get_current_dir_name();
269 if (cwd == NULL) {
270 cwd = "?";
273 struct passwd *passwd = getpwuid(getuid());
274 char *username = "?";
275 if (passwd != NULL) {
276 username = passwd->pw_name;
279 local_prompt = malloc(sizeof(char) * (strlen(cwd) + strlen(username) + 4));
280 local_prompt[0] = '\0';
281 strcat(local_prompt, username);
282 strcat(local_prompt, "@");
283 if (passwd != NULL && strstr(cwd, passwd->pw_dir) != NULL) {
284 strcat(local_prompt, "~");
285 strcat(local_prompt, cwd+strlen(passwd->pw_dir));
286 } else {
287 strcat(local_prompt, cwd);
289 strcat(local_prompt, "$ ");
292 line = readline(local_prompt);
294 if (prompt == NULL) {
295 if (strcmp(cwd, "?")) {
296 free(cwd);
298 free(local_prompt);
301 return line;
305 * Prints help.
306 * @param the name of the current program.
308 void print_usage(char *progname) {
309 printf("Usage: %s [OPTIONS]\n", progname);
310 printf(" Options:\n");
311 printf(" -p <prompt> The prompt to be displayed\n");
312 printf(" -h Display this help\n");
315 int main(int argc, char **argv) {
316 char *prompt = NULL;
317 char *input;
318 char option;
319 while ((option = getopt(argc, argv, ":hp:")) != -1) {
320 switch(option) {
321 case 'h':
322 print_usage(argv[0]);
323 exit(EXIT_SUCCESS);
324 break;
325 case 'p':
326 prompt = optarg;
327 break;
328 case ':':
329 printf("Missing argument for option '%c'.\n", optopt);
330 print_usage(argv[0]);
331 exit(EXIT_FAILURE);
332 break;
333 case '?':
334 printf("Unknown option '%c'.\n", optopt);
335 print_usage(argv[0]);
336 exit(EXIT_FAILURE);
337 default:
338 print_usage(argv[0]);
339 exit(EXIT_SUCCESS);
340 break;
344 using_history();
346 do {
347 input = get_input(prompt);
348 } while (!handle_input(input));
350 return 0;