Starting from:

$30

CSCI4210 Homework 2- Interactive Shell Solved

In this second homework, you will use C to implement a rudimentary interactive shell similar to that of bash. The focus of this assignment is on process creation, process management, and inter-process communication (IPC) via fork(), wait(), waitpid(), pipe(), etc.

As with Homework 1, continue to use calloc(), realloc(), and free() to properly and efficiently manage your memory usage. Consider using valgrind to verify that there are no memory leaks. We will continue to test for this on Submitty.

To properly implement your shell, create an infinite loop that repeatedly prompts the user to enter a command, parses the given command, locates the command executable, then executes the command (if found).

To execute the given command, a child process is created via fork(), with the child process then calling execv() to execute the command. In the meanwhile, the parent process calls waitpid() to suspend its execution and wait for the child process to terminate. This is called foreground processing. (And note that you must use these specific system calls.)

If instead the command is to be run with the parent process not waiting for the child process to complete its execution, then your shell will instead use background processing, which is achieved by using the & symbol (and explained in more detail on page 4).

Locating the command executable
Before executing a command entered by the user, the command executable must be found using the list of possible paths specified by an assignment-specific environment variable called $MYPATH. Do not use $PATH for this assignment (since $PATH is used for bash).

Similar to $PATH, this new $MYPATH environment variable consists of a series of paths delimited by the : character. And if $MYPATH is not set, use /bin:. as the default, meaning commands will be searched for first in the /bin directory, then the . (i.e., current) directory. By default, the $MYPATH variable is not set, so for testing, set and unset this variable manually in the bash shell before running your shell. Here’s how:

bash$ export MYPATH=/usr/local/bin:/usr/bin:/bin:.

bash$ echo $MYPATH

MYPATH=/usr/local/bin:/usr/bin:/bin:. bash$ unset MYPATH

To obtain $MYPATH (or any environment variable, e.g., $HOME) from within your program, use the getenv() function. Do not use setenv().

Executing the command
Searching left-to-right in $MYPATH, if the requested command is found in one of the specified directories, your program runs the executable in a child process via the fork() and execv() system calls. Note that you must use execv().

Further, in the parent process, you must use lstat() to determine whether the requested command exists (e.g., does /bin/ls exist?) and whether it is executable. See the man page and the directories.c example.

Commands are line-based, as in bash. Therefore, each command may optionally have any number of arguments (i.e., argv[1], argv[2], etc.). You can assume that each command read from the user will not exceed 1024 characters. Further, you can assume that each argument will not exceed 64 characters, but all memory must be dynamically allocated.

You may also assume that command-line arguments do not contain spaces. In other words, do not worry about parsing out quoted strings in your argument list, as in: bash$ cat a.txt b.txt "some weird file.txt" d.txt

Special shell commands
Not all commands entered into the shell actually result in a call to fork(). For the cd command, if your shell did execute the command via fork(), your shell’s current working directory would not change! Therefore, you must use the chdir() system call in the parent to handle this special case. Further, if the cd command has no arguments, then you should use the $HOME environment variable as the target directory.

As for wildcards and special characters, you do not need to support *, ?, and [] in your shell, though note that these are typically expanded by the shell before calling fork() and execv().

Finally, to exit your shell, the user enters exit. When this occurs, your shell must output bye and terminate.

Required output
The command prompt in the shell must show the current working directory followed by the '$' prompt character and one space. To obtain the current working directory for the running process, use the getcwd() function. And use fgets() to read in a command from the user.

Required output is shown below, with sample input also shown. As per usual, you must match the given output format exactly as shown.

/cs/goldsd/s19/os/assignments/hw2$ cocoapuffs

ERROR: command "cocoapuffs" not found /cs/goldsd/s19/os/assignments/hw2$ ls annoying.c a.out code hw2.aux hw2.log hw2.out hw2.pdf hw2.tex

/cs/goldsd/s19/os/assignments/hw2$ ls -l total 156 drwxrwx--x 3 goldsd goldsd 4096 May 27 15:08 . drwxrwx--x 5 goldsd goldsd 51 May 27 11:59 .. -rw-rw----+ 1 goldsd goldsd    197 May 27 15:08 annoying.c -rwxrwx--x 1 goldsd goldsd 8344 May 27 15:08 a.out drwxrwx--x 2 goldsd goldsd  6 May 27 15:04 code -rw-rw---- 1 goldsd goldsd    662 May 27 15:04 hw2.aux

-rw-rw---- 1 goldsd goldsd 20507 May 27 15:04 hw2.log

-rw-rw---- 1 goldsd goldsd                                  0 May 27 15:04 hw2.out

-rw-rw---- 1 goldsd goldsd 97289 May 27 15:04 hw2.pdf

-rw-rw----+ 1 goldsd goldsd 9886 May 27 15:07 hw2.tex

/cs/goldsd/s19/os/assignments/hw2$ cat annoying.c /* annoying.c */

#include <stdio.h>

#include <stdlib.h> #include <unistd.h>

int main()

{

while ( 1 )

{ printf( "Hey, get back to work!\n" ); sleep( 3 );

}

return EXIT_SUCCESS;

}

/cs/goldsd/s19/os/assignments/hw2$ exit bye

Foreground and background processing
Normally, a shell will execute the given command via a child process, with the parent calling either wait() or waitpid() to wait for the child process to complete its command and terminate. The child process essentially calls execv() to execute the given command with the arguments allocated dynamically.

Your shell must be able to execute a process in the background if the user includes an ampersand (i.e., '&') at the end of a command. In this case, when the child process is created, the parent does not wait for the child to terminate before prompting the user for the next command.

For a background process, the parent must report that the child process has been created.

The parent must also report when the child process does terminate (if it does). When you detect that the background process has terminated (i.e., before you display the prompt), display the child process ID and its exit status, as in:

[process 9335 terminated with exit status 0]

This should be the normal reporting for all children that terminate whether they return EXIT SUCCESS or an error code.

For background child processes that terminate due to a kill signal or other abnormal termination, display the following:

[process 9335 terminated abnormally]

Note that the & symbol can only be included at the end of the command line; otherwise, this is a user error. Also note that the output may be interleaved with background processes, so do not expect to always match the example output exactly line for line.

/cs/goldsd/s19/os/assignments/hw2$ ls annoying.c a.out code hw2.aux hw2.log hw2.out hw2.pdf hw2.tex

/cs/goldsd/s19/os/assignments/hw2$ a.out &

[running background process "a.out"] Hey, get back to work!

/cs/goldsd/s19/os/assignments/hw2$ Hey, get back to work!

Hey, get back to work!

Hey, get back to work!

/cs/goldsd/s19/os/assignments/hw2$ ls annoying.c a.out code hw2.aux hw2.log hw2.out hw2.pdf hw2.tex /cs/goldsd/s19/os/assignments/hw2$ Hey, get back to work!

Hey, get back to work!

Hey, get back to work!

/cs/goldsd/s19/os/assignments/hw2$ exit bye

Hey, get back to work!

Hey, get back to work!

For the above example, you will need to use kill in the bash shell to terminate the background process since it will continue to execute after your shell terminates. And note that for this assignment, you are required to use waitpid() for both foreground and background processes.

Do not do anything extra to kill any child processes, in particular when the user exits your shell. And to properly “catch” background processes when they terminate, mimic the behavior of bash by checking for terminated background processes immediately before you display the prompt to the user. Do not use a signal handler for this.

IPC via pipes
Finally, add support for a pipe between two processes; you need only support one pipe per command line. Two processes (i.e., A and B) may be connected via a pipe such that the output on stdout from process A is the input on stdin to process B.

A pipe is indicated via the | symbol. To create a pipe, use the pipe() system call. Further, the two processes A and B must have your shell process as their parent process.

/cs/goldsd/s19/os/assignments/hw2$ ps -ef | grep goldsd

root
23553 1414 0 15:00 ?
00:00:00 sshd: goldsd [priv]
goldsd
23556                 1 0 15:00 ?
00:00:00 /lib/systemd/systemd --user
goldsd
23558 23556 0 15:00 ?
00:00:00 (sd-pam)
goldsd
23714 23553 0 15:00 ?
00:00:00 sshd: goldsd@pts/0
goldsd
23715 23714 0 15:00 pts/0
00:00:00 -bash
goldsd
23716 23715 0 15:00 pts/0
00:00:00 myshell
root
23729 1414 0 15:01 ?
00:00:00 sshd: goldsd [priv]
goldsd
23813 23729 0 15:01 ?
00:00:00 sshd: goldsd@notty
goldsd
23814 23813 0 15:01 ?
00:00:00 /usr/lib/openssh/sftp-server
goldsd
24615 23716 0 15:15 pts/0
00:00:00 ps -ef
goldsd
24616 23716 0 15:15 pts/0
00:00:00 grep goldsd
/cs/goldsd/s19/os/assignments/hw2$ ls -1 annoying.c

a.out code hw2.aux hw2.log hw2.out hw2.pdf hw2.tex

/cs/goldsd/s19/os/assignments/hw2$ ls -1 | wc -l

8

/cs/goldsd/s19/os/assignments/hw2$ exit bye

Pipes and background processes
Note that a pair of piped processes can be run in the background if the user specifies an ampersand at the end of the line. When run in the background, both processes are background processes. And when these processes terminate, show both processes.

Here is an example with 12117 and 12118 as the process IDs of the two background processes:

/cs/goldsd/s19/os/assignments/hw2$ ls -1 | wc -l

8

/cs/goldsd/s19/os/assignments/hw2$ ls -1 | wc -l &

[running background process "ls"]

[running background process "wc"]

/cs/goldsd/s19/os/assignments/hw2$

8

[process 12117 terminated with exit status 0]

[process 12118 terminated with exit status 0] /cs/goldsd/s19/os/assignments/hw2$ exit bye

As noted previously, output may be interleaved with background processes, so do not expect to always match the example output exactly line for line.

Relinquishing allocated resources
Be sure that all processes (i.e., the parent shell process and all child processes) properly deallocate memory via free(), close all opened file descriptors, etc.

Error handling
If improper command-line arguments are given, report an error message to stderr and abort further program execution. In general, if an error is encountered, display a meaningful error message on stderr by using either perror() or fprintf(), then abort further program execution.

Error messages must be one line only and use the following format:

ERROR: <error-text-here>

Note that you should not abort your shell program if the user enters an invalid command (e.g., command not found) or if the child process reports an error.

More products