Shell Implementation - Suggested Step 2

In the previous step only (some) builtin commands were implemented and no child processes were created by the shell.

Next add to the shell the following functionalities:

  1. Error Checking for fg and bg (but no execution yet; just error checks)
  2. Create child process to execute non-builtin commands (but running in the foreground only; no background processes, yet).
  3. Wait for each child to finish (but NOT by calling waitpid)
  4. Completely remove the child process; that is, reap the child process so that it is completely removed from the Linux process table. (The SIGCHLD signal handler will call waitpid to do this after a child finishes.)

Contents

  1. Error checking for fg and bg
  2. Create Child Process to execute non-builtin commands
  3. Temporary waitfg implementation
  4. The sigchld_handler function

Error checking for fg and bg[top]

The do_bgfg function gets the array of command line arguments:

 void do_bgfg(char *argv[]);      
    

This function will only be called if argv[0] is either "fg" or "bg", but its argument might be incorrect or missing.

Missing argument

The argv array will contain a NULL entry at the index after the last actual command line argument. So argv[1] will be NULL if the argument for "fg" or "bg" is missing. If the command is "fg" and the argment is missing, you should print this message and return.

fg command requires PID or %jobid argument
    
Incorrect argument type

If the argument to "fg" or "bg" is NOT missing, it is in argv[1].

The argument should either be the pid (an integer) or %jid (% followed immediately by the job id integer). E.g.,

  fg 1400

or

  fg %2
    

But any other kind of argument would be an error. E.g.

  fg a

or

  fg [2]      
    

If the argument to "fg" is not a pid or %jid, then print this message and return.

fg: argument must be a PID or %jobid      
    
Correct argument type, but pid or job id doesn't exist

If the argument to fg is an integer, it should be the pid of an existing job. But you need to check if it is in the job table.

Use the provided helper function

struct job_t *getjobpid(struct job_t *jobs, int pid);
    

This function returns the NULL pointer if no job exists for pid. For example, if the argument to "fg" is 12345 and there is no job with pid = 12345,

 p = getjobpid(jobs, pid)
    

would return NULL.

In that case do_bgfg should print this message and return:

(12345): No such process      
    

Similarly, if the argument is of the form %jid, check if this jobs is in the job table with the function:

struct job_t *getjobjid(struct job_t *jobs, int jid);      
    

If the argument to "fg" is %2 but there is no job with jid = 2, then print this message and return:

fg %2: No such job      
    

Create Child Process to execute non-builtin commands [top]

If the command was not builtin,

  1. create a child process; using fork
  2. child executes; use execvp
  3. if execvp fails, child prints a message and exits.

    For example, if there is no program or command named "blob" and execvp fails, the child should print

    blob: Command not found	  
    	
  4. The shell should wait for the child by calling the waitfg function:

    void waitfg(pid_t pid);	  
    	

    See below for how to provide a temporary implementation of the waitfg function. (The implementation should NOT call waitpid.)

Temporary waitfg implementation[top]

The waitfg function should wait until the foreground process changes state (either terminates or is stopped) and then return.

But the comment before waitfg warns that you should not use the waitpid function for this, but just simply loop until the job state changes.

The only function that should call waitpid is the sigchld_handler.

However, at this step jobs are not being added to the job table where the job state is to be recorded and maintained by the shell.

But a temporary implementation of waitfg can be provided that doesn't call waitpid. Just call the pause function like this:

 pause();      
    

This will block until a signal is received. When the foreground process changes state, the SIGCHLD signal is sent to the shell. This will cause the pause function to return.

Don't try to run jobs in the background yet. This means there will be only one child process at a time. With this restriction, using the pause() function works to wait for that child.

Once background jobs are allowed, the implementation of waitfg will need to be implemented as stated in the waitfg comment.

The sigchld_handler function[top]

Recalling that signals are not queued, the sigchld_handler needs to loop calling waitpid until all children who have changed state (i.e., terminated or stopped) have been waited for. If there are additional children who have not yet changed state, the sigchld_handler should not wait for those children.

So the third parameter (options paramter) passed to waitpid needs to set the following in addition to the default of returning information when a child terminates:


Note:

The tsh.c program already has a nifty way of turning on/off print statements, which is useful for debugging.

For example, if you wanted to print a message for debugging purposes every time the sigchld_handler executes, include this code in sigchld_handler:

  if (verbose) {
	printf("... your message  ...\n");
  }
      

Then if you want debugging messages like this printed, start the shell with the -v option:

 tsh -v	
      

This option will set the value of verbose to be 1. Otherwise, without the -v option, the value of verbose is 0.

The global int variable verbose is already declared in the tsh.c file (outside any function) and so it can be used as above from any function in the tsh.c file.