Simple Shell in C/C++ and Java


Table of Contents
1. C/C++ Shell
    a. Documentation
    b. Source Code
    c. Sample Output
2. Java Shell
    a. Documentation
    b. Source Code
    c. Sample Output


C/C++ Shell


Introduction
The purpose of this document is to document my design choices when implementing the Clone Shell program. I will first split the discussion to briefly explain how parallel and sequential execution was implemented and then I will go into further detail about how timeout was incorporated into this and the trials/alternative options. An outline of what I’ve done is included at the end as well as links to references.


2a. Sequential Execution:
I first created an array of pids of size count. Within an if-else statement, I determined whether the program is going to run in parallel or sequentially. Within the sequential execution code block, I begin by creating a for-loop that iterates over count. Then within the for loop, I have an if statement that forks a new child process. In the same if statement, I equate the forked process to zero. If that is true, which it should be, then it is a child process. The process is then executed by calling execvp, the timeout is implemented (please refer to the timeout section below the section for Parallel Execution), and the program waits (waitpid) for the child process that was forked to terminate. The for-loop then starts again with an incremented i and will loop through until i is equal to count. In summary, sequential execution creates a process and waits for it to terminate before creating a new process.


Parallel Execution:
If the program is not running sequentially, it is running in parallel. I use a for-loop to iterate over count. I then fork a count number of child processes, and begin their execution (by calling execvp). After doing do, I exit out of the loop and in another for-loop I wait for the child processes to finish. In summary, I create and start the execution of all the child processes and only after doing so do I start calling waitpid() on them.


Timeout:
Timeout is implemented by having a child process fork three new processes. The child process, pid[i], forks the execute_pid, sleep_pid, and the exited_pid. The execute_pid executes the process by calling execvp. While the process is executing, the sleep_pid sleep for “timeout” amount of seconds. The exited_pid waits for either the sleep_pid or the execute_pid to finish. Whichever one finishes first is set to equal the exited_pid. If the excute_pid finished first, then the process execution completed on time. We then give a kill call to the sleep_pid to terminate it. Otherwise, the processed timedout-- indicating that the sleep_pid “woke up” before the execute_pid could finish execution. A kill call is then given to the execute_pid to terminate it. For each case, a corresponding status message is printed out to the console.

time_t/clock_t with WNOHANG:
From gnu.org, I discovered that there is a WNOHANG constant that allows the parent process to not wait for the child process. I decided that I would record the elapsed time of a process and if the elapsed time becomes greater than or equal to the timeout time, then I will call waitpid with this constant.

My first two attempt in recording elapsed time was using the time_t and clock_t class (http://www.cplusplus.com/reference/ctime/time_t/). My attempt in calculating the elapsed time was like so:

time_t start,end;
time(&start)
//execvp(..);
time(&end)
double elapsed = difftime(start, end);

and

clock_t start, end;
start=clock();
//execvp(..);
end=clock()
double elapsed = (end-start)/CLOCKS_PER_SEC)

However, these two classes failed to effectively record the elapsed time. The result printed was either 0 or a number less than 0. Knowing that this couldn’t possibly be correct, I began researching different ways to record elapsed time.


Alternative Options:
Not knowing another approach to record elapsed time, I took to Google. Unfortunately, Googled returned many sites that used time_t and clock_t to record time. From my previous attempt, I knew that these two classes didn’t work for me, though I am not exactly know why. Finally I stumbled upon a Stack Overflow page (http://stackoverflow.com/questions/282176/waitpid-equivalent-with-timeout) that discussed a few possible ways to record elapsed time without using time_t and clock_t. There was a discussion on:

1. self-pipes
2. alarm & signals
3. child process sleep “count” (creating a process to “count” the seconds using sleep)

I didn’t understand self-pipes so I decided to not attempt that approach. I knew about alarms and how to use it, but I wasn’t so familiar with signals. Therefore, I decided to try the process “sleep” count method first. If that method failed, I would attempt the alarm/signal approach.


General Idea/Outline:
When clone shell runs, it takes in three inputs from the user: count, parallel or sequential, and timeout. From this information, I first declare an array of pids of size count. Using the conditional if-else statements, I separated the code for parallel execution from that of sequential execution.
If sequential execution was selected, then the program goes into a for loop that iterates count number of times (the number of processes that have to be created). For each iteration, a child process is forked and its ID is printed out. A new child process (execute_pid) is then forked within this child process that will execute the command line command. Two other child process (sleep_pid and exit_pid) is forked almost immediately after the 2nd child. One of the newly spawned processes sleeps for timeout seconds, and the other waits for either the 2nd child (execute_pid) process or the 3rd (sleep_pid) child process to finish. If the 2nd child process finish first, then the process finished. Otherwise, the process timed out. The corresponding message is displayed on the command line. We then wait for the 1st child (the one whose ID was printed) process to finish.
If parallel execution was selected, then the program goes into a for loop that iterates count number of times spawning a new child processes ( pid[i] ) which in turns, spawns three of its own child processes (execute_pid, sleep_pid, exit_pid). The three new child processes operate the same way as they did for sequential execution. The for loop is then closed and a new for loop is declared to wait on all the count number of child processes that were created in the previous for loop. As a result, all the child processes are made first and have started execution (running in parallel) before waitpid is called on it.


References:
GNU pid_t (http://www.gnu.org/software/libc/manual/html_node/Process-Identification.html)
waitpid/wait (http://www.gnu.org/software/libc/manual/html_node/Process-Completion.html)
Stack Overflow timeout (http://stackoverflow.com/questions/282176/waitpid-equivalent-with-timeout)


Source Code

closh.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>

#define TRUE 1
#define FALSE 0

// tokenize the command string into arguments - do not modify
void readCmdTokens(char* cmd, char** cmdTokens) {
  cmd[strlen(cmd) - 1] = '\0'; // drop trailing newline
  int i = 0;
  cmdTokens[i] = strtok(cmd, " "); // tokenize on spaces
  while (cmdTokens[i++] && i < sizeof(cmdTokens)) {
    cmdTokens[i] = strtok(NULL, " ");
  }
}

// read one character of input, then discard up to the newline - do not modify
char readChar() {
  char c = getchar();
  while (getchar() != '\n');
  return c;
}

void timeOut(int timeout, pid_t execute_pid, pid_t sleep_pid){
  /*A new process is created that sleeps for "timeout" seconds. After doing so
    it exits*/
  if ((sleep_pid = fork()) == 0) {
      sleep(timeout);
      exit(0);
  }
  //exited_pid is created which just waits for a process to end. 
  pid_t exited_pid = wait(NULL);
    /*if the first process to end is the execute_pid, the process that is
        executing the user inputed command, then a successful process termination
        message is displayed and the sleep pid is terminated. Otherwise, a timeout 
        message is printed and the the execute_pid is killed and we wait for the 
        sleep_pid to terminate*/
  if (exited_pid == execute_pid){
    printf("Child Process ID %d terminated \n", getpid());
    kill(sleep_pid, SIGKILL);
  }
  else {
    printf("Child Process ID %d Timedout\n", getpid());
    kill(execute_pid, SIGKILL);
  }
  wait(NULL); // waits for any process to finish
}


// main method - program entry point
int main() {
  char cmd[81]; // array of chars (a string)
  char* cmdTokens[20]; // array of strings
  int count; // number of times to execute command
  int parallel; // whether to run in parallel or sequentially
  int timeout; // max seconds to run set of commands (parallel) or each command (sequentially)

  while (TRUE) { // main shell input loop

    // begin parsing code - do not modify
    printf("closh> ");
    fgets(cmd, sizeof(cmd), stdin);
    if (cmd[0] == '\n') continue;
    readCmdTokens(cmd, cmdTokens);
    do {
      printf("  count> ");
      count = readChar() - '0';
    } while (count <= 0 || count > 9);
    printf("  [p]arallel or [s]equential> ");
    parallel = (readChar() == 'p') ? TRUE : FALSE;
    do {
      printf("  timeout> ");
      timeout = readChar() - '0';
    } while (timeout < 0 || timeout > 9);
    // end parsing code



    //This creates an array of processes of size count
    pid_t pid[count], execute_pid, sleep_pid;
    int i; //some into to iterate in a for loop
    
    //Sequential Execution code. 
    if (!parallel){ 
      for (i = 0; i < count; i++){ //loops until i == count
        if ((pid[i] = fork()) == 0){ //forks a new child process

          //Prints out child process id
          printf("Child Process ID %d \n", getpid());

          /*This is where processes are executed
              A new process is created which will execute the user inputed
                command by calling execvp.
              If execvp(...) is < 10, then there is an error and an error message
                is printed and we use exit(1).
              Otherwise, we use exit(0)-- normal exit
          */
          if ((execute_pid = fork()) == 0) 
            if (execvp(cmdTokens[0], cmdTokens) < 0) {
              printf("Can't execute %s\n", cmdTokens[0]);
              exit(1);
            }
          
          //This is where timeout is implemented. Refer to the timeOut() method
          timeOut(timeout, execute_pid, sleep_pid);
          }
          //This is where pid[i] is terminated 
          waitpid(pid[i], 0, 0);
      }
    }

    //Parallel Execution
    else{
      //In this for loop we fork count number of child processes at once. 
      for (i = 0; i < count; i++){
        if ((pid[i] = fork()) == 0){
          printf("Child Process ID %d \n", getpid());

          /*This is where processes are executed
              A new process is created which will execute the user inputed
                command by calling execvp.
              If execvp(...) is < 10, then there is an error and an error message
                is printed and we use exit(1).
              Otherwise, we use exit(0)-- normal exit
          */
          if ((execute_pid = fork()) == 0) 
            if (execvp(cmdTokens[0], cmdTokens) < 0) {
              printf("Can't execute %s\n", cmdTokens[0]); // only reached if running the program failed
              exit(1);
            }
          
          //This is where timeout is implemented. Refer to the timeOut() method
          timeOut(timeout, execute_pid, sleep_pid);
        }
        exit(1);
      }
        
        /*After the previous for loop, all the child processes that are created
          are running in parallel. In this for loop, we shall wait for all the
          "count" number of processes to terminate.*/
        for (i = count-1; i >= 0; i--)
          waitpid(pid[i], 0, 0);
    }
  }
}

Sample Output

1x-vl915-72-19-125-100:Part2 xianchen$ ./closh
closh> ./sleephello
  count> 3
  [p]arallel or [s]equential> s
  timeout> 5
Child Process ID 4916 
Hello World
Child Process ID 4916 terminated 
Child Process ID 4920 
Hello World
Child Process ID 4920 terminated 
Child Process ID 4924 
Hello World
Child Process ID 4924 terminated 
closh> ./sleephello
  count> 3
  [p]arallel or [s]equential> p
  timeout> 5
Child Process ID 4928 
Child Process ID 4929 
Child Process ID 4930 
Hello World
Hello World
Hello World
Child Process ID 4929 terminated 
Child Process ID 4928 terminated 
Child Process ID 4930 terminated 
closh> ./sleephello
  count> 3
  [p]arallel or [s]equential> s
  timeout> 1
Child Process ID 4940 
Child Process ID 4940 Timedout
Child Process ID 4944 
Child Process ID 4944 Timedout
Child Process ID 4948 
Child Process ID 4948 Timedout
closh> ./sleephello
  count> 3
  [p]arallel or [s]equential> p
  timeout> 1
Child Process ID 4953 
Child Process ID 4952 
Child Process ID 4954 
Child Process ID 4952 Timedout
Child Process ID 4953 Timedout
Child Process ID 4954 Timedout
closh> ls
  count> 1
  [p]arallel or [s]equential> s
  timeout> 3
Child Process ID 4964 
closh   closh.c   sleephello
Child Process ID 4964 terminated 
closh> hostname
  count> 9
  [p]arallel or [s]equential> s
  timeout> 5
Child Process ID 4992 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 4992 terminated 
Child Process ID 4995 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 4995 terminated 
Child Process ID 4998 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 4998 terminated 
Child Process ID 5001 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 5001 terminated 
Child Process ID 5004 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 5004 terminated 
Child Process ID 5007 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 5007 terminated 
Child Process ID 5010 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 5010 terminated 
Child Process ID 5013 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 5013 terminated 
Child Process ID 5016 
1x-vl946-128-119-250-120.wireless.umass.edu
Child Process ID 5016 terminated 
closh> date
  count> 9
  [p]arallel or [s]equential> p
  timeout> 3
Child Process ID 5022 
Child Process ID 5023 
Child Process ID 5021 
Child Process ID 5025 
Child Process ID 5027 
Child Process ID 5031 
Child Process ID 5033 
Child Process ID 5024 
Child Process ID 5029 
Thu Oct  3 21:44:35 EDT 2013
Thu Oct  3 21:44:35 EDT 2013
Child Process ID 5027 terminated 
Thu Oct  3 21:44:35 EDT 2013
Child Process ID 5022 terminated 
Thu Oct  3 21:44:35 EDT 2013
Thu Oct  3 21:44:35 EDT 2013
Thu Oct  3 21:44:35 EDT 2013
Child Process ID 5033 terminated 
Thu Oct  3 21:44:35 EDT 2013
Thu Oct  3 21:44:35 EDT 2013
Child Process ID 5021 terminated 
Child Process ID 5029 terminated 
Child Process ID 5024 terminated 
Child Process ID 5025 terminated 
Child Process ID 5023 terminated 
Thu Oct  3 21:44:35 EDT 2013
Child Process ID 5031 terminated 
closh> sdf
  count> 1
  [p]arallel or [s]equential> p
  timeout> 3
Child Process ID 5052 
Can't execute sdf
Child Process ID 5052 terminated 


Java Shell


Introduction
The purpose of this document is to document my design choices when implementing the simple Java Shell program. This document will outline which classes were used, how exceptions are handled, and the overall thought process in developing this shell. References are listed at the end.


General Idea/Outline:
I started creating this program by declaring a while loop. Shell usually doesn’t stop running until a user quits out of it. Therefore, if a user does not exit, by typing in “exit” as a command , CTRL-D in a terminal like program, or x out, the Java shell will continue running. While(true) is the simplest way to do this. Within the loop, the program asks for a user input (using Scanner) and then if statements are used to check the user input. If the input is “exit”, the program terminates. If nothing is entered, the program ask the user again for an input. If the user input is an actual command, a runtime object is created as well as a (child) process (by calling .exec()). A BufferedReader is created after and it takes in a character stream from the process that was just created. The BufferedReader then reads in the stream line by line until it reaches null. After this, the process will wait for the (child) process to finish and terminates it. The while loop is then closed and we start again from the beginning where the user is prompted to enter in a command


References:
Oracle Buffered Reader (http://docs.oracle.com/javase/7/docs/api/java/io/BufferedReader.html)
InputStreamReader (http://docs.oracle.com/javase/7/docs/api/java/io/InputStreamReader.html)
Process (http://docs.oracle.com/javase/7/docs/api/java/lang/Process.html)
Runtime (http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html)
Scanner (http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Scanner.html)


Source Code

JShell.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;


public class JShell {
  
  public static void main (String[] args){
    while (true){ // begin loop
      /*System.out.println("Simple Shell in Java: \n Enter a command to execute \n 
        Type 'exit' to terminate the program.\n");*/
      System.out.print("mysh> ");  //prompt
      Scanner scan = new Scanner(System.in); //takes in a command line input
      String input = scan.nextLine(); //the input is taken as a String

      String run = null; 

      //If a user types in "exit" at the mysh> prompt, the program will terminate
      if (input.equals("exit")){
        System.out.println("Program Terminated");
        System.exit(0);
      }
      //If a user enters nothing, the program returns to the prompt "mysh>"
      if (input.equals(""))
        continue;

      // attempts to take in IO
      try { 
        //Creates a runtime object
        Runtime rtime = Runtime.getRuntime();
        /*Creates a child process and try to execute the command the user input 
          in line 18 (Scanner) */
        Process one = rtime.exec(input);
        /*InputStreamReader gets a byte stream from the process and changes it to chars
        BufferedReader reads in the character Stream*/
        BufferedReader reader = new BufferedReader(new InputStreamReader(one.getInputStream()));

        //BufferedReader is read line by line
            while ((run = reader.readLine()) != null)
              System.out.println(run);
            
            /*We now wait for the program to finish running. At first used: one.destroy(); 
              but it just kills them so wait is more appropriate */
            try { 
              one.waitFor();
            }
            //If there is some error in terminating a program, we display error message
            catch (InterruptedException e) { 
              System.out.println("Process terminated with an error");
            }
                
      } catch (IOException e) { //If there is an IO error
        System.out.println("This command does not exist.");
        //e.printStackTrace();
      }
      //Prints a new line. For Aesthetic reasons
      System.out.println("");
    }
  }

}

Sample Output

1x-vl915-72-19-125-100:Part1 xianchen$ javac JShell.java
1x-vl915-72-19-125-100:Part1 xianchen$ java JShell
mysh> ls
JShell.class
JShell.java

mysh> mkdir HelloWorld

mysh> ls
HelloWorld
JShell.class
JShell.java

mysh> hostname
1x-vl946-128-119-250-120.wireless.umass.edu

mysh> rmdir HelloWorld

mysh> ls
JShell.class
JShell.java

mysh> 
mysh> 
mysh> sdfdsf
This command does not exist.


mysh> date
Thu Oct  3 21:46:12 EDT 2013

mysh> cal
    October 2013
Su Mo Tu We Th Fr Sa
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

mysh> exit
Program Terminated