QuestionQuestion

Transcribed TextTranscribed Text

Project 3 – Shell Project #3 : Shell 1. Introduction For this assignment, you are going to use C to implement a simple shell program called MASH (MAson SHell). Once running, MASH would be able to accept/execute commands from user and perform basic job management. This assignment will help you to get familiar with the principles of process management and job control in a Unix-like operating system. Our lectures on processes, signals, and Unix-IO as well as Textbook Ch8 (in particular 8.4 and 8.5) and 10.3 will provide good references to this project. 2. Project Overview A typical shell program receives line-by-line command input by the user from a terminal. The shell would support a set of built-in commands which will be executed directly by the shell. If the user input is not a built-in command, however, the shell will interpret the input as the name of a program to be executed, along with arguments to be passed to it. In that case, the shell will fork a new child process and execute the program in the context of the child. A shell program typically also provides job control. Normally, a user command (built-in or not) is executed as a foreground job, which means the shell will wait for the command to complete before reading the next command. But if the user command ends with an ampersand ‘&’, the command will be started in the background and the shell will return to the prompt immediately to accept the next command. Some built-in commands are usually provided by the shell for the user to view the list of background jobs or to switch a job between background and foreground. For this assignment, your shell implementation should be able to perform the following: • Accept a single line of command from user; • Execute a built-in command (detailed list of supported commands below); • Load and run the user specified program with the provided arguments; • Must support any number of jobs simultaneously (not limited to any system limitations); • Perform basic job control; • Support basic file redirection; We will describe each of them in more details with some examples below. Specifications Document: (Chapter 3 at the end has some guidance on starting your design) This document has of a breakdown of each of the features, looking at specific details, required logging, and sample outputs. This is an open-ended project that will require you to make your own design choices on how to approach a solution. Read the whole document before starting. 2 2.0 Use of the Logging Functions In order to keep the output format consistent, you must call provided logging functions at the appropriate times to generate the right output from your shell program. All logging output starts with a unique header that can help us to keep track of the activities of our shell. The generated output will also be used for grading. The files logging.c and logging.h provide the functions for you to call. Most of the log functions require you to provide a process ID (pid) and the relevant command line (cmd) to make the call. We will explain more details and specify how to use them below. 2.1 Prompt, Accepting, and Parsing User Commands Once start, the shell would print a prompt and wait for the user to input commands. Each line from the user is considered as one command. Logging Requirements: • You must call log_prompt() to print out the provided prompt. If a command line is empty, it should be ignored by the shell. Otherwise, each non-empty command must follow these (simplified) rules: • Every item in the line must be separated by one or more spaces; • It must start with either a built-in command or a name of the program to be executed; • Optionally, it could include any arguments that the user wants to supply; • Optionally, it might specify file redirection options but at most one file for input and at most one file for output; • Optionally, it might end with "&" to indicate the command should start a background job. void parse(char *cmd, char *argv[], Cmd_aux *aux); This is the required function to parse the user command into useful pieces. You must implement this function and call it after receiving the user input (called from main() provided to you). When it is called, cmd will contain the line typed in by the user, while argv and aux will be uninitialized pointers to the respective elements, as you can see in main(). • cmd: the line typed in by user WITHOUT the ending newline (\n). • argv: an array of NULL terminated char pointers with the similar format requirement as the one used in execv(). You set the value of this array in parse() to record the parsing result. o argv[0] should be either a built-in command, or the name of the program to be loaded and executed; o the remainder of argv[] should be the list of arguments used to run the program o argv[] must have NULL following its last argument member. • aux: the pointer to a Cmd_aux record which should also be used to record the additional information you extract from parsing. The detailed definition of the struct is as below. /* Cmd_aux Definition (see shell.h for details) */ typedef struct cmd_aux_struct{ char *in_file; /* input file name */ char *out_file; /* output file name */ int is_append; /* append to the output file or not */ int is_bg; /* background job or not */ }Cmd_aux; 3 Assumptions: You can assume that all user inputs are valid command lines (no need for format checking in your program). You can also assume that the maximum number of characters per command line is 100 and the maximum number of arguments per line is 50. Check shell.h for relevant constants defined for you. Implementation Hints: • If you use fgets() to receive user input, it will keep the ‘\n’ character in the buffer. You need to remove that before making the call to parse(). • When parsing the user input, make use of strtok() to handle the tokenizing of the inputs for you. All parts of the user input will be separated by a space delimiter. As you are tokenizing the input, think about the order of items that may be present. Your parser will need to handle the options for built-in commands, non-built-in commands with and without arguments, and each type of redirection. Remember, a command may use both redirection in and out in the same command! Examples of parse(): cmd argv aux in_file out_file is_append is_bg "help" {"help",NULL} NULL NULL -1 0 "sleep 200" {"sleep","200",NULL} NULL NULL -1 0 "sleep 200 &" {"sleep","200",NULL} NULL NULL -1 1 "ls -l > ls.txt &" {"ls","-l", NULL} NULL "ls.txt" 0 1 "wc < ls.txt >> wc.txt" {"wc", NULL} "ls.txt" "wc.txt" 1 0 After calling parse(), the provided shell.c leaves the design and implementation up to you as an open-ended project for you to solve. You are encouraged to write many helper functions as well and you may add additional code in to main as needed. A valid user command might be either a built-in command or a program to load in and execute. You must give the built-in command a higher priority in execution. For example, one of our supported built-in command is "help". If there is a program that happens to share the same name "help", it will be shadowed by the built-in command and will not be executed. 2.2 Non-Built-in Commands If the user command is not a shell built-in command, it must be interpreted as a program to be loaded in and executed by the shell on behalf of the user. It can be either a foreground or background job. The shell program must fork a process to take the job for both cases. When your shell starts a foreground job, it must wait for the job to complete before showing the prompt to user and accepting the next command. When a background job gets started, however, the shell does not need to wait and can immediately accept the next command. Paths When you execute these commands using execv or execl, you will also need to know the full path of the command to satisfy its first argument. To generate this, you will need to check two different paths for each command. These are: "./" and "/usr/bin/". Both of these paths must be checked, in this order, for an entered command. 4 For example, if the user enters the command "ls -al", you will try both "./" and "/usr/bin/" as the path argument to execv or execl, in that order. Check the error code on execv or execl to see if the path was not found before checking the next one. If neither path option leads to a valid program, then you handle it as a path error and issue the appropriate log function. argv[0] Since the path argument of execv or execl needs to be modified from the original command by concatentating in "./" or "/usr/bin/", you will simply keep the original command name as argv[0]. So, if the user inputs "ls -al", then your path may be either "./ls" or "/usr/bin/ls" depending on which one works, but your argv[0] will still need to be "ls", which is what the user typed in. Assumptions: You can assume only the commands listed below will be used for testing/grading: • Non-Built-In Commands that will be used in testing (with and without arguments, with and without redirection): sleep, ls, pwd o Note: sleep works by recording the time it started. This means if you suspend and resume it, it will check to see if x seconds have passed since starting. § sleep only checks if x seconds have passed since you started it. § So, if you use sleep 5, then ctrl-Z 1 second into the run, wait 30 seconds, and then resume it, it will see at least 5 seconds have elapsed since it started and will immediately quit, even though it only ‘ran’ for 1 second. • Non-Built-In Commands that will be used in testing (either with a file argument or with redirection in from a file, with/without redirection out to a file): wc , cat (eg. wc < file.txt, wc file.txt > out.txt, cat file.txt, cat < in.txt >> out.txt) • Your shell does NOT need to support a program that need exclusive access to terminal (e.g. vi) or that reads from terminal (e.g. wc without a file input). Logging Requirements: • For foreground jobs, you must call log_start_fg(pid, cmd) to report the start of the job. • For background jobs, you must call log_start_bg(pid, cmd) to report the start of the job. • If the program cannot be loaded and executed, you must call log_command_error(cmd) to report the error then immediately exit the process. Implementation Hints: • You can use fork() to create a new child process; • You can use either execl() or execv() to load a program and execute it in a process. • Even though execl or execv do not normally return, they will return with a -1 value if there are any errors, such as the path or command not being found. Use the man pages for the command you wish to use to see the details and think about how to handle them. Remember to check both valid paths (./ and /usr/bin) with each command before considering it an error. Example Run (Command Error): 5 2.3 Basic Built-in Shell Commands A typical shell program supports a set of built-in command. If a built-in command is received, the shell process must execute that directly without forking any additional process. For this assignment, your shell program must support the following built-in commands: • help: when called, your shell should print on terminal a short description of the shell including a list of built-in commands and their usage. Logging Requirements: o You must call log_help() to print out the predefined information. • quit: when called, your shell should terminate. Logging Requirements: o You must call log_quit() to print out the predefined information. o You will then need to exit your shell program. • fg/bg/jobs/kill: These are described in the Job Control section. Assumptions: • You can assume that the user will never end a built-in command with "&". • You can assume that the user will never request file redirection with a built-in command. • You can assume there are no nonterminated background jobs when quit command is given. Example Run (Built-in help/quit): 6 2.4 Job Control and Relevant Built-in Commands Your shell might have multiple jobs running concurrently: 0 or 1 foreground job; 0 or more background jobs. Your shell needs to maintain the record for the foreground job (if there is any) and a list of background jobs that are not terminated. Simple job control tasks include: • On start, every background job gets assigned a positive integer job ID: if there is no other non-terminated background job, it gets job ID 1; otherwise, it takes the next integer that is higher than any non-terminated job IDs. • On start, the foreground job gets a dummy temporary job ID 0. If the foreground job is switched to background for the first time, it will be assigned a positive job ID, following the same rules as above. • A job can be switched between background and foreground (see details below). Regardless of the switching, once assigned a positive job ID, a job keeps the same job ID until it is terminated. • You will need to update your record/list of background jobs if there is any status change (process stopped, terminated, continued, etc) or if there is any switching between foreground and background jobs. • Some details need to be included in your record for each job, including the assigned job ID, the process ID, the execution status of the job, and the initial command line that starts the job (without the ending newline "\n"). You can assume that the execution state is either "Running" or "Stopped", Some additional built-in commands related to job control must be supported. • jobs: When the user inputs jobs command, your shell should print on terminal the list of background jobs with the job ID, process ID, running state, and initial command line. • Logging Requirements: o You must first call log_job_number(num_jobs) to report how many background jobs are currently alive (not terminated). o For each of the job, you must then call log_job_details(job_id, pid, state, cmd) to report the job ID, process ID, execution state (either "Running" or "Stopped"), and the original command line that triggered this job. o If there are multiple background jobs, they should be printed in the ascending order of their job IDs. • Implementation Hints: o A SIGCHLD will be sent to parent process when there is a status change to a child process (stopped, terminated, continued). You can use waitpid() to specify which situations you want to monitor. You can also check the status of involved child process using different macros (WIFEXITED, WIFSTOPPED, WIFSIGNALED, WIFCONTINUED, etc). o You can use sigaction() to override the default signal handling and define what actions to take when a signal arrives. See the Appendix to get more details. 7 Example Runs (Built-in/jobs): • kill SIGNAL PID: when called, your shell should send a signal specified by SIGNAL to the process with a process id matching PID. • Logging Requirements: o You must call log_kill(signal, pid) to report kill command has been received and activated. • Implementation Hints: o You can use kill() to send a signal to a particular process. • Assumptions: o You can assume only these signals will be used in testing/grading: 2(SIGINT), 9(SIGKILL), 18(SIGCONT), 19(SIGSTOP). Example Runs (Built-in/kill): 8 • fg JOBID: when called, your shell should switch the specified background job to be foreground and then wait until it completes. If the job has been previously stopped, resume its execution after moving it back to foreground and the status should be "Running". • Logging Requirements: o You must call log_job_fg(pid, cmd) to report which background process has been switched to be foreground. o If the specified job ID is invalid (i.e. cannot be located in the list of background jobs), you must call log_jobid_error(job_id) to report the issue. No change should be made if JOBID is invalid. • Implementation Hints: o You can use kill() to send signals to a particular process. Example Runs (Built-in/fg): • bg JOBID: when called, your shell would resume the execution of a background job with the specified JOBID. No change should be made if provided JOBID is invalid or if the specified job is already actively running. • Logging Requirements: o You must call log_job_bg(pid, cmd) to report command bg has been applied to which background job. o If the specified job ID is invalid (i.e. cannot be located in the list of background jobs), you must call log_jobid_error(job_id) to report the issue. • Implementation Hints: o You can use kill() to send signals to a particular process. Example Runs (Built-in/bg): 9 Assumptions: • You can assume that the user will never end a built-in command with "&". • You can assume that the user will never request file redirection with a built-in command. • You can assume kill/bg/fg commands typed in by the user are always well-formatted. The only errors that you need to report are the jobid_errors as described above. 2.5 Keyboard Interaction A number of keyboard combinations can trigger signals to be sent to the group of foreground jobs. Note that by default, the signal triggered by those keyboard combinations will be sent to the whole foreground process group, which includes both the shell program and its foreground job. Your program must change that default behavior to make sure the signal only affects the foreground job, not the shell program itself. The shell program will need to forward the signals to the appropriate process. For this assignment, you need to support two keyboard combinations: • ctrl-c: A SIGINT(2) should be sent to the foreground job to terminate its execution. • ctrl-z: A SIGTSTP(20) should be sent to the foreground job to first pause its execution and then switch it to be a background job. • If there is no foreground job when these combinations are input, they should be ignored (i.e. they should not affect the execution of the shell program or any background jobs). Logging Requirements: • You must call log_ctrl_c() to report the arrival of SIGINT triggered by ctrl-c. • You must call log_ctrl_z() to report the arrival of SIGTSTP triggered by ctrl-z. Assumptions: • You can assume that SIGINT signals received by the shell process are only triggered by ctrlc; and that SIGTSTP signals received by the shell process are only triggered by ctrl-z. Implementation Hints: • You can use setpgid() to change the group ID of a process. See the Appendix of this document for more details. • You can use sigaction() to change the default response to a particular signal; 10 • Your shell program can use kill() to send/forward the received signal to another process. Example Runs (Keyboard ctrl-c / ctrl-z): Example Runs (Keyboard ctrl-c / ctrl-z with no Foreground Job): 2.6 File Redirection If the user specifies a file to be used as the input and/or output of a program, your shell needs to redirect the standard input and/or standard output of that program (process) to the specified file(s). Logging Requirements: • On failure of opening the file, you must call log_file_open_error(file_name) to report the issue. Once you log the error, immediately exit the process. Implementation Hints: • You can use open() to open files; • If a file is created by open(), it takes a third argument to set the reading/writing/executing permission of that file. To simplify the task, make sure to use 0600 as the third argument of open() if needed. It will typically set the file to be readable and writable by the owner of the file only. • You can use dup2() to change the standard input/output if the call to open succeeded; o If the open fails (eg. File not found), you can skip the dup2 and exit immediately. Assumptions: • We will not use the same file for both input redirection and output redirection. Example Runs (File Error): 11 Project 3 – Shell Example Runs (Non-bulit-in Commands w/ File Redirection): 2.7 Signal Handling There are various signals that you might want to override the default handler and specify what actions to take. You will need to define signal handlers to help implementing the tasks discussed above. Logging Requirements: • For multiple possible status changes of a child process, you must call the corresponding logging function to report the change. All those logging functions require the process ID pid and the original command line cmd of the involved child process. They are summarized in the table below. Foreground or Background Job Status Change Logging Function to Call Foreground RunningàTerminated log_job_fg_term(pid, cmd) RunningàTerminated (by a signal) log_job_fg_term_sig(pid, cmd) RunningàStopped (will soon switch to background, used for ctrl-z) log_job_fg_stopped(pid, cmd) StoppedàRunning (used for fg) log_job_fg_cont(pid, cmd) Background RunningàTerminated log_job_bg_term(pid, cmd) Running/StoppedàTerminated (by a signal) log_job_bg_term_sig(pid, cmd) RunningàStopped log_job_bg_stopped(pid, cmd) StoppedàRunning log_job_bg_cont(pid, cmd) Implementation Hints: • Signals that you need to monitor and handle include: SIGCHLD, SIGINT, and SIGTSTP; 12 • You can use sigaction() to register those handlers. Note: the textbook introduces the usage of signal(), which has been deprecated and replaced by sigaction(). Check the Appendix of this document to get the basic idea and example usage of sigaction(). • It’s recommended not to use any stdio functions in a signal handler. If you need to print any debug statements to the, use write() with STDOUT_FILENO directly. You can check the provided logging.c for examples (e.g. log_job_fg_term()). 2.8 Race Conditions You will start to experience the fun and challenges of concurrent programming in this assignment. In particular, if your design includes a global job list, be alert that race conditions might occur. The typical race is between the shell process updating the list when start or move jobs and signal handler updating the same list when a process changes its status (termination, continuation etc). For example, the following sequence is possible if no synchronization is provided: 1. The shell(parent) starts a new job (child process) and the newly created child gets to run; 2. Before the parent gets the chance to add the job into the list, the child process terminates and triggers a SIGCHLD to parent; 3. SIGCHLD handler is executed but does not see the job in list and cannot do anything; 4. After the handler completes, the shell (parent) resumes normal execution and adds the job into the list (when it is too late!). One approach that we recommend is to block SIGCHLD signal (and other signals that might trigger the updates of the global list) before the call to fork() and unblock them only after the job has been added into the list. Both blocking and unblocking of signals can be implemented with sigprocmask(). Also notice that children inherit the blocked set of their parents, so you must unblock them in the child before calling execl() or execv(). There might be other similar situations that you need to temporarily block signals to ensure the updates to the global list are well synchronized. 3. Getting Started First, get the starting code (project3_handout.tar) from the same place you got this document. Once you un-tar the handout on Zeus (using tar xvf project3_handout.tar), you will have the following files in the p3_handout directory: • shell.c – This is the only file you will be modifying (and submitting). There are stubs in this file for the functions that will be called by the rest of the framework. Feel free to define more functions if you like but put all of your code in this file! • shell.h – This has some basic definitions and includes necessary header files. • logging.c – This has a list of provided logging functions that you need to call at the appropriate times. • logging.h – This has the prototypes of logging functions implemented in logging.c. • Makefile – to build the assignment (and clean up). 13 • hello.txt – a simple textual file for convenient testing (of file redirection). • my_pause.c – a C program that can be used as local program to load/execute in shell. It will not terminate normally until SIGINT has been received for three times. Feel free to edit the C source code to change its behavior. • slow_cooker.c – a C program that can be used as local program to load/execute in shell. It will slowly count down from 10 to 0 then terminate normally. You can specify a different starting value and/or edit the C source code to change its behavior. To get started on this project, read through the provided code in shell.c and the constants and definitions in shell.h and logging.c. The first thing you want to include in your design is how to parse out the cmd_line to get the different pieces and put them into the structure and into argv[]. A good hint is to write a lot of supporting functions to make this easier to test. Once you have some basic parsing in, add creating and running a simple program with no arguments into the project as a foreground process. Once you have this in your design, add the various features, such as arguments, different paths, and redirection. From here, look at how you would handle keyboard commands (ctrl-c and ctrl-z) on this foreground process; this will add signal handling into your design. Finally, add in background support. This will involve having to come up with a design for the job system to let you track background processes and handling all the SIGCHLD events (stopped/resumed/terminated child process events all send a SIGCHLD). This also requires you to design how you will handle background and foreground job tracking, moving jobs between the foreground and background, and handling suspend and resume events. After this, make sure all of the details in each section of this document are met, such as all of the required logging is present (this is critically important – all grading is done from these log calls), that you have all the cases handled, and that all features are incorporated. The more modular you make your design, the easier it will be to debug, test, and extend your code. 15 Appendix: Useful System Calls Here we include a list of system calls that perform process control and signal handling. You might find them helpful for this assignment. The system calls are listed in alphabetic order with a short description for each. Make sure you check our textbook, lecture slides, and Linux manual pages on zeus to get more details if needed. • int dup2(int oldfd, int newfd); o It makes newfd to be the copy of oldfd; useful for file redirection. o Textbook Section 10.9 o Manual entry: man dup2 • int execv(const char *path, char *const argv[]); • int execl(const char *path, const char *arg, ...); o Both are members of exec() family. They load in a new program specified by path and replace the current process. o Textbook Section 8.4 o Manual entry: man 3 exec • void exit(int status); o It causes normal process termination. o Textbook Section 8.4 o Manual entry: man 3 exit • pid_t fork(void); o It creates a new process by duplicating the calling process. o Textbook Section 8.4 o Manual entry: man fork • int kill(pid_t pid, int sig); o Used to send signal sig to a process or process group with the matching pid. o Textbook Section 8.5.2 o Manual entry: man 2 kill • int open(const char *pathname, int flags); • int open(const char *pathname, int flags, mode_t mode); o Opens a file and returns the corresponding file descriptor. o Textbook Section 10.3 o Manual entry: man 2 open • int setpgid(pid_t pid, pid_t pgid); o It sets the group id for the running process. o Textbook Section 8.5.2 o Manual entry: man setpgid • int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); o Used to change the action taken by a process on receipt of a specific signal. 16 o Textbook Section 8.5.5 (pp.775) o Manual entry: man sigaction • int sigaddset(sigset_t *set, int signum); • int sigemptyset(sigset_t *set); • int sigfillset(sigset_t *set); o The group of system calls that help to set the mask used in sigprocmask. o sigemptyset() initializes the signal set given by set to empty, with all signals excluded from the set. o sigfillset() initializes set to full, including all signals. o sigaddset() adds signal signum into set. o Textbook Section 8.5.4 o Manual entry: man sigsetops • int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); o Used to fetch and/or change the signal mask; useful to block/unblock signals. o Textbook Section 8.5.4, 8.5.6 o Manual entry: man sigprocmask • unsigned int sleep(unsigned int seconds); o It makes the calling process sleep until seconds seconds have elapsed or a signal arrives which is not ignored. Note it measures the elapsed time by absolute difference between the start time and the current clock time, regardless whether the process has been stopped or running. o Textbook Section 8.4.4 o Manual entry: man 3 sleep • pid_t waitpid(pid_t pid, int *status, int options); o Used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. o Textbook Section 8.4.3 o Manual entry: man waitpid • ssize_t write(int fd, const void *buf, size_t count); o It writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd. The standard output can be referred to as STDOUT_FILENO. o Textbook Section 10.4 o Manual entry: man 2 write 17 Appendix: Process Groups Every process belongs to exactly one process group. • pid_t getpgrp(void); // #include <unistd.h> • The getpgrp() function shall return the process group ID of the calling process. When a parent process creates a child process, the child process inherits the same process group from the parent. • int setpgid(pid_t pid, pid_t pgid); // #include <unistd.h> • The setpgid() function shall set process group ID of the calling process. In particular, a process can call setpgid(0,0) to create a new process group with itself in the group. 18 Appendix: System call sigaction() The sigaction() system call is used to change the action taken by a process on receipt of a specific signal. The sigaction() function has the same basic effect as signal() but provides more powerful control. It also has a more reliable behavior across UNIX versions and is recommended to be used to replace signal(). • int sigaction (int signum, const struct sigaction *action, struct sigaction *old-action) // #include <signal.h> o signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP o If action is non-NULL, the new action for signal signum is installed from action. It could be the name of the signal handler. o If old-action is non-NULL, the previous action is saved in old-action. Example program: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void sigint_handler(int sig) { /* signal handler for SIGINT */ write(STDOUT_FILENO, "SIGINT\n", 7); exit(0); } int main(){ struct sigaction new; /* sigaction structures */ struct sigaction old; memset(new, 0, sizeof(new)); new.sa_handler = sigint_handler; /* set the handler */ sigaction(SIGINT, &new, &old); /* register the handler for SIGINT */ int i=0; while(i<100000){ /* this will loop for a while */ fprintf(stderr, "%d\n", i); /* break loop by Ctrl-c to trigger SIGINT */ sleep(1); i++; } return 0; }

Solution PreviewSolution Preview

These solutions may offer step-by-step problem-solving explanations or good writing examples that include modern styles of formatting and construction of bibliographies out of text citations and references. Students may use these solutions for personal skill-building and practice. Unethical use is strictly forbidden.

/* Constants */
//static const char *shell_path[] = {"./", "/usr/bin/", NULL};
static const char *shell_path[] = {"./", "/bin/", NULL};
static const char *built_ins[] =
{"quit", "help", "kill", "jobs", "fg", "bg", NULL};

#define RUNNING 0
#define FOREGROUND 1
#define BACKGROUND 2
#define STOPPED 3
#define TERMINATED 4


struct processNode
{
    int pid;
    int jid;
    int state;
    int inBackground;
    char command[MAXLINE];
    struct processNode *next;
};
typedef struct processNode node;

/* Feel free to define additional functions and update existing lines */
int allPids[10000];
int inBackground = 0;
int numActivePids = 0;
int numKilledPids = 0;
int exitCommand = -5;

node *head = NULL;

typedef void s_handler(int);
void displayJobs(node *);
int getFGProcess();
char* joinCommandString(char *[]);
void handleKill(char *argv[]);
void handleFG(char *argv[]);
void handleBG(char *argv[]);
void handleBGFG(char *argv[], int);
s_handler* signals(int signum, s_handler *signalHandler);

void waitFunction(int signum)
{
wait(NULL);
//pid_t pid = wait(NULL);
    //log_job_bg_term(pid, "");
}

void handleCtrC(int signum)
{
//signal(SIGINT, handleCtrC);
log_ctrl_c();
node *tmp = head;
while(tmp != NULL) {
if (tmp->pid > 0)
{
if (tmp->state == FOREGROUND) {
tmp->state = TERMINATED;
//printf("%d %d\n", tmp->pid, tmp->state);
kill(tmp->pid, SIGQUIT);
//waitpid(tmp->pid, NULL, 0);
if (waitpid(tmp->pid, &exitCommand, 0) < 0) {
kill(tmp->pid, SIGKILL);
}
break;
}
}
tmp = tmp->next;
}
}

void handleCtrZ(int signum)
{
log_ctrl_z();
node *tmp = head;
while(tmp != NULL) {
if (tmp->pid > 0)
{
if (tmp->state == FOREGROUND)
break;
}
tmp = tmp->next;
}

if (tmp != NULL) {
if (tmp->pid > 0 && tmp->state != TERMINATED) {
tmp->state = STOPPED;
kill(tmp->pid, SIGSTOP);
//waitpid(tmp->pid, NULL, 0);
}
}
}

void handleChildProcess(int signum)
{
//signal(SIGINT, handleCtrC);
int status = -1;
pid_t pid;

//printf("%s\n", "Hello1");

while((pid = waitpid(-1, &status, WNOHANG|WUNTRACED)) > 0) {

if (WIFSIGNALED(status)) {

//printf("%s %d\n", "WIFSIGNALED", pid);
node *tmp = head;
while(tmp != NULL) {
if (tmp->pid > 0)
{
if (tmp->pid == pid) {
//printf("%s %d\n", "WIFSIGNALED", pid);
tmp->state = TERMINATED;
//log_job_fg_term_sig(tmp->pid, tmp->command);
break;
}
}
tmp = tmp->next;
}

} else if( WIFSTOPPED(status) ){
//printf("%s\n", "Hello4");
//printf("%s %d\n", "WIFSTOPPED", pid);
node *tmp = head;
while(tmp != NULL) {
if (tmp->pid > 0)
{
if (tmp->pid == pid) {
tmp->state = STOPPED;
//printf("%s %d\n", "WIFSTOPPED", pid);
log_job_fg_stopped(tmp->pid, tmp->command);
break;
}
}
tmp = tmp->next;
}
if (tmp != NULL) {
if (tmp->state == FOREGROUND) {

}
else if (tmp->state == BACKGROUND) {
log_job_bg_term_sig(tmp->pid, tmp->command);
}
}
} else if (WIFEXITED(status)) {
//printf("%s %d\n", "WIFEXITED", pid);
node *tmp = head;
while(tmp != NULL) {
if (tmp->pid > 0)
{
if (tmp->pid == pid) {
//printf("%s %d %d\n", "WIFEXITED", pid, tmp->state);

if (tmp->state == BACKGROUND) {
//printf("%s %d %d\n", "WIFEXITED", pid, tmp->state);
tmp->state = TERMINATED;
log_job_bg_term(tmp->pid, tmp->command);
//printf("\n");
log_prompt();
}
else if (tmp->state == TERMINATED) {
log_job_bg_term(tmp->pid, tmp->command);
} else {
//tmp->state = TERMINATED;
//log_job_fg_term(tmp->pid, tmp->command);
}

break;
}
}
tmp = tmp->next;
}

}
}

}

void handleBGFG(char *argv[], int bgFg) {
int jobNum = atoi(argv[1]);
node *tmp = head;
while(tmp != NULL) {
if (tmp->pid > 0)
{
if (tmp->jid == jobNum) {
break;
}

}
tmp = tmp->next;
}

if (tmp == NULL) {
log_jobid_error(jobNum);
} else {
if (bgFg == 1) {
log_job_bg(tmp->pid, tmp->command);
tmp->state = RUNNING;
kill(tmp->pid, SIGCONT);
}
if (bgFg == 0) {
log_job_fg(tmp->pid, tmp->command);
tmp->state = FOREGROUND;
kill(-tmp->pid, SIGCONT);
int status;
do {
    waitpid(tmp->pid, &status, WUNTRACED);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
}
}

void handleQuit(int signum)
{
//signal(SIGINT, handleCtrC);
exit(1);
}

node *new_node()
{
node *newNode = (node *) malloc(sizeof(node));
newNode->next = NULL;
newNode->pid = 0;
newNode->inBackground = 0;
return newNode;
}

node *insert(node *head, node *newNode)
{
if (head == NULL)
{
head = newNode;
} else
{
node *tmp = head;
while (tmp != NULL && tmp->next != NULL)
{
tmp = tmp->next;
}
tmp->next = newNode;
}
return head;
}


s_handler* signals(int signum, s_handler *signalHandler) {
struct sigaction s_action, s_prev_action;
s_action.sa_handler = signalHandler;
sigemptyset(&s_action.sa_mask);
//s_action.sa_flags = ERESTART;
sigaction(signum, &s_action, &s_prev_action);
return s_prev_action.sa_handler;
}

void handleKill(char *argv[]) {
int signal = atoi(argv[1]);
int pid = atoi(argv[2]);
node *tmp = head;
while(tmp != NULL) {
if (tmp->pid == pid)
{
break;
}
tmp = tmp->next;
}

//int stat;
log_kill(signal, pid);
if (tmp != NULL) {
if (tmp->pid > 0) {
tmp->state = TERMINATED;
kill(tmp->pid, SIGQUIT);
//wait(&stat);
}
}

}...

By purchasing this solution you'll be able to access the following files:
Solution.zip.

$75.00
for this solution

or FREE if you
register a new account!

PayPal, G Pay, ApplePay, Amazon Pay, and all major credit cards accepted.

Find A Tutor

View available C-Family Programming Tutors

Get College Homework Help.

Are you sure you don't want to upload any files?

Fast tutor response requires as much info as possible.

Decision:
Upload a file
Continue without uploading

SUBMIT YOUR HOMEWORK
We couldn't find that subject.
Please select the best match from the list below.

We'll send you an email right away. If it's not in your inbox, check your spam folder.

  • 1
  • 2
  • 3
Live Chats