Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Executing and verifying successful process execution in Linux

5.00/5 (8 votes)
8 Aug 2017CPOL6 min read 13K   3  
This article is about launching a secondary process from within a process and verifying the successful launch.

Introduction

In linux process can be launched using fork and exec however since exec replaces the process image completely, it is often not possible to notify the parent process that a secondary process has been successfully started. We will use pipes to help us here where the pipe will be automarically close on a successful call to exec and on error we will simply write the error code through the pipe and the parent will read the error code and print out the error message accordingly.

Using the code

First step is to include the necessary headers

C++
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <fcntl.h>

The fork and exec calls as well as the pipe creation function are all present in unistd.h and the flags used for the pipes are in fcntl.h. The rest of the headers are just for convenient input and output methods. 

Next step is to declare our variables for the process.

int pipe_status, exec_status, fork_status, pipe_descriptors[2];

Now we create the pipe and to do that we use the pipe2 function. It takes a pointer to an int which essentialy points to our array as the first argument and the pipe creation flags as the second argument. We will set the flags to CLOSE-ON-EXEC for our situation that essentially means the pipe will be automatically closed on a successful call to exec.

pipe_status = pipe2(pipe_descriptors, O_CLOEXEC);

On error pipe2 returns -1 and errno is set accordingly to show the error that caused the creation failure.

Next step is to create a clone of our existing process and we'll use fork to do that. Do exercise caution that code written after a call to fork will seem to be executing twice (once for each process) so that's why we create our pipe before forking or we would end up with two seperate pipes for both the parent and child process thereby leaving us with way of communicating with each other.

fork_status = fork();

Upon failure fork returns -1 and errno is set accordingly. On success a clone of the existing process is created and fork returns the PID of the child to the parent and 0 to the child process. Therefore it is important that we test the value of fork_status to determine which process is the parent and which one is the child.

 

if (fork_status == 0){

//Child Process

}

if (fork_status > 0){

//Parent Process

}

The child process will only return true to fork_status being equal to 0 and the parent process will only return true to fork_status being greater than 0.

Now that the child and parent processes have been identified let's look at how we will go about running a new process and checking for successful completion.

1) Use execlp to execute the target process which will essentially replace the process image of our forked child process with the process image of the program we are trying to execute and the pipe that we opened earlier will automatically close.

2) If an error occurs then execlp will return -1 and errno will be set to show the cause of failure. 

3) We will simply write the value of errno into the pipe and the parent can then read it and print out an error message accordingly.

4) If the parent process has read 0 bytes from the pipe then that means that the pipe was closed which indicates a successful exec call. If the number of bytes read in the parent process from the pipe is greater than 0 then that means the child has written something to the pipe which in this case will be the error number.

Let's do this.

exec_status = execlp("gedit", "gedit", NULL);

The execlp function takes the name of the program to execute as the first argument. In our case it is gedit. The second argument is the argument that is to be passed to the program when it is executed and it must always be the name of the program itself. When launching programs from the terminal, this is done implicitly by the terminal itself but when using the exec family of calls, this is something we have to do ourselves. The third argument is the next argument passed to the target and you can add as many arguments as you like all seperated by a comma however only the first arguments in the execlp call are mandatory.

Gedit is located in /usr/bin but we don't need to specify the full path here because execlp copies over all the paths added to the PATH variable. Had we used the execl method then passing the full path would have been necessary.

On successful execution of the target process, the pipe will automatically close and no code written after the execlp call will run for the child process because it's entire process image has been replaced with the target process.

On failure, execlp will return -1 and we will then simply write errno to the pipe by first converting it to a char array using sprintf.

if (exec_status == -1)
{
    char error_buffer[4] = {0};
    int error = errno;
    sprintf(error_buffer, "%d", error);
    close(pipe_descriptors[0]);
    write(pipe_descriptors[1], error_buffer, sizeof(int));
    close(pipe_descriptors[1]);
}

Before writing to a pipe it is a good practice to close the read end of the pipe which is at index 0 of the pipe descriptor array. This is done to prevent the process from exceeding the maximum numbers of allowed open descriptors.  After writing to the pipe, close the write end of the pipe as well for the same reason.

Now we need to read from the pipe in the process.

if (fork_status > 0)
{
    char buffer[4] = {0};
    close(pipe_descriptors[1]);
    int bytes_read = read(pipe_descriptors[0], buffer, sizeof(int));
    if (bytes_read == 0)
        cout << "HOORAH!!!" << endl;
    if (bytes_read > 0)
    {
        int error = atoi(buffer);
        cout << "EXECLP Failed because: " << strerror(error) << endl;
    }
}

We wrote the error as a char array and that is exactly we also read it as a char array. Before reading from the pipe, be sure to close the write end of the pipe. This is done to avoid wasting the limit of allowed open descriptors but also allow the parent to recieve EOF which essentially keeps the parent from waiting indefinitely for incoming bytes even after the child has finished writing to the pipe.

The read call will cause the parent process to block until something is written to the pipe. The return value of read is basically the number of bytes read from the pipe. If it returns 0 then that means that the pipe was closed which in turn means that a call to execlp was successful. If it returns a value greater than 0 than that means the child has written something to the pipe and in our case it is going to be the error number.

Summary

In the win32 API, there is a method called CreateProcess that returns a zero or nonzero value  indicating a successful process creation or a failure. In Linux on the other hand, you can only know the process being successfully launched if you see it appear on the screen, however exec returns only on failure. Using pipes is one way of notifying the parent that a process was successfully exec'ed in the child. 

Cheers.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)