“Midway upon the journey of our life I found myself within a forest dark, For the straightforward path had been lost” - Dante Alighieri, The Divine Comedy
penn-shell
Table of contents
- Collaboration
- Overview
- Setup
- Specification
read,fork,exec,wait, and repeat!- Control Signals
- Standard input/output redirections
- Milestone 0
- Pipelines
- Milestone 1
- Foreground and background processes (interactive shell only)
- Job Control
- Non-interactive mode of penn-shell
- Code Design
- clang-tidy and clang-format
- Arguments to
penn-shell - Terminal Signals
- Parsing
- Acceptable System Calls and Library Functions
- Pitfalls
- Memory Errors and Leaks
- Error Handling
- Reusing Code
- Developing Your Code
- What to turn in
- Submission
- Retrospective Meeting
- Grading Guidelines
- Attribution
- Optional Challange
Collaboration
You must work in groups of 2 for this project. You may not work with others outside your pairing. Please regularly check EdStem through-out this course for project specification clarifications. You are required to version control your code, but please only use the GitHub repository created for you by the staff. Do not work in public GitHub repositories! Please avoid publishing this project at any time, even post-submission, to observe course integrity policies.
You may use code from Project 0, but only code you or your partner wrote. If you are caught using code from other groups or any sources from public code repositories, your entire group will receive ZERO for this assignment, and will be sent to the Office of Student Conduct where there will be additional sanctions imposed by the university.
Overview
In this assignment you will implement a shell named penn-shell that is a simplified version of a fully featured shell like bash. penn-shell will have foreground and background processes; standard input and output redirections; pipelines; and job control. As in the previous project, you will only use the functions specified in this project handout.
We will publish the Makefile used by the autograder. In your C source code, you should use the PROMPT macro defined in the Makefile instead of defining your own macros.
Setup
Register you and your partner as being in a group.
For this assignment you will work with a partner and use a github repo that will be created for you. To know who is paired with who, and thus which repos to create, you must register your group on canvas. Please go to canvas where you should see a tab called “People” on the left hand side.

You should then see a tab near the top called “everyone” and “groups”. Select the groups tab and you should see a list of penn-shell groups similar to what is seen below:

Once you are on this page, you just need to join a group and have your partner join the same group with you.
Get the repository for you and your partner setup
Once you have your group registered, you need to get the github repository that you will use for sharing & maintaining your code. Please commit regularly, every year there is a case where a group loses a large portion of the project code close to the deadline. DO NOT LET THIS BE YOUR GROUP. Code can be lost in a number of ways, maybe you accidentally rm it, maybe your docker container or laptop that has the code won’t boot anymore. To make sure most of your progress is kept even in a catastrophic situation, commit and push your code regularly.
To have your github repo setup for your gorup, you should follow almost the same instructions as you did in project 0, except for two differences:
- Your
account.txtfile should contain the email and github username of both you and your partner as shown below:
<email 1>
<account 1>
<email 2>
<account 2>
- Your will submit your
account.txtto the Penn-Shell GitHub Repo Creation assignment, which is different than the one for project 0.
Shortly afterwards a repo should be created and your group should be invited to it. If you are having issues, please post on Ed.
Download the provided makefile and parser code
Once you have a repository, you should download the provided starter code.
Every group will need to download the following files:
parser.h: the header file for the provided parser code that you should use for your projectparser.c: the C source file for the provided parser codeJob.h: the header file that declares theJobstruct that you should use and add on to in this project.clang-tidy: this is used to configure some of the options of clang-tidy- Note: you may not see this file when running
lsas any file that starts with.is considered hidden in Linux. You do not need to open or modify this file.
- Note: you may not see this file when running
Makefile: used to compile your code
To get these into your environement, run the following two commands in your terminal:
wget https://www.engineering.upenn.edu/~cis5480/current/projects/code/01/shell-code.zip
unzip shell-code.zip
If you get an error that wget doesn’t exist or something like that, you can run apt install wget and it should install and work again. You may have to type “Y” or “Yes” for the install of wget to complete. If it says you don’t have permission, then run sudo apt install wget and use the password we gave in the setup documentation, or you set for yourself if you are using WSL or some other environment.
Lastly, we highly recommend you download and take a look at the following file. It is not required and should not be included in your project code, but it demonstrates how to use the parser code that we have provided. You are free to use this as a reference or starting point for your penn-shell code.
wget https://www.engineering.upenn.edu/~cis5480/current/projects/code/01/parser-demo.c
If you are having issues with wget you can try to click the following links to get the files instead:
- parser.h
- parser.c
- Job.h
- Makefile
- .clang-tidy NOTE: You will have to unzip it and rename the file in the zip from “clang-tidy” to “.clang-tidy”
- parser-demo.c
Specification
Read the specificaiton, seriously, read it all. If you have questions, ask in Office Hours or Ed.
Design & Implement penn-shell
This is a long specification and a long project. To help your team decide where to start and make sure your group makes progress, there are two project milestones milestone 0 and milestone 1 submission that is due before the full assignment. We highly recommend that you read over the entire specification but focus on and implement the parts that are included in milestone 0 first, then milestone 1 afterwards.
At its core, an interactive shell is a simple loop that prompts the user for a program to run, executes the program as a separate process, and waits for the process to finish. The shell exits the loop when the user presses Ctrl-D at the prompt.
In this assignment, you will implement a shell with four additional features: standard input/output redirections, pipelines, background/foreground processing, and job control. Also, the shell can run as a non-interactive shell without supporting background processing and job control. You may reuse any code from the previous assignment, as long as one of your team members wrote it.
read, fork, exec, wait, and repeat!
The core loop of a shell is essentially accomplished with the following system calls:
write(2): write the prompt tostderrread(2): read input fromstdininto a buffer (getline(3)is also allowed)fork(2): create a new process that is a copy of the current (parent) processexecvp(2): replace the current (child) process with anotherwaitpid(2): wait for a child process to finish before proceeding
You should spend some time carefully reading the man pages for all of these functions. For system calls, you want to use section 2 of the man pages. For C library functions, you want to use section 3. Additionally, carefully read the header file for the parser code we provide. More details modifying this core functionality are detailed further in this specification.
Control Signals
Ctrl-C Behaviour
Ctrl-C (SIGINT) is a very helpful signal often used from the shell to terminate the current running process. Child processes started from penn-shell should respond to Ctrl-C by following their normal behavior on SIGINT, but penn-shell itself should not exit (even when Ctrl-C is typed without a child process running). Instead, your shell must catch SIGINT and re-prompt (after printing a newline character if appropriate).
Ctrl-D Behaviour
Ctrl + D is used to send “EOF” (end of file) to stdin. This means that a program reading from stdin would have to stop reading from stdin (because there is nothing else to read), and for most programs (including penn-shell) the program exits gracefully.
NOTE: despite looking very similar to Ctrl-C, Ctrl-D is NOT a signal in the system sense.
If the user presses Ctrl-D at the beginning of the input line, your shell must exit. If the input line is empty and user types Ctrl-D, your shell must exit. If the input line is not empty and the user types Ctrl-D, your shell should try to process the user input, print a newline character, and DO NOT exit.
Standard input/output redirections
By default, standard input and output refer to the terminal. However, they can be redirected to read from and write to files, respectively.
Standard input redirection
In the following example, the wc command reads from the script.txt file via the standard input redirection operator < and prints the number of lines in the file.
penn-shell> wc -l < script.txt
2
penn-shell>
If script.txt can’t be opened successfully, penn-shell should throw an error without executing the command.
Standard output redirection
In the following example, the pwd command outputs to the out.txt file via the standard output redirection operator >.
penn-shell> echo hello > out.txt
penn-shell> cat out.txt
hello
penn-shell>
If out.txt doesn’t exist, it will be created. Otherwise, it will be truncated to 0 size.
The >> operator is identical to >, except that the standard output is appended to the file if it already exists:
penn-shell> echo foo > log.txt
penn-shell> echo bar >> log.txt
penn-shell> cat log.txt
foo
bar
penn-shell>
Combining redirections
The standard input/output redirections can be combined. For example, the cat command below effectively copies log.txt to a new file log2.txt.
penn-shell> cat < log.txt > log2.txt
penn-shell> diff log.txt log2.txt
penn-shell>
Milestone 0
In order to ensure that students stay on strack and receive early feedback, we require all groups to submit some intermediate solutions before the deadline. For the first milestone, students are expected to have completed the basic shell plus standard input/output redirection. Do include a README in this submission, including an accounting of the work you have completed thus far. This milestone is designed to guide your workflow. As such, we will not grade it for style, but your grade on the milestone will be 2.5% of your final penn-shell grade. You cannot use late tokens for this milestone.
Pipelines
Pipes are another form of redirections. Instead of redirecting to a file, a pipe connects the standard output of one process to the standard input of another. This is best demonstrated via an example:
penn-shell> head -c 128 /dev/urandom | base64
The head command will read the specified number of random bytes from /dev/urandom, and its output is piped to the standard input of the base64 program. In this assignment you are required to implement a multi-process pipeline, i.e., one process can pipe output to the input of another process, whose output can be an input to another and so on as seen in many full-featured shells. A good example of a multi-stage pipeline would be:
penn-shell> echo brains > log
penn-shell> cat log | grep brains | wc -l
1
penn-shell>
For the above pipeline, the output of the cat (i.e. the content in log), is sent to the grep command, whose output is piped to the wc utility. As a result, the above command would print out the number of lines in the log file that contain the word brains.
Implementing a multi-process pipeline isn’t trivial if not planned out properly. We recommend that you think through the flow of the execution (e.g. draw a diagram of how each part of the pipe should
pipe(2) system call
A pipe is a special unidirectional file descriptor with a single read end and a single write end. To generate a pipe, you will employ the pipe(2) system call, which will return two file descriptors, one for reading and one for writing. You will then dup2(2) each end to the standard input and output of the respective processes in the pipeline. Be sure to close the file descriptors that are unused, or else your pipe will not work.
In past semesters we have seen students creat unecessary processes. If your pipeline consists of N commands, you should only need to fork N processes. For example.
ls | wcshould result in only two calls to fork. These two processes should be direct children of the shell. (Do NOT have thelsprocess fork thewcprocess. Do NOT have the shell fork an intermediate child that then forkslsandwc. Doing so will only make your code less efficient, more messy, and more bug prone.
Jobs and process group isolation (interactive shell only)
When executing a pipeline, penn-shell will execute multiple processes in parallel, forking for each command in the pipeline. All processes are part of the same job; a single command line execution. If penn-shell runs as an interactive shell, each job must be in its own process group that is different from the shell’s process group and other job’s process groups. (Even a job containing a single process must be in its own process group.) To set a process group, you will use the setpgid(2) system call, and you should refer to the manual for more details.
Note: when we say that the processes in a pipeline are run in parallel, we mean it. You CAN NOT fork a child, have the child call exec, and the parent wait for that child to finish before moving on to the next one. Your code MUST fork all children processes that will go on to
exectheir part of the pipeline, only then can the parent wait for these processes to finish.We say this beacuse it will not pass the autograder if you do not do it in parallel, and it is not always easy to alter a sequential implementation into one that is parallel.
Isolating jobs via process groups can affect terminal signaling. You may need to block certain signals during critical sections. See the signal.h and signal(7) manuals for the reasons signals are delivered and which system calls are re-entrant and which are not. If a non re-entrant (or “unsafe”) function is used in contexts where it can be interrupted, the behavior of the program is undefined. This can cause many headaches and bugs, so be sure to consider what is critical and what is not.
Milestone 1
To keep you on Track for penn-shell, we are again asking you to submit a second milestone. For the second milestone, students are expected to have completed everything from milestone 0, pipelined jobs, and process group isolation. Do include a README in this submission, including an accounting of the work you have completed thus far. This milestone is designed to guide your workflow. As such, we will not grade it for style, but your grade on the milestone will be 2.5% of your final penn-shell grade. You cannot use late tokens for this milestone.
Foreground and background processes (interactive shell only)
A key feature of penn-shell is the ability to start a job in either the foreground or the background. For a job executed in the foreground, the shell waits until the entire job is completed before prompting the user for more input. Alternatively, a job can be started in the background, and the shell will immediately prompt the user for the next command following executing the background job.
To start a job in the background, the user appends the & token to the last command of a pipeline. Here is an example:
penn-shell> sleep 1&
Running: sleep 1
penn-shell> echo Hello World
Hello World
Finished: sleep 1
penn-shell>
sleep is started in the background, and the shell drops back to a prompt allowing the user to execute echo prior to the completion of sleep. The user was notified of the status of the background process. This requirement is described in more detail in the Reporting job statuses section below.
Additionally, a job can be started in the foreground and later moved to the background (and vice versa). This is done through a combination of terminal control and job control builtin commands, both of which are detailed below
Polling Background Processes
With multiple jobs executing at the same time, it is infeasible to simply call wait(2) and expect everything to work. For example, if there is a background job that has completed while a foreground job is running, wait() will return due to the completion of the background job but not the foreground one. Moreover, penn-shell must wait on all completed jobs so that they do not become zombies. Worse, a job may create two background processes, each of which must be waited on.
penn-shell periodically polls for state changes of all background processes. Such polling consumes CPU cycles, so you should poll only once for each shell read and pass -1 as the pid argument to waitpid(2), e.g., waitpid(-1, &status, WNOHANG | WUNTRACED). You should poll the background jobs after reading user input but before parsing any input or running any command.
Terminal Control
In order to support background and foreground jobs, penn-shell is now responsible for delegating the terminal control. This becomes particularly important when jobs are isolated via process groups. For example, if a process group that is not in control of the terminal attempts to read from it, a SIGTTIN signal is delivered and the default action is to stop the process group.
This concept is best demonstrated with the cat program. When run with no arguments, cat will read from the standard input and write to the standard output. If cat is executed in the foreground, then it should be in control of the terminal. For example:
penn-shell> cat > file.txt
Hello World
^D
penn-shell>
Here, the user used cat as a mini-text editor by redirecting input to the file file.txt. However, if this were executed in the background:
penn-shell> cat > file.txt &
Running: cat > file.txt
penn-shell>
Stopped: cat > file.txt
penn-shell>
The cat command would be stopped because it attempted to read from the standard input when it was not in control of the terminal. A SIGTTIN would be generated, and its default action is to stop the process.
Although penn-shell should not change the disposition of SIGTTIN, it must stop background jobs that try to read from the standard input while allowing the foreground job to read from the standard input.
To do this, you will use the library function tcsetpgrp(3). We will provide a toy program that demonstrates how to use it with the lecture on terminal control.
Another consequence of having multiple process groups is handling the delivery of terminal signals. There are two that you should pay particular attention to: SIGINT, the signal generated by Ctrl-C; and SIGTSTP, the signal generated by Ctrl-Z.
penn-shell is responsible for properly relaying those signals to the foreground job. If you have proper process group isolation and terminal-control setup, then this should happen by default. In any event, penn-shell should never stop or exit due to the delivery of SIGTSTP or SIGINT. You should set up appropriate signal handlers to relay the signal to the right process group.
A sample of correct behaviors for terminal signals are as follows:
penn-shell> ^C
penn-shell> ^Z
penn-shell> sleep 100
^C
penn-shell> sleep 200
^Z
Stopped: sleep 200
penn-shell>
Job Control
penn-shell supports basic job control.
Job Control builtins
A shell builtin is a built-in command that runs as a function in the same process as the shell. That is, the builtin is not forked as a child process. penn-shell has three builtins to support job control: bg, fg, and jobs.
penn-shell has a notion of the current job. If there are stopped jobs, the current job is the most recently stopped one. Otherwise, it is the most recently created background job.
bgsyntax:bg [job_id]resumes in the background the stopped job identified byjob_id, which defaults to that of the current job. If the job is already running or no such job exists, bg throws an error.fgSyntax:fg [job_id]brings to the foreground the background job identified byjob_id, which defaults to that of the current job. If the background job is stopped,fgresumes it before bringing it to the foreground. If the job does not exist,fgthrows an error.jobsSyntax:jobsprints to the standard error all background jobs, sorted byjob_idin ascending order. If there are no background jobs, it simply returns without throwing an error. (See the example below for the format.)
The following example illustrates how these builtins work together:
penn-shell> jobs
penn-shell> sleep 100
^Z
Stopped: sleep 100
penn-shell> sleep 200 &
Running: sleep 200
penn-shell> jobs
[1] sleep 100 (stopped)
[2] sleep 200 (running)
penn-shell> bg
Running: sleep 100
penn-shell> jobs
[1] sleep 100 (running)
[2] sleep 200 (running)
penn-shell> fg
sleep 200
^C
penn-shell> jobs
[1] sleep 100 (running)
penn-shell>
Below is an example of running fg and bg with a job id:
penn-shell> sleep 100 &
Running: sleep 100
penn-shell> sleep 200 &
Running: sleep 200
penn-shell> jobs
[1] sleep 100 (running)
[2] sleep 200 (running)
penn-shell> fg 1
sleep 100
^Z
Stopped: sleep 100
penn-shell> jobs
[1] sleep 100 (stopped)
[2] sleep 200 (running)
penn-shell> bg 1
Running: sleep 100
penn-shell> jobs
[1] sleep 100 (running)
[2] sleep 200 (running)
penn-shell>
A builtin is meant to run as a single command, and standard input/output redirections and the trailing & (if any) will be ignored. If the builtin is the first command of a multi-process pipeline, the rest of the pipeline should be ignored.
Job Queue
penn-shell must maintain a queue of background jobs, identified by job id. You are allowed to use Penn-Vec or you may create a new data structure. The choice is yours.
You may find your vector implementation from Project 0 useful, and we suggest you make use of it!
Job ids start at 1 and must be unique. A job id must remain constant for the duration of the job. The job id for a new background job is always that of the most recently created background job plus 1.
Reporting Job Statuses
penn-shell must notify the user when a background job changes state. However, it should hold on to the notifications and print them right before prompting the user. The notifications should be printed to the standard error. The format for reporting job statuses is as follows:
- Stopping the foreground job:
Stopped: <pipeline> - Starting a job, or continuing a stopped job, in the background:
Running: <pipeline> - Continuing a stopped job in the foreground:
Restarting: <pipeline> - Bringing a running background job to the foreground:
<pipeline> - When a background job is completed:
Finished: <pipeline>
where <pipeline> is a job without the trailing & if any. For example:
penn-shell> sleep 1 &
Running: sleep 1
penn-shell> echo hello world
hello world
Finished: sleep 1
penn-shell> sleep 100
^Z
Stopped: sleep 100
penn-shell> bg
Running: sleep 100
penn-shell> fg
sleep 100
^C
penn-shell>
Non-interactive mode of penn-shell
Finally, the last step. penn-shell can run in either interactive or non-interactive mode. penn-shell runs in interactive mode by default: it prompts the user for input. However, it can also run in non-interactive mode by redirecting the standard input at invocation:
# cat script.txt
echo hello world
pwd
# ./penn-shell < script.txt
hello world
/root/workspace/26sp-project-1-<username>
#
By default, the standard input of penn-shell refers to the terminal. In the above non-interactive mode, it’s redirected to the script.txt file. You can use isatty(3) to check if the standard input is redirected.
Control Signals: Revisited
If penn-shell runs as an interactive shell and has control of the terminal, Ctrl-C and Ctrl-D have the behaviors described above, and Ctrl-Z has the same behavior as Ctrl-C. If penn-shell runs as a non-interactive shell, Ctrl-C terminates it and Ctrl-Z stops it.
Code Design
This is a large, complex assignment. You should organize your code in a reasonable way. It is not reasonable to have all your code in one file. Take the time to break your code into modules. You must place all .c and .h files in the top directory where the Makefile will be located, for the autograder Makefile to be able to locate it.
You should not need to modify the makefile unless directed by course staff. This also means that you should not make sub-folders inside of the penn-shell folder. Otherwise, the autograder will not work.
Since we use clang-tidy in this course, we enforce that your functions do not get too long or complex. You should modularize your code into helper functions to avoid deductions.
One design tip that we feel may be useful to follow is to minimze usage of global variables. You will need some, but try not to overdo it. Code with 50 global variables is very difficult to debug as the global state is difficult to keep track of.
If your code is unreasonably organized to an extent that it becomes difficult to reason about or read (e.g. everything is in one file or everything is in
main(), course staff has the right to tell you to reorganize your code first before helping you debug in office hours.We do this so that we can make best use of TA time helping students in office hours, and because breaking it into functions allows for students to more easily read and debug their own code.
clang-tidy and clang-format
The Makefile we provided with this assignment is configured to help make sure your code is properly formatted and follows good C coding conventions. To do this, we make use of two tools: clang-format and clang-tidy
To make sure your code identation is nice, we have clang format. All you need to do to use this utility, is to run make format, which will run the tool and indent your code propely. Code that is turned in is expected to follow the specified style, code that is ill-formated must be fixed and resubmitted.
clang-tidy is a more complicated tool. Part of it is checking for style and readability issues but that is not all it does. Examples of readability issues include:
Not using curly braces around if statements and loops:
if (condition) // clang-tidy will complain about missing curly braces
printf("hello!\n");
Declaring variables or parmaters with names that are too short:
void foo(char c) { // clang-tidy will complain about the name `c`
// does something
}
Having functions that are too complex and long. The tool calculates “cognitive complexity” of your code and will complain about anything that is too complex. This means you should think about how to break your code into helpers, because if you don’t, clang-tidy will complain and you will face a deduction. More on this specific error can be found here: Cognitive Complexity
One common thing that adds to cognitive complexity is to do the following:
bool foo(char* param) {
if ( !error1 ) {
if ( !error2 ) {
if ( !error3 ){
// do some computation
return true;
}
}
}
return false;
}
If you have a lot of nested code, then it contributes more to cognitive complexity. Rewriting the code as shown below will reduce the cognitive complexity.
bool foo(char* param) {
if ( error1 ) {
return false;
}
if ( error2 ) {
return false;
}
if ( error3 ) {
return false;
}
// do some computation
return true;
}
clang-tidy is also useful for noticing some memory errors and pointing out bad practices when writing C code. Because of all this, we are enforcing that your code does not produce any clang-tidy errors. You can run clang-tidy on your code by running: make tidy-check. Whenever you compile your code using make, you should also re-run clang-tidy to check your code for errors. Note that you will have to fix any compiler errors before clang-tidy will run (and be useful).
Code that has clang-format, clang-tidy or any compiler errors will face a deduction.
If you have any questions about understanding an error, please ask on Ed discussion and we will happily assist you.
Arguments to penn-shell
penn-shell accepts an optional argument --async, which is needed only for the extra credit. Also, it can optionally read from redirected standard input (See the Non-interactive mode of penn-shell section).
Terminal Signals
An interactive penn-shell should never exit or stop because of the following terminal signals: SIGTTIN, SIGTTOU, SIGINT, SIGQUIT, and SIGTSTP.
Parsing
This assignment is not about writing a parser. As such, we have provided a full parser so that you can focus on the other parts of penn-shell more relevant to an OS course. (See the setup section above)
You will have to handle the cases where the parser fails to read user input correctly. Be sure to print the word
invalidsomewhere in the error message that you print when the parser fails
Acceptable System Calls and Library Functions
In this assignment you may use only the following system calls and library functions.
strtol(3)clearerr(3),feof(3), andferror(3)close(2),dup2(2),open(2),pipe2(2)andpipe(2)execvp(3),fork(2), andwaitpid(2)_exit(2)andexit(3)fprintf(3),fputc(3),fputs(3),fwrite(3),printf(3),putc(3),putchar(3),puts(3), andwrite(2)free(3),realloc(3),calloc(3)andmalloc(3)getline(3)andread(2)getpgid(2),getpgrp(2), andsetpgid(2)getpid(2)andgetppid(2)isatty(3)kill(2)andkillpg(2)strcmp(3)andstrlen(3)perror(3)sigaction(2)sigsuspend(2)sigemptyset(3),sigfillset(3),sigdelset(3), andsigaddset(3)tcgetpgrp(3)andtcsetpgrp(3)
Pitfalls
Do not make the mistake of writing to stdin (file descriptor 0). Students have done this in the past and it becomes a headache to figure out where the output of the program went to. One way to verify if you are not writing to stdout is to redirect the output of the program to a file.
# ./penn-shell > output_of_penn_shell
Memory Errors and Leaks
You are required to check your code for memory errors. This is a nontrivial task, but an extremely important one. Code with memory leaks and memory violations will incur deductions. Fortunately, there is a very nice tool Valgrind that is available to help you. You must find and fix any bugs that Valgrind locates, but there is no guarantee it will find all memory errors in your code.
Error Handling
Most system calls and library functions report errors via the return value. You should check for errors on these functions.
Reusing Code
We do allow the reuse of code as long as the code was personally authored by you and must be cited. We do not allow, for example, copying and pasting CIS 2400 linked list boilerplate code into the project. Reuse of code not authored by you will result in a 0 for the project.
Developing Your Code
As before, you must develop your code using docker – not ENIAC!
Students who have been identified by CETS as developing this project on ENIAC will be penalized. Students who have been identified by CETS as responsible for launching a fork-bomb on ENIAC will receive a ZERO on this assignment.
What to turn in
You must provide each of the following for your submission, no matter what. Failure to do so may reflect in your grade.
- README file. In the README you will provide
- Your names and Pennkeys
- A list of submitted source files
- Overview of work accomplished
- Description of code and code layout
- General comments and anything that can help us grade your code
- A description of what tools you used (Generative AI / Textbooks / References / etc.). A deep description is not needed, but you should describe how you used the tool and what was completed using the tool. If you did not use any tools, please state so.
- Your code. You may think this is a joke, but people have forgotten to include it before.
Submission
Submission should be done through Gradescope.
First, organize your homework directory so that you have all the code and Makefile in a subdirectory called /penn-shell. Once you are done, your directory should look something like this
─26sp-cis5480-student1-student2
└─penn-shell
├─penn-shell.c
├─Job.h
├─parser.h
├─parser.c
├─any other .c or .h files you need
└─Makefile
After you are done, commit and push your code to Github.
Finally, you will be submitting your Github repo to Gradescope. On Gradescope, when prompted to submit your assignment, select the “Github” option from Submission Method. Be sure to also add your partner to the submission, this can be done after you submit your code. The auto-grader will run once submitted, and you may move on if you get a 100% on the auto-grader. You have unlimited submission attempts before the deadline, as well as during the late-day submission period.
Be sure to add your partner to the Gradescope submission when you submit.
Retrospective Meeting
The Friday and Saturday that penn-shell is due, your partner group will sign up to demo your project to discuss your project with 1 or 2 assigned TAs.
In this meeting each of you will be asked a few questions to verify that you learned the expected content from the assignment. For these questions, you should be familiar with your code base and be able to easily find which parts correlate to which functionality, but we will expect more than this. We do not just want you to have memorized the code you did for this project. Some (if not all) of these questions will be designed for you to take your understanding of the material and apply it to a new context/concept and see if you can synthesize an answer based on what you learned. The new concept will be related to what you have learned in the project, and is not intenged to be a “trap”. You can almost think of these like a “technical interview we want you to pass”. The TA(s) may nudge you in the correct direction, but you are expected to eventually get to the answer on your own.
More details on what will happen in these meetings and how these are scheduled will be released closer to the end of the project.
Grading Guidelines
Each team will receive a group grade for this project; however, individual grades may differ. This can occur due to lack of individual effort, or other group dynamics. The team Git repository will be monitored and used to determine individual contribution. In most cases, everyone on a team will receive the same grade. Below is the grading guideline.
- 7.5 points - Milestones
- 7.5 points - Documentation
- 5 points - clang-tidy
- 25 points - Style
- 70 points - Functionality
- 35 points - Retrospective Questions
Please note that general deductions may occur for a variety of programming errors including memory violations, lack of error checking, etc.
Your programs will be graded with docker and must execute as specified there. You may develop and test on other machines but you should ensure that your program functions properly with docker.
Attribution
The primary rule to keep you safe from plagiarism/cheating in this project is to attribute in your documentation any outside sources you use. This includes both resources used to help you understand the concepts, and resources containing sample code you incorporated in penn-shell. The course text need only be cited in the latter case. You should also use external code sparingly. Using most of an exam- ple from the pipe(2) man page is ok; using the ParseInputAndCreateJobAndHandleSignals() function from Foo’s Handy Write Your Own Shell Tutorial is not (both verbatim, and as a closely followed template on structuring your shell).
Optional Challange
Reap background zombies asynchronously
Don’t attempt this unless the baseline assignment can clear the autograder. You must submit exactly the same compressed tar file for both the regular and optional challange assignment. The autograder will add the --async option when grading the optional challange. The due date is the same as the baseline Penn Shell.
The SIGCHLD signal is ignored by default. In the regular credit, penn-shell polls the background jobs and reaps zombies if any. This may leave some zombies around indefinitely.
See the following example.

Here, we ran sleep 1 in the background. When it completed, sleep became a zombie, as indicated by the Z in the S column. It will stay in the zombie state1 until penn-shell gets around to reaping it when polling background processes the next time.
For this optional challange, you are to implement asynchronous SIGCHLD handling (with detailed documentation on how you implemented it). You must be able to handle signaling child processes on demand. You are not allowed to poll for child state changes. Instead, you will register a SIGCHLD handler. Remember, SIGCHLD is generated whenever a child changes state (e.g., running to stopped, running to exited, etc.), and this will invoke the signal handler.
You are required to add a command-line option --async to penn-shell. When run with this option, penn-shell will remove zombies asynchronously.
Before you submit penn-shell to the autograder, make sure you can pass the following basic test:
# penn-shell --async
penn-shell> sleep 5 &
Running: sleep 5
penn-shell> cat // within 5 seconds
Note that the // within 5 seconds is a comment stating to run just the command cat within 5 seconds, so that it starts before sleep 5 finishes.
Note that sleep 5 is not printed while cat is running. You are expected to print the completed job after the current running process is finished. While cat is waiting for input, run ps j in another terminal, and you should see no zombies:
# ps j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1004 1005 1005 1005 pts/0 12101 Ss 1000 0:00 -bash
1005 12095 12095 1005 pts/0 12101 S 1000 0:00 penn-shell --
12095 12101 12101 1005 pts/0 12101 S+ 1000 0:00 cat
12288 12289 12289 12289 pts/1 12347 Ss 1000 0:00 -bash
12289 12347 12347 12289 pts/1 12347 R+ 1000 0:00 ps j
#
For this optional challange, you can also use the following system calls and library functions:
sigprocmask(2)
