Linux Shell in C Homework Sample

The regular command line shell is called BASH and you will be writing a replacement. You need to parse the command line, and call the external function. The commands cd and pwd should be implemented as internal commands (cd = change directory, pwd = print working directory). If & is present in the command then the process should be run in the background. Support > for redirect output to a file, and >> to redirect output to a file (in append mode). Add support for < redirecting input from stdin, and the | character is used to direct output from one function to the input from another. For more C programming assignment help contact us for a quote.

Solution:

cshell.c

//
// cshell.c
//

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>

#include <termios.h>

/* CANNOT BE CHANGED */
#define BUFFERSIZE 256
/* ——————–*/
#define PROMPT “myShell >> ”
#define PROMPTSIZE sizeof(PROMPT)

/* Structure used to save command data */
typedef struct
{
char **argv;
int argc;
int background;
char redir; /* redirection type 0 = no redirection, 1 = < , 2= >, 3 = >> */
char *redir_file; /* file to use for redirection if not NULL*/
int pipe_fd[2]; /* pipe connections */
}command_t;

/* Structure used to save an array of commands for the history */
typedef struct
{
char **commands;
int n_commands;
}history_t;

/*
Reads a char from stdin without blocking, if no character is ready returns 0
*/
char
_getch(){
int c;
static struct termios oldt, newt;

tcgetattr( STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
newt.c_cc[VTIME] = 1;
newt.c_cc[VMIN] = 0;
tcsetattr( STDIN_FILENO, TCSANOW, &newt);

if(read(STDIN_FILENO, &c, 1) != 1) /* read one char */
c = 0;
tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
return c;
}

/*
Prints a message directly on the screen (avoid buffering)
*/
void
print(char *msg)
{
write(STDOUT_FILENO, msg, strlen(msg));
}

/*
Clears the current line and prints the prompt again
*/
void
reprint_prompt(char *prompt)
{
print(“\r\x1b[2K”); /* clear all line */
print(prompt); /* show prompt */
}

/*
Reads a line, handles up and down arrow commands to browse history, ESC to
clear the input and Del to delete characters
*/
void
read_line(char *buffer, int max_len, char *prompt, history_t *history)
{
int i, end;
char c, d, e;
int cur_history;

cur_history = history->n_commands;

i = 0;
end = 0;
while(!end)
{
while(!(c = _getch()));
if (c == 27) /* escape key */
{
if ((d = _getch()) != 0) /* second escape code*/
{
e = _getch(); /* third escape code*/
if (e == 0x41 && cur_history > 0) /* up key */
{
cur_history–; /* display previous command in history */
reprint_prompt(prompt);
strcpy(buffer, history->commands[cur_history]);
print(buffer);
i = strlen(buffer);
}
if (e == 0x42) /* down key */
{
cur_history++;
if (cur_history < history->n_commands) /* display next command in history */
{
reprint_prompt(prompt);
strcpy(buffer, history->commands[cur_history]);
print(buffer);
i = strlen(buffer);
}
else
{
cur_history = history->n_commands;
reprint_prompt(prompt);
i = 0;
}
}
}
else
{
reprint_prompt(prompt);
i = 0; /* discard previous read chars */
}
}
else if (c == 127 && i > 0) /* delete */
{
write(STDOUT_FILENO, “\x1b[D\x1b[K”, 6); /* move left one char, clear line */
i–;
}
else if (c == 10 || (c >=32 && c < 127))
{
write(STDOUT_FILENO, &c, 1);
buffer[i] = c; /* save in buffer */
if (c == ‘\n’) /* if newline, end input */
end = 1;
else if (i + 1 >= max_len) /* if we ran out of buffer space, end*/
end = 1;
else
i++;
}
}
buffer[i] = 0; /* insert end of string*/
}

/*
Split a string using the given sepatator, returns an array of pointers to the
split parts, it updates argc with the number of parts found
*/
char**
split(char *string, char *separator, int *argc)
{
char *token;
char **argv;

argv = (char **) malloc(sizeof(char *));
*argc = 0;
token = strtok(string, separator);
while(token != NULL)
{
argv[(*argc)++] = token;
argv = (char **) realloc(argv, ((*argc) + 1)*sizeof(char *));
token = strtok(NULL, separator);
}
argv[*argc] = NULL;
return argv;
}

/*
Parses a command, splitting a command line by spaces and looking for redirection
characters, it also looks for the backround execution character &
*/
command_t*
get_command(char *string)
{
command_t *command;

command = (command_t *) malloc(sizeof(command_t));
command->background = 0;
command->redir = 0;
command->redir_file = NULL;
command->pipe_fd[0] = -1;
command->pipe_fd[1] = -1;
command->argv = split(string, ” \t”, &(command->argc)); /* split by spaces */

if (command->argc > 1 && !strcmp(command->argv[command->argc – 1], “&”)) /* if it’s a background command */
{
command->argv[command->argc – 1] = NULL; /* remove char from argv */
command->background = 1;
command->argc–;
}
if (command->argc > 2)
{
if (!strcmp(command->argv[command->argc – 2], “<“)) /* if it’s a < redirection */
{
command->argv[command->argc – 2] = NULL; /* remove redirection from argv */
command->redir = 1;
command->redir_file = command->argv[command->argc – 1];
command->argc -= 2;
}
else if (!strcmp(command->argv[command->argc – 2], “>”)) /* if it’s a > redirection */
{
command->argv[command->argc – 2] = NULL; /* remove redirection from argv */
command->redir = 2;
command->redir_file = command->argv[command->argc – 1];
command->argc -= 2;
}
else if (!strcmp(command->argv[command->argc – 2], “>>”)) /* if it’s a >> redirection */
{
command->argv[command->argc – 2] = NULL; /* remove redirection from argv */
command->redir = 3;
command->redir_file = command->argv[command->argc – 1];
command->argc -= 2;
}
}
return command;
}

/*
Frees the space allocated for a command structure
*/
void
free_command(command_t *command)
{
free(command->argv);
free(command);
}

/*
Redirects standard input and output based on the command redirection data
and the pipes connected to it
*/
void
redirect_io(command_t *command)
{
int fd;
if (command->redir_file != NULL) /* if redirecting to a file */
{
switch (command->redir)
{
case 1: /* input redirection */
if (command->pipe_fd[0] != -1) /* if input was connected to pipe, close pipe input */
{
close(command->pipe_fd[0]);
command->pipe_fd[0] = -1;
}
fd = open(command->redir_file, O_RDONLY); /* open file for reading */
if (fd == -1)
{
fprintf(stderr, “Unable to open file ‘%s’\n”, command->redir_file);
exit(1);
}
dup2(fd, STDIN_FILENO); /* redirect input to file*/
close(fd); /* close duplicate */
break;
case 2: /* output redirection */
if (command->pipe_fd[1] != -1) /* if input was connected to pipe, close pipe output */
{
close(command->pipe_fd[1]);
command->pipe_fd[1] = -1;
}
/* open and truncate or create new file for writing */
fd = open(command->redir_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd == -1)
{
fprintf(stderr, “Unable to open file ‘%s’\n”, command->redir_file);
exit(1);
}
dup2(fd, STDOUT_FILENO); /* redirect output to file*/
close(fd); /* close duplicate */
break;
case 3: /* output redirection, appending */
if (command->pipe_fd[1] != -1) /* if input was connected to pipe, close pipe output */
{
close(command->pipe_fd[1]);
command->pipe_fd[1] = -1;
}
/* open and truncate or create new file for writing */
fd = open(command->redir_file, O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd == -1)
{
fprintf(stderr, “Unable to open file ‘%s’\n”, command->redir_file);
exit(1);
}
dup2(fd, STDOUT_FILENO); /* redirect output to file*/
close(fd); /* close duplicate */
break;
}
}
if (command->pipe_fd[0] != -1) /* if input was connected to pipe */
{
dup2(command->pipe_fd[0], STDIN_FILENO); /* redirect input to file*/
close(command->pipe_fd[0]); /* close duplicate */
}
if (command->pipe_fd[1] != -1) /* if output was connected to pipe */
{
dup2(command->pipe_fd[1], STDOUT_FILENO); /* redirect input to file*/
close(command->pipe_fd[1]); /* close duplicate */
}
}

/*
Executes the cd command, it executes on the parent process
*/
void
execute_cd(command_t *command)
{
if (command->pipe_fd[0] != -1) /* if input was connected to pipe */
close(command->pipe_fd[0]);
if (command->pipe_fd[1] != -1) /* if output was connected to pipe */
close(command->pipe_fd[1]);
if (command->argc != 2)
fprintf(stderr, “Invalid number of commands for cd\n”);
else if( chdir(command->argv[1]) < 0)
fprintf(stderr, “Unable to change to ‘%s’\n”, command->argv[1]);
}

/*
Executes the pwd command, it assumes it’s running in a child process
*/
void
execute_pwd(command_t *command)
{
char cur_dir[BUFFERSIZE];
if (command->argc != 1)
{
fprintf(stderr, “Invalid number of commands for pwd\n”);
exit(1);
}
else
{
getcwd(cur_dir, BUFFERSIZE);
printf(“%s\n”, cur_dir);
exit(0);
}
}

/*
Executes a command whose data has been saved on the command structure,
it creates a child process to run it and it returns the child pid
*/
pid_t
execute_command(command_t *command)
{
pid_t pid;

if (!strcmp(command->argv[0], “cd”))
{
execute_cd(command);
pid = -1;
}
else {
pid = fork(); /* fork the process */
if (pid == -1) /* if there was an error */
{
fprintf(stderr, “Error forking process\n”);
return -1;
}
else if (pid == 0) /* if we are in the child process */
{
redirect_io(command); /* redirect input and output */
if (!strcmp(command->argv[0], “pwd”))
execute_pwd(command);
else if (execvp(command->argv[0], command->argv) < 0)
{
fprintf(stderr, “Invalid command ‘%s’\n”, command->argv[0]);
exit(1);
}
}
}
return pid;
}

/*
Starts the execution of a command line which can consist of single or piped
commands, it dispatches the execute command as required. It handles the
pipe connections between piped commands
*/
void
execute_command_line(char *cmd_line)
{
char **cmds;
int n_cmds;
int **pipe_fd;
int i, status;
command_t *command;
pid_t pid;

cmds = split(cmd_line, “|”, &n_cmds); /* split by pipes */

if (n_cmds == 1) /* single command */
{
command = get_command(cmds[0]);
pid = execute_command(command);
if (pid != -1 && !command->background)
waitpid(pid, &status, 0);
free_command(command);
}
else
{
pipe_fd = (int **) malloc((n_cmds – 1)*sizeof(int *));

for (i = 0; i < n_cmds; i++)
{
if (i < n_cmds – 1)
{
pipe_fd[i] = (int *) malloc(2*sizeof(int));
pipe(pipe_fd[i]); /* create pipe */
}

command = get_command(cmds[i]);
if (i > 0)
command->pipe_fd[0] = pipe_fd[i – 1][0]; /* connect previous pipe */
if(i < n_cmds – 1)
command->pipe_fd[1] = pipe_fd[i][1]; /* connect to pipe */

pid = execute_command(command);
if (i == n_cmds – 1 && pid != -1 && !command->background)
{
waitpid(pid, &status, 0);
}
free_command(command);
if (i < n_cmds – 1)
close(pipe_fd[i][1]);
}
for (i = 0; i < n_cmds – 1; i++)
{
close(pipe_fd[i][0]);
close(pipe_fd[i][1]);
free(pipe_fd[i]);
}
free(pipe_fd);
}
free(cmds);
}

/*
Looks for the home directory in the environment variables and returns a
pointer to it
*/
char*
get_home_directory(char **env)
{
char *home;
int i = 0;

home = NULL;
while(env[i]!=NULL && home == NULL)
{
if (!strncmp(env[i], “HOME=”,5)) /* search for home variable */
home = &env[i][5];
i++;
}
return home;
}

/*
It saves the current prompt that includes the current directory. It replaces
the home directory in the currend path by a ~ character
*/
void
update_prompt(char *home, char *prompt)
{
char cur_dir[BUFFERSIZE];
int len;

sprintf(prompt, “myShell “);

getcwd(cur_dir, BUFFERSIZE); /* get current directory */
len = strlen(home);
if (!strncmp(cur_dir, home, len)) /* if the path starts with the home directory */
{
strcat(prompt, “~/”);
if (cur_dir[len] == ‘/’) /* avoid printing double /*/
len++;
strcat(prompt, &cur_dir[len]); /* print the rest of the path */
}
else
strcat(prompt, cur_dir);
strcat(prompt, ” >> “);
}

/*
Creates a new history structure and initializes its fields
*/
history_t*
create_history()
{
history_t *history;

history = (history_t *) malloc(sizeof(history_t));
history->commands = NULL;
history->n_commands = 0;
return history;
}

/*
Adds a command line to the history
*/
void
add_to_history(char *command, history_t *history)
{
if (history->commands == NULL)
history->commands = (char **) malloc(sizeof(char *));
else
history->commands = (char **) realloc(history->commands, (history->n_commands + 1) * sizeof(char *));
history->commands[history->n_commands++] = strdup(command);
}

/*
Frees the space allocated for the history structure and all the commands
saved within
*/
void
free_history(history_t *history)
{
int i;
if (history != NULL)
{
for (i = 0; i < history->n_commands; i++)
free(history->commands[i]);
if (history->commands)
free(history->commands);
free(history);
}
}

/*
Main program, it shows the prompt, reads input and executes the commands.
Initializes the home directory variable and the history array. It frees all
allocated memory before exit.
*/
int
main(int argc, char **argv, char **env)
{
char buffer[BUFFERSIZE];
int quit = 0;
char *home;
char prompt[BUFFERSIZE];
history_t *history;

home = get_home_directory(env);
history = create_history();

while (!quit)
{
update_prompt(home, prompt); /* update prompt based on current dir */
write(STDOUT_FILENO, prompt, strlen(prompt)); /* print prompt */

read_line(buffer, BUFFERSIZE, prompt, history); /* read command line */
if(buffer[0] != ‘\0’)
{
if (!strcmp(buffer, “exit”))
quit = 1;
else
{
add_to_history(buffer, history);
execute_command_line(buffer);
}
}
}
free_history(history);
return 0;
}