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 *****************************************************************************/
29 #include <readline/readline.h>
30 #include <readline/history.h>
35 #include <sys/types.h>
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
) {
47 for (i
= 0; input
[i
] != '\0'; i
++) {
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;
72 char *single_word
= malloc(sizeof(char) * strlen(input
) + 1);
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
] == '\\') {
82 strcat(single_word
, "\\");
83 offset
= input
+ i
+ 1;
84 previous_was_space
= 0;
85 } else if (escaped
&& input
[i
] == ' ') {
87 strcat(single_word
, " ");
88 offset
= input
+ i
+ 1;
89 previous_was_space
= 0;
92 previous_was_space
= 0;
93 } else if (input
[i
] == '\\') {
94 strncat(single_word
, offset
, input
- offset
+ i
);
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;
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
);
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");
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
) {
149 if (argv
[0] == NULL
) {
150 printf("The 'kill' builtin takes at least one argument (a PID).\n");
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
));
157 fprintf(stderr
, "Killed (or sent signal %d) to PID %d.\n", SIGTERM
, pid
);
160 printf("%s is not a valid PID. Skipping.\n", argv
[i
]);
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
) {
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
));
182 todir
= passwd
->pw_dir
;
184 } else if (argv
[1] == NULL
) {
187 fprintf(stderr
, "The 'cd' builtin takes at most one argument (the directory to move to).\n");
192 fprintf(stderr
, "Could not change the current working directory to '%s': %s.\n", todir
, strerror(errno
));
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;
219 } else if (input
[0] == '\0') {
223 argv
= split_input(input
);
224 if (history_length
== 0 || history_search_pos(input
, 1, 0) == -1) {
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]);
237 if (execvp(argv
[0], argv
)) {
238 fprintf(stderr
, "Could not execute '%s': %s\n", argv
[0], strerror(errno
));
242 while (!waitpid(pid
, &wait_status
, 0) && WIFEXITED(wait_status
));
245 for (i
= 0; argv
[i
] != NULL
; i
++) {
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
) {
267 if (prompt
== NULL
) {
268 cwd
= get_current_dir_name();
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
));
287 strcat(local_prompt
, cwd
);
289 strcat(local_prompt
, "$ ");
292 line
= readline(local_prompt
);
294 if (prompt
== NULL
) {
295 if (strcmp(cwd
, "?")) {
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
) {
319 while ((option
= getopt(argc
, argv
, ":hp:")) != -1) {
322 print_usage(argv
[0]);
329 printf("Missing argument for option '%c'.\n", optopt
);
330 print_usage(argv
[0]);
334 printf("Unknown option '%c'.\n", optopt
);
335 print_usage(argv
[0]);
338 print_usage(argv
[0]);
347 input
= get_input(prompt
);
348 } while (!handle_input(input
));