Search code examples
c++fork

How to stop the first child process from being executed?


Aim: To design a linux shell, which shows a prompt to take input from user, creates a new process to execute that command then terminates/exits the process. Here is my code

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

using namespace std;

string cmd; //global string so cmd copied to child to execute    

void HandleAsParent(){
    cout<<"Linux Shell 1.0\n";
    string s;
    while (!exitflag) {
        cout<<"myShell>";
        getline(cin,cmd); //Take user input
        fork();
        wait(NULL);
    }
}

void HandleAsChild(){
    cout<<"Executing";
    system(cmd.c_str());
}

int main() {
    pid_t p = fork();
    if(p != 0){
        HandleAsParent(); //This is parent process
    }
    else {
        HandleAsChild(); //This is child process
    }
}

The problem is that, because of the first fork() call in the main,

myShell>Executing

is displayed on the first line when the program runs instead of just

myShell>

. I am able to understand why this is happening but cannot figure out how do I stop that first child process from being executed. Please suggest me workarounds/solutions to my problem.

Edit 1: This is one of my Assignment(for learning UNIX Processes) questions, and It is clearly stated that the program " prompts the user for a command, parses the command, and then executes it with a child process "


Solution

  • As I already guessed, system() probably uses a combination of fork(), exec() and wait(). Out of curiosity, I googled for source code and found one on woboq.org: glibc/sysdeps/posix/system.c.

    This in mind, using system(), the required child process "comes for free". So, I got this minimal sample:

    #include <iostream>
    
    void callCmd(const std::string &cmd)
    {
      system(cmd.c_str());
    }
    
    int main()
    {
      std::cout << "My Linux Shell 1.0\n"
        << "Type exit[Enter] to exit.\n";
      for (;;) {
        std::cout << "> ";
        std::string input; std::getline(std::cin, input);
        if (input == "exit") return 0;
        callCmd(input);
      }
    }
    

    Compiled and tested on cygwin on Windows 10:

    $ g++ -std=c++11 -o mycroShell mycroShell.cc 
    
    $ ./mycroShell 
    My Linux Shell 1.0
    Type exit[Enter] to exit.
    > echo "Hello"
    Hello
    > exit
    
    $
    

    After getting this running, the system() call in callCmd() can be replaced by fork()/exec()/wait() without the necessity to change anything else.


    A simplified version could look like this:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    void callCmd(const std::string &input)
    {
      // the pre-processing: split the input into command and arguments
      std::string cmdArgs = input;
      std::vector<char*> args;
      char *cmd = &cmdArgs[0];
      args.push_back(cmd);
      for (char *c = cmd; *c; ++c) {
        if (*c == ' ') {
          *c = '\0'; args.push_back(c + 1);
        }
      }
      args.push_back(nullptr); // append terminator
      // simple replacement of system() (not that sophisticated)
      int ret = fork();
      if (ret < 0) { // failure
        std::cerr << "Failed to execute '" << cmd << "'!\n";
      } else if (ret == 0) { // child
        execvp(cmd, args.data());
      } else { // parent
        waitpid(ret, nullptr, 0);
      }
    }
    
    int main()
    {
      std::cout << "My Linux Shell 1.1\n"
        << "Type exit[Enter] to exit.\n";
      for (;;) {
        std::cout << "> ";
        std::string input; std::getline(std::cin, input);
        if (input == "exit") return 0;
        callCmd(input);
      }
    }
    

    Compiled and tested on cygwin on Windows 10 again:

    $ g++ -std=c++11 -o mycroShell mycroShell.cc 
    
    $ ./mycroShell
    My Linux Shell 1.1
    Type exit[Enter] to exit.
    > /usr/bin/echo "Hello"
    "Hello"
    > exit
    
    $
    

    Notes:

    1. IMHO, the most tricky part of this is to prepare a proper argument vector for execvp.

    2. I tried with echo "Hello" as well and it worked. This surprised me a bit as echo is a bash built-in command. I assume that it found /usr/bin/echo and used it as well as in my above output.

    3. The error handling is rather poor – something which should be extended for serious applications.