How to Use of Execve in C

Abdul Mateen Feb 02, 2024
  1. the exec System Call in C
  2. the execve System Call in C
How to Use of Execve in C

This tutorial will discuss the use of execve to run both Linux standard commands and our executables in C.

First, we will discuss the exec system call and the exec’s family. Next, we will call our executables in C, and finally, we will discuss both standard Linux commands and our executables.

the exec System Call in C

The exec system call replaces the running process with some other executable process. The address space of the running process is replaced with the address space of the new process.

It is important to note that the new program is loaded into the same address space. The process id remains the same.

The new program will run independently; that is, the starting point will be the entry point of the new program. The exec system call has many variants.

  1. execl
  2. execle
  3. execlp
  4. execv
  5. execve
  6. execvp

These functions use the same base exec followed by one or multiple letters. The detail of the extra letters is below.

  1. e - Here, e is for environment variables; this function has an array of pointers that points to environment variables. The list of environment variables is explicitly passed to the newly loaded program.
  2. l - Here, l is for command line arguments. We can give the list of command line arguments to the function.
  3. p - Here, p is for the environment variable path. In this function, the path variable helps to find the file, passed as an argument to the newly loaded process.
  4. v - Here, v is also for the command line arguments. However, in this function, command line arguments are passed to the newly loaded process as an array of pointers.

In the basic exec system call, the current process address space is replaced with the newly loaded program’s address space; as a result, the currently running process is terminated. The freshly loaded process is passed as an argument in this system call.

The newly loaded process has the same process id, the same environment variables, and the same set of file descriptors. However, the CPU statistics and virtual memory are affected.

Syntax of exec Calls

Here, we have six system calls’ syntax, variants of basic exec system calls.

int execl(const char* path, const char* arg, ...) int execle(
    const char* path, const char* arg, ...,
    char* const envp
        []) int execlp(const char* file, const char* arg,
                       ...) int execv(const char* path,
                                      const char* argv
                                          []) int execve(const char* path,
                                                         const char* argv[],
                                                         char* const envp
                                                             []) int execvp(const char*
                                                                                file,
                                                                            const char* argv
                                                                                []) int execvpe(const char*
                                                                                                    file,
                                                                                                const char* argv
                                                                                                    [],
                                                                                                char* const envp
                                                                                                    [])

First, all functions’ return type is int. However, in case of a successful operation (that is, the new program is loaded and replaced), nothing is returned because the current program is no more there to receive the return value.

In failure due to some error, the new program is not loaded, and -1 is returned to the existing program.

In the first argument, there is a difference between path and file. The p, execlp, execvp, and execvpe have a file instead of the path.

The path specifies the file’s full path to be executed/loaded. The file specifies the path name, which helps to locate the new program’s file.

In the second argument, the difference is that functions with v have a two-dimensional array of type char with multiple strings (including file name).

In contrast, other procedures have one or more one-dimensional arrays of type char, where the first element of this list contains the file name, the second element may include some parameters, etc.

Lastly, in the case of functions having e, a third/last parameter has an environment variable as an array of pointers.

Coding Example of exec System Call

It is better to see an example before further discussion. In this example, we are using the program’s source code as the program’s input.

Here, we have saved this program (with the name execl0.c) in the directory of executable code. This means both source and executable code exist in the same directory.

#include <stdio.h>
#include <unistd.h>
int main(void) {
  char binaryPath[] = "/bin/wc";
  char arg1[] = "wc";
  char arg2[] = "-w";
  char arg3[] = "execl0.c";
  printf("First line of current program\n");
  execl(binaryPath, arg1, arg2, arg3, NULL);
  printf("Last line of current program\n");
  return 1;
}

The above code uses an execl system call, having only a few simple variables of type char*. The first variable contains the path and name of the new program (to be executed), and the second variable has a parameter wc (again, the program’s name).

The third variable has a parameter -w to run the command as wc -w to count words in the source file.

It is also important to note two additional print statements, first before the system call and second at the end of the program.

Output:

First line of current program
32 execl0.c

The output shows that our new program is successfully loaded and executed. However, note that the first print statement is executed (see the first line of output ('First line of current program').

The last print statement is not run because the current program automatically ends when a new program is successfully loaded.

The second output line shows the word count in the file execl0.c.

the execve System Call in C

Now, we will discuss the execve call in detail.

Syntax:

int execve(const char* path, const char* argv[], char* const envp[])

Here, the first argument is the path; as already discussed, the path environment variable helps to find the program to be executed as a new program.

The second argument is a two-dimensional array of characters or a one-dimensional array of strings having a list of command line arguments.

The third argument is again a two-dimensional array of characters or a one-dimensional array of strings having a list of environment variables.

In the exec family, execve is a compelling command with three arguments path, a list of command line arguments, and a list of environment variables. Let’s see a code to execute the echo command from the program.

#include <stdio.h>
#include <unistd.h>
int main(void) {
  char *binaryPath = "/bin/bash";
  char *args[] = {binaryPath, "-c",
                  "echo visit $HOSTNAME:Fun with your browser", "", NULL};
  char *const env[] = {"HOSTNAME=www.delftstack.com", "port=8080", NULL};
  execve(binaryPath, args, env);
  return 1;
}

In the first line of the primary function, /bin/bash is the path where the command exists. In the second line, the list of command line arguments contains three parameters before NULL that terminates the argument.

Again, the first argument is the path, and the second parameter, -c, stands for cmd, which allows passing code as a string.

The third parameter is the command; like in the code, echo is the command.

The code’s third line has two strings with two environment variables, HOSTNAME and port. Finally, the output of the code is:

visit www.delftstack.com : Fun with your browser

In this code, we have executed a Linux command from our program. Next, we will execute an external executable program inside the current program.

First of all, see this program:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *args[]) {
  int i;
  int count = atoi(args[1]);
  for (i = 1; i <= count; i++) printf("[%d]", i);
  printf("\n");
  return 0;
}

This program is taking command line arguments. The command line argument (passed as a string) is converted to an integer in the second line of the main function.

Next, we run a loop from one to count and print counting in square brackets. See the output of this code.

$./ test 4 [1][2][3][4]

We have created the executable with the name test. We have executed the test file with parameter 4 from a command prompt.

We can see counting one to four in square brackets in the output.

Next, we have to run this program test as an external command from another program. For this, we have to specify the path of the executable test program.

Here, the complete path is /home/mateen/Documents/test. Therefore, we will specify this path in our next program to locate the executable file.

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *ar[]) {
  printf("This is the first line\n");
  char *binaryPath = "/bin/bash";
  char name[80] = "/home/mateen/Documents/test ";
  strcat(name, ar[1]);
  char *args[] = {binaryPath, "-c", name, NULL};
  char *env_args[] = {"/bin/bash", (char *)0};
  execve(binaryPath, args, env_args);
  printf("This is the last line\n");
  return 1;
}

We have included another library to use the function to concatenate strings. In the third line of the main function, we have a complete path and file name because this is not a Linux command; instead, this is user defined executable program (already discussed in detail).

In the following line, we are concatenating the command line argument passed to our current program with the new program’s name. Again, in the fifth line, we have command line arguments having a path, -c.

The third parameter is the variable name having path + name of executable + argument passed to the current program.

Output:

$ ./a.out 5
This is the first line
[1][2][3][4][5]

We are running our current program with the command line parameter 5. The first line of output has the first print statement.

Next, you can see our test program is executed. The counting from 1 to 5 is written in square brackets.

Finally, the conclusion is we can run both Linux commands and our executable programs using execve. In the case of the Linux command, we can pass the path to locate the Linux program.

In the case of some other/external executable, we can give a complete path with the file name; in this case, the program will be automatically located at the given path. In this case, the command will ignore the path variable in the third line of the main function.