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 *****************************************************************************/
31 #include <readline/readline.h>
32 #include <readline/history.h>
37 #include <sys/types.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"},
55 static int last_status
= 0;
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
) {
65 if (input
[0] == '\0') {
69 while (*input
== ' ') ++input
;
70 for (; *input
!= '\0'; ++input
) {
71 if (*input
== '\\' && *(input
+ 1) == ' ') {
73 } else if (*input
== ' ') {
75 for (; *(input
+ 1) == ' '; ++input
);
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
);
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
));
105 char **split
= malloc(sizeof(char *) * (words
+1));
107 fprintf(stderr
, "Could not allocate memory: %s\nExiting...\n", strerror(errno
));
111 /* skip first spaces */
112 while (*input
== ' ') ++input
;
113 for (; *input
!= '\0'; input
++) {
114 if (*input
== '\\' && (
115 *(input
+ 1) == ' ' ||
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
);
125 single_word
[i
++] = *input
;
130 single_word
[i
] = '\0';
131 split
[j
++] = strdup(single_word
);
141 int help_cmd(char **argv
) {
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");
152 for (i
= 0; !IS_LAST_BUILTIN(builtins
[i
]); i
++) {
153 if (!strcmp(argv
[0], builtins
[i
].name
)) {
154 printf("%s", builtins
[i
].help
);
158 if (IS_LAST_BUILTIN(builtins
[i
])) {
159 printf("Unknown builtin '%s'. Type 'help' to see a list of builtins.\n", argv
[0]);
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");
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
) {
189 if (argv
[0] == NULL
) {
190 printf("The 'kill' builtin takes at least one argument (a PID).\n");
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
));
197 fprintf(stderr
, "Killed (or sent signal %d) to PID %d.\n", SIGTERM
, pid
);
200 printf("%s is not a valid PID. Skipping.\n", argv
[i
]);
208 int true_cmd(char **argv
) {
212 int status_cmd(char **argv
) {
213 if (argv
[0] != NULL
) {
214 fprintf(stderr
, "The 'status' builtin doesn't take any arguments.");
216 printf("%d\n", last_status
);
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
) {
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
));
236 todir
= passwd
->pw_dir
;
238 } else if (argv
[1] == NULL
) {
241 fprintf(stderr
, "The 'cd' builtin takes at most one argument (the directory to move to).\n");
246 fprintf(stderr
, "Could not change the current working directory to '%s': %s.\n", todir
, strerror(errno
));
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;
272 } else if (input
[0] == '\0') {
276 argv
= split_input(input
);
277 if (history_length
== 0 || history_search_pos(input
, 1, 0) == -1) {
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]);
287 if (IS_LAST_BUILTIN(builtins
[i
])) {
290 if (execvp(argv
[0], argv
)) {
291 fprintf(stderr
, "Could not execute '%s': %s\n", argv
[0], strerror(errno
));
295 while (!waitpid(pid
, &wait_status
, 0) && WIFEXITED(wait_status
));
296 last_status
= WEXITSTATUS(wait_status
);
299 for (i
= 0; argv
[i
] != NULL
; i
++) {
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
) {
321 if (prompt
== NULL
) {
322 cwd
= get_current_dir_name();
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
));
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
));
345 strcat(local_prompt
, cwd
);
347 strcat(local_prompt
, "$ ");
350 line
= readline(local_prompt
);
352 if (prompt
== NULL
) {
353 if (strcmp(cwd
, "?")) {
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
) {
376 rl_replace_line("", 1);
381 int main(int argc
, char **argv
) {
385 while ((option
= getopt(argc
, argv
, ":hp:")) != -1) {
388 print_usage(argv
[0]);
395 printf("Missing argument for option '%c'.\n", optopt
);
396 print_usage(argv
[0]);
400 printf("Unknown option '%c'.\n", optopt
);
401 print_usage(argv
[0]);
404 print_usage(argv
[0]);
411 struct sigaction action
;
412 if (sigemptyset(&sigset
)) {
413 fprintf(stderr
, "Could not install a signal handler for SIGINT.\n");
415 action
.sa_handler
= handle_interrupt
;
416 action
.sa_mask
= sigset
;
418 sigaction(SIGINT
, &action
, NULL
);
421 rl_catch_signals
= 0;
424 input
= get_input(prompt
);
425 } while (!handle_input(input
));