Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

CIS 3800 - Project 0: penn-shredder

Fall 2023

Goals

Refamiliarizing yourself with C programming (Pointers, output parameters, etc.)

Introduction to processes & the Unix Terminal

Collaboration

This is an individual assignment. You may not work with others. 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.

Overview

In this assignment you will implement a basic shell named that will restrict the runtime of executed processes. Your shell will read input from the user and execute it as a new process, but if the process exceeds a

timeout, it will be terminated. You will complete this assignment using only a specified set of Linux system calls and standard C library functions.

Setup

Task 1. Register github account with the course and setup development environment.

For this assignment, you need to setup and/or register a github account with the course, and setup a development

environment. Please follow the instructions below to setup your account:

Github creation & registration

Enivronment & Git repo setup

Once you have your environment setup and cloned the repo, download the provided makefile here: Makefile Alternatively, you can use the following command in your docker container to get the file:

wget https://www.seas.upenn.edu/~cis3800/23fa/code/00/Makefile

Specification

At its core, a shell is a simple loop. Upon each iteration, the shell 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

at the prompt. Your shell, , will do all of that, but with a slight twist.

takes a command line argument that specifies a timeout for the user-entered program. If any program runs for longer than the specified timeout, will terminate the program and report to the user his snide  catch-phrase, “Bwahaha … Tonight, I dine on turtle soup!” . The following is an example of input that should invoke the   catch-phrase:

$ penn-shredder 2

penn-shredder# /bin/sleep 3

Bwahaha ... Tonight, I dine on turtle soup!

penn-shredder# /bin/sleep 1

penn-shredder#

Here, penn-shredder was executed with a timeout argument of 2 seconds, and any program that runs longer than 2 seconds will be terminated (or shredded). The sleep program with an argument of 3 seconds was terminated after

exceeding the timeout. Conversely, sleep  1 completes before the timeout, without upsetting Shredder and his henchmen.

We will give you the Makefile used by the autograder. In your C source code, you should use the two macros

PROMPT and CATCHPHRASE  defined in the Makefile instead of defining your own macros. See the skeleton code below.

read , fork, exec , wait , and repeat!

As described previously, a shell is just a loop performing the same procedure over and over again. Essen- tially, that procedure can be completed with these system calls:

·  write(2) : write the prompt to stderr

·  read(2) : read input from stdin into a buffer

· fork(2) : create a new process that is a copy of the current (parent) process

·  execve(2) : replace the current (child) process with another

·  wait2) : wait for a child process to finish before proceeding Your program, in skeleton code, should look like this:

while (true) {

write(STDERR_FILENO, PROMPT, ...);

read(STDIN_FILENO, ...);

pid = fork();

if (pid == 0) {

execve(...);

perror(...);

exit(EXIT_FAILURE);

}

if (pid > 0)

wait(...);

}

You should spend some time carefully reading the man pages for all these system calls. To do so, in a terminal enter:

$ man 2 read

where 2 specifies the manual section. If you do not specify the manual section, you may get information for a different read command. For system calls, you want to use section 2 of the man pages, for c library functions, you want to use  section 3.

Timing is Everything

To time a running program, your shell will use the alarm(2) (the 2 indicates that it is in section 2 of the man pages) system call, which simply tells the operating system to raise a SIGALRM signal after a specified time. Your shell may need to catch the signal to avoid being terminated by the signal.

To handle a signal, a signal handing function must be registered with the operating system via the signaL2) system  call. When the signal is delivered, the operating system will interrupt your shell ’s current operations (e.g., waiting for the program to nish).

The kiLL2) system call sends a specified signal to a specified process. Despite its morbid name, it will only kill (or terminate) a program if the right signal is delivered. One such signal which will always do just that is SIGKILL . This    signal has the special property that it cannot be caught or ignored, so no matter what program your shell executes, it must heed the signal.

Prompting and I/O

Your shell must prompt the user for input. See , , and repeat section above . Following the prompt,  your shell will read input from the user. The system call read(2) will return when the user presses Enter or ctrl-D .

The maximum line length is 4095 bytes (plus the terminating newline character if the user pressed Enter). It ’s recommended that you call read(2) as follows

const int maxLineLength = 4096;

char cmd[max_line_length];

int numBytes = read(STDIN_FILENO, cmd, max_line_len);

If the user presses ctrl-D after typing something, you should print a newline before executing the program or re- prompting as if the Enter key were pressed.

Argument Delimeters

The user-entered program may have arguments which are whitespace (space and tab) delimited. There may be leading and trailing whitespaces from the user input. For example:

penn-shredder#  /bin/sleep      10

Executing a program

Your shell may only use the execve(2) system call to execute a program. This system call instructs the operating system to replace the current running program – that would be the child of your shell – with the specified program.  Please refer to the manual for more details.

The execve(2) system call is the base of a larger collection of functions; however, you may not use those other

functions. Specifically, you may not use execl(3) , execv(3)), or any other function listed in the execc3) man page. As a result, the user of your shell must specify the path to a program to execute it. To learn where a program lives (i.e.,    its path), use the which program in bash.

Since the number of arguments varies from program to program, you are not allowed to declare a fixed- size array for

the second argument of execve() . Instead, you must count the number of arguments for each user-entered program and use malloc(3) to allocate just enough memory for the array. You should not invoke a system call if it ’s simple to

determine from the internal state that the system call would do nothing, and you should monitor the status of any forked child process. (For example, your shell should ignore empty or all-whitespace lines instead of always calling fork(2) .)

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-shredder should respond to Ctrl-C by following their normal behavior on SIGINT , but penn-shredder 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):

$ penn-shredder

penn-shredder# /bin/cat

ˆC

penn-shredder# ˆC

penn-shredder# sleepˆC

penn-shredder# /bin/sleep 1

penn-shredder#

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 (beacuse there is nothing else to read), and for most programs (including penn-shredder) the program exits gracefully.

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 tpyes Ctrl-D, your shell should try to

process the user input, print a newline character, and DO NOT exit.

Arguments to

Your penn-shredder program must be able to take an optional argument: the execution timeout. If the value of the   argument is 0, then penn-shredder imposes no time restriction on child processes. For example, an optional argument of 10 results in a timeout of 10 seconds:

$ penn-shredder 10

Omitting the optional argument results in no timeouts. You must error out if the value of the argument is negative or

more than one argument is provided. In either case, penn-shredder should return a nonzero ( EXIT_FAILURE) status via returning it from mai or via the exit() or __exit() functions.

Error Handling

Most system calls and library functions report errors via the return value. You must check for errors for those with an asterisk in the Acceptable System Calls and Library Functions section below.

Code Organization

Sane code organization is critical for all software. Your code should not be all in one giant penn-shredder.c file.

Rather, you should create reasonable modules and expose the relevant interfaces and constants in  .h files. Make sure that your source code ( .c and  .h files) is in the top-level directory and can be built by the autograder ’s Makefile

using the following command:

$ make -B

Your code should adhere to DRY (Don ’t Repeat Yourself). If you are writing code that is used in more than one place, you should most likely write a function or a macro.

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. Below is a sample run of penn-shredder in valgrind:

$ valgrind ./penn-shredder 2

== 151614== Memcheck, a memory error detector

== 151614== Copyright C) 2002-2017, and GNU GPL’d, by Julian Seward et al.

== 151614== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info == 151614== Command: ./penn-shredder 2

== 151614==

penn-shredder# sleep

sleep: No such file or directory

== 151627==

== 151627== HEAP SUMMARY:

== 151627== in use at exit: 0 bytes in 0 blocks

== 151627== total heap usage: 3 allocs, 3 frees, 1,512 bytes allocated

== 151627==

== 151627== All heap blocks were freed -- no leaks are possible

== 151627==

== 151627== For lists of detected and suppressed errors, rerun with: -s

== 151627== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

penn-shredder# /bin/sleep

/bin/sleep: missing operand

Try ’/bin/sleep --help for more information.

penn-shredder# /bin/sleep 3

Bwahaha ... Tonight, I dine on turtle soup!

penn-shredder# /bin/sleep 1

penn-shredder# cat ˆC

penn-shredder# /bin/cat

Bwahaha ... Tonight, I dine on turtle soup!

penn-shredder# ˆC

penn-shredder# /bin/cat

ˆC

penn-shredder#

== 151614==

== 151614== HEAP SUMMARY:

== 151614== in use at exit: 0 bytes in 0 blocks

== 151614== total heap usage: 6 allocs, 6 frees, 112 bytes allocated