Saturday, March 7, 2015

Linux shell implementation.

Source Code

A fairly simple implementation of a Linux shell in C++(don't expect anything clever on the code,I'm quite rusty in C++).

Allows directory change,execution of commands and pipeline(2 commands), redirection of input/output and background execution.The symbols used are the common ones(cd,|,>,>>,<,&).

Round Robin algorithm is used to handle the background processes.

Supported commands examples:
  • cd "new/dir/" to change directory.
  • "command"
  • "command&"
  • "dir/to/command/command"
  • "command1 | command2"
  • "command1 | command2&"
  • "command1file"
  • "command>>file"
  • "command>file"
  • "command<file"
  • "exit"

Handling input-output redirection for Linux commands in C++

Opening the file and duplicating the file descriptor for input.

Throws errno if either of those operations failed.
Retuns the file descriptor.
int startRedirectIn(const char *file){
  int in;
  in=open(file,O_RDONLY);
  if(in<0){
    throw errno;
  }
  int dup=dup2(in,0);
  if(dup<0){
    close(in);
    throw errno;
  }
  return in;
}

Opening/creating the file and duplicating the file descriptor for output.

Throws errno if either of those operations failed.
append is used to distinguish between ">" and ">>"
Retuns the file descriptor.

int startRedirectOut(const char *file,bool append){
  int out;
  if(append){
    out=open(file,O_WRONLY | O_CREAT | O_APPEND);
  }else{
    out=open(file,O_WRONLY | O_CREAT);
  }
  if(out<0){
    throw errno;
  }
  int dup=dup2(out,1);
  if(dup<0){
    close(out);
    throw errno;
  }
  return out;
}

Closing a file descriptor/stopping the redirection.

void stopRedirect(int rdr){
  if(rdr!=-1){
    close(rdr);
  }
}


This is part of the LinuxShell project.

Implementing a pipeline for two commands in Linux from C++

Executes the first command and redirects it's output to the second.

False waitExecution causes the the first command to be executed at the time of the call and the second when the background handler allows it.
redIn refers to the first command and redOut to the second.

If the command parameters do not contain "/" the executable is searched in PATH.

Throws errno in case of failed pipelining,failed forking,failed redirection when waitExecution is true and failed background handling and initiation of the new process.

redIn,redOut should be NULL if no redirection is required,append should be true if output needs to be redirected to a file,appened at the end.

Check LinuxShell for missing methods and complete source code.

void pipe(const char *command1, char *const args1[],const char *command2, char *const args2[],bool waitExecution,const char *redIn,const char* redOut,bool append){

  int pipeDesc[2];
  bool error=false;
  if(pipe(pipeDesc)<0){
    throw errno;
  }
  pid_t pid1,pid2;
  int in=-1,out=-1;
  if((pid1=fork())==0){//first command handling
    dup2(pipeDesc[1],1);
    close(pipeDesc[0]);
    close(pipeDesc[1]);
    if(redIn!=NULL){
      try{
        in=startRedirectIn(redIn);
      }catch(int ex){
        exit(ex);
      }
    }
    stopRedirect(in);
    execvp(command1,args1);
    exit(1);
  }
  if(pid1==-1){//fork error
    throw errno;
  }
  if((pid2=fork())==0){//second command handling
    dup2(pipeDesc[0],0);
    close(pipeDesc[0]);
    close(pipeDesc[1]);
    if(redOut!=NULL){
      try{
        in=startRedirectOut(redOut,append);
      }catch(int ex){
        exit(ex);
      }
    }
    stopRedirect(out);
    execvp(command2,args2);
    exit(1);
  }
  if(pid2==-1){//fork error
    throw errno;
  }
  close(pipeDesc[0]);
  close(pipeDesc[1]);

  if(waitExecution){
    int status;
    pid_t ws=wait(&status);
    if(ws!=-1){//first command is executed
      if(WIFEXITED(status)){
        int exitStatus=WEXITSTATUS(status);
        if(exitStatus!=0){
          kill(pid2,SIGKILL);
          throw exitStatus;
        }
      }
    }else{// error while executing the first command
      throw errno;
    }
    ws=wait(&status);
    if(ws!=-1){//second command is executed
      if(WIFEXITED(status)){
        int exitStatus=WEXITSTATUS(status);
        if(exitStatus!=0){
          throw exitStatus;
        }
      }
    }else{// error while executing the second command
      throw errno;
    }
  }else{// waitExecution is false
    int status;
    pid_t ws=wait(&status);
    bool pid1Finished=true;
    if(ws!=-1){//first command is executed
      if(WIFEXITED(status)){
        int exitStatus=WEXITSTATUS(status);
        if(exitStatus!=0){
          kill(pid2,SIGKILL);
          throw exitStatus;
        }
      }
    }else{// error while executing the first command
      throw errno;
    }
    if(kill(pid2,SIGSTOP)==0){
      backgroundProcs.push_back(pid2);
    }else{
      throw errno;
    }
  }


}

Executing a command in Linux from C++

Executes the command using the arguments defined in the second parameter.

If the command parameter does not contain "/" the executable is searched in PATH.

If waitExecution is true the function waits for the process to be finished.

Throws errno in case of failed forking,failed redirection when waitExecution is true and failed background handling and initiation of the new process.

redIn,redOut should be NULL if no redirection is required,append should be true if output needs to be redirected to a file,appened at the end.

Check LinuxShell for missing methods and complete source code.

void execute(const char *command, char *const args[],bool waitExecution,const char *redIn,const char* redOut,bool append){
  
  pid_t pidChild;
  pidChild = fork();
if (pidChild== 0) {//executed in child
  int in=-1,out=-1;
  if(redIn!=NULL){
    try{
      in=startRedirectIn(redIn);
    }catch(int ex){
      if(waitExecution){
        throw ex;
      }else{
        exit(ex);
      }
    }

  }
  if(redOut!=NULL){

   try{
    out=startRedirectOut(redOut,append);
  }catch(int ex){
    if(waitExecution){
      throw ex;
    }else{
      exit(ex);
    }
  }
}

stopRedirect(in);
stopRedirect(out);
execvp(command,args);
exit(1);


}else if (pidChild < 0) {//fork failed
  throw errno;
}
else{// executed in parent
  if(waitExecution){
   int childExitStatus;
   if(wait(&childExitStatus)!=-1){
    if(WIFEXITED(childExitStatus)){
      int exitStatus=WEXITSTATUS(childExitStatus);
      if(exitStatus!=0){
        throw exitStatus;
      }
    }else{
      throw errno;
    }
  }
}else{
  if(kill(pidChild,0)==0){
    if(kill(pidChild,SIGSTOP)==0){
      backgroundProcs.push_back(pidChild);
    }else{
      throw errno;
    }
  }else{
    throw errno;
  }


}

}
}

Round Robin algorithm in C++

The function bellow should be called using a timer with the desired interval, for example a signal timer like this.

vector<pid_t> backgroundProcs;

void backgroundHandler(){
  static bool locked=false;
  if(locked){
    return;
  }

  locked=true;
  static int running=-1;
  if(backgroundProcs.size()==0){//no processes
    locked=false;
    return;
  }
  int killStatus;
  // Pauses the current process and checks if it is finished
  if(running!=-1 ){
    pid_t pid=backgroundProcs.at(running);
    killStatus=kill(pid,SIGSTOP );
    try{
     int exitError=-1;
     if(isFinished(pid,exitError)){
      if(exitError!=-1){
        printError(exitError);
      }else{
        printPidFinished(pid);
      }
      backgroundProcs.erase(backgroundProcs.begin()+running);
      if(backgroundProcs.size()==0){
        running=-1;
        locked=false;
        return;
      }
    }
  }catch(int ex){
    // Unexpected exception caused on runtime
    // Probably rare since calls on the try block don't usually throw exceptions
    // and backgroundProcs is only accessed by one instance due to the synchronization
    // of the function with the locked variable.
    // No action is required
  }
}
// Picks the next process,loop ends when a process is successfully picked
// or all the processes in the vector are actually finished.
// There is one loop unless there are finished processes in the vector.
do{
  if(running>=backgroundProcs.size()-1){
    running=0;
  }else{
    ++running;
  }
  pid_t pid=backgroundProcs.at(running);
  if(kill(pid,0)==0){
    kill(backgroundProcs.at(running),SIGCONT);
    break;
  }else{
    backgroundProcs.erase(backgroundProcs.begin()+running);
    if(backgroundProcs.size()==0){
      running=-1;
      locked=false;
      return;
    }
  }
}while(true);
locked=false;
}

This is part of the LinuxShell project.

Simple signal timer in C++


#define INTERVAL 1000
struct itimerval it_val; 
  if (signal(SIGALRM, signalRecieved) == SIG_ERR) {
    printError("Error catching SIGALRM.");
    exit(1);
  }
  it_val.it_value.tv_sec =     INTERVAL/1000;
  it_val.it_value.tv_usec =    (INTERVAL*1000) % 1000000; 
  it_val.it_interval = it_val.it_value;
  if (setitimer(ITIMER_REAL, &it_val, NULL) == -1) {
    printError("Error calling setitimer.");
    exit(1);
  }

void signalRecieved(int sig){
 //handle signal
}

This is part of the LinuxShell project.