Skip to content

Instantly share code, notes, and snippets.

@panqiincs
Created August 6, 2018 05:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panqiincs/55c7681a115a6fe7caa2bd743029cb1f to your computer and use it in GitHub Desktop.
Save panqiincs/55c7681a115a6fe7caa2bd743029cb1f to your computer and use it in GitHub Desktop.
A Shell program with basic functionality.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#define MAX_ARG_NUM 20 // max number of arguments
#define MAX_ARG_LEN 200 // max length of commands
#define MAX_PROMPT_LEN 100 // max length of prompt string
void print_prompt(char *);
void handle_input(char *, char **);
void run_cmd(char **);
int main(int argc, char **argv)
{
char *arg_buf; // store user input strings
char *arg_list[MAX_ARG_NUM + 1]; // argument list
char prompt_str[MAX_PROMPT_LEN + 1]; // store prompt string
// in parent process, ignore SIGINT and SIGQUIT, or process
// will quit
//signal(SIGINT, SIG_IGN);
//signal(SIGQUIT, SIG_IGN);
arg_buf = (char *)malloc(sizeof(char) * MAX_ARG_LEN);
for (;;) {
print_prompt(prompt_str);
handle_input(arg_buf, arg_list);
run_cmd(arg_list);
}
free(arg_buf);
exit(EXIT_SUCCESS);
}
/**
* Construct a prompt string, store it in an charactor array which address
* is stringp and print out. A prompt should be like this:
*
* [psh]krist@linux-szhw:/home/krist/Workspace$
* | | <--------> <------------------->|
* | | | | |
* header username hostname pwd prompt
*
* @param stringp a pointer to a char array that store prompt string, the
* function modifies the array content
* @return void
*/
void print_prompt(char *stringp)
{
size_t offset = 0;
// header [psh]
stringp[offset++] = '[';
stringp[offset++] = 'p';
stringp[offset++] = 's';
stringp[offset++] = 'h';
stringp[offset++] = ']';
// username@hostname
getlogin_r(stringp + offset, MAX_PROMPT_LEN - offset);
offset += strlen(stringp + offset);
stringp[offset++] = '@';
gethostname(stringp + offset, MAX_PROMPT_LEN - offset);
offset += strlen(stringp + offset);
stringp[offset++] = ':';
// current directory
getcwd(stringp + offset, MAX_PROMPT_LEN - offset);
offset += strlen(stringp + offset);
// prompt
if (geteuid() == 0) {
stringp[offset++] = '#'; // superuser
} else {
stringp[offset++] = '$'; // normal
}
stringp[offset++] = ' ';
stringp[offset] = '\0';
printf("%s", stringp);
}
/**
* Store user input in input_buf, parse it to arguments, and store the
* argument list in arg_vec
*
* @param input_buf a pointer to a char array, store one line of user
* input, the caller should guarantee the buffer size is
* big enough
* @param arg_vec an array of pointers to arguments, terminated by NULL
* @return void
*/
void handle_input(char *input_buf, char **arg_vec)
{
size_t len; // max length of arg string buffer
ssize_t num_read; // number of characters read from stdin
size_t arg_num; // arguments count
char *token;
char *stringp;
len = MAX_ARG_LEN;
num_read = 0;
if ((num_read = getline(&input_buf, &len, stdin)) == -1) {
exit(EXIT_FAILURE);
}
if (len > MAX_ARG_LEN) {
printf("Exceeds max argument length limit, please try again!\n");
exit(EXIT_FAILURE);
}
#ifdef DEBUG
printf("input_buf = %p, len = %zu\n", input_buf, len);
printf("Retrieved line of length %zu:\n", num_read);
printf("%s", input_buf);
#endif
input_buf[num_read - 1] = '\0'; // overwrite the newline character
stringp = input_buf;
arg_num = 0; // number of arguments
while (((token = strsep(&stringp, " ")) != NULL)
&& (arg_num < MAX_ARG_NUM)) {
// Token is terminated by overwriting the delimiter with a null
// byte('\0'), so continuous space will result in a token with
// only a null byte, skip it.
if (strcmp(token, "") != 0) {
arg_vec[arg_num] = token;
arg_num++;
}
}
arg_vec[arg_num] = NULL;
#ifdef DEBUG
printf("%zu arguments in total:\n", arg_num);
char **ptr;
for (ptr = arg_vec; *ptr != NULL; ptr++) {
printf("%s\n", *ptr);
}
printf("\n");
#endif
}
/**
* Execute a normal(not built-in) command, fork a child process for each
* command to run, the parent process wait for children to exit
*
* @param arg_vec an array of pointers to arguments, terminated by NULL
* @return void
*/
void exec_cmd(char **arg_vec)
{
int status;
pid_t pid;
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) { // child process, run the command
// in child process, default actions
//signal(SIGINT, SIG_DFL);
//signal(SIGQUIT, SIG_DFL);
// execvp() returns only if an error occurs
execvp(arg_vec[0], arg_vec);
perror("execvp");
exit(EXIT_FAILURE);
} else { // parent process, wait for children to exit
while (wait(&status) != pid)
;
#ifdef DEBUG
printf("\nchild exited with status %d, %d\n",
status >> 8, status&0377);
#endif
}
}
/**
* Check if it is a built-in command, if it is, run it in parent process
* without a fork, if not, return
*
* @param arg_vec an array of pointers to arguments, terminated by NULL
* @return 0 it is a built-in command, finish running and return
* -1 it is not a built-in command and return directly
*/
int builtin_cmd(char **arg_vec)
{
if (strcmp(arg_vec[0], "cd") == 0) { // cd
if ((arg_vec[1] != NULL) && (arg_vec[2] == NULL)) {
if (chdir(arg_vec[1]) == -1) {
perror("cd");
}
} else {
printf("Usage: cd [directory]\n");
}
return 0;
} else if (strcmp(arg_vec[0], "exit") == 0) { // exit
exit(EXIT_SUCCESS);
}
return -1;
}
/**
* Run the command, both built-in and normal commands
*
* @param arg_vec an array of pointers to arguments, terminated by NULL
* @return void
*/
void run_cmd(char **arg_vec)
{
if (arg_vec[0] == NULL) {
return;
}
if (builtin_cmd(arg_vec) == -1) { // run build-in commands
exec_cmd(arg_vec); // run normal commands
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment