using a new path with execve to run ls command

james picture james · Oct 3, 2011 · Viewed 17k times · Source

I am trying to use execve to run the ls command. Currently I'm running it with the following arguments:

execve(args[0], args, env_args)
//args looks like {"ls", "-l", "-a", NULL}
//env_args looks like {"PATH=/bin", "USER=me", NULL}

What I expected this to do was run the ls command using my new env_args meaning that it would look up ls in my PATH. However, this code actually doesn't do anything and when I run the code it just returns to my command prompt without output.

Using the same args[] I was using execvp and ls worked and searched my current path.

Can you tell me what I am doing wrong?

What I am trying to do is write my own shell program where I can create and export my own environment and have exec use the environment I have defined in a char**. Essentially I am writing my own functions to operate on env_args to add and remove vars and when I call exec i want to be able to call exec on {"ls", "-l", NULL} and have it look down my new environments path variable for a valid program called ls. I hope this explains what I am doing a little better. I don't think the extern environ var will work for me in this case.

Answer

Jonathan Leffler picture Jonathan Leffler · Oct 3, 2011

The execve() function does not look at PATH; for that, you need execvp(). Your program was failing to execute ls, and apparently you don't report failures to execute a program after the execve(). Note that members of the exec*() family of functions only return on error.

You'd get the result you expected (more or less) if you ran the program with /bin as your current directory (because ./ls - aka ls - would then exist).

You need to provide the pathname of the executable in the first argument to execve(), after finding it using an appropriate PATH setting.

Or continue to use execvp(), but set the variable environ to your new environment. Note that environ is unique among POSIX global variables in that is it not declared in any header.

extern char **environ;

environ = env_args;
execvp(args[0], &args[0]);

You don't need to save the old value and restore it; you're in the child process and switching its environment won't affect the main program (shell).


This seems to work as I'd expect - and demonstrates that the original code behaves as I'd expect.

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

extern char **environ;

int main(void)
{
    char *args[]     = { "ls", "-l", "-a", NULL };
    char *env_args[] = { "PATH=/bin", "USER=me", NULL };

    execve(args[0], args, env_args);
    fprintf(stderr, "Oops!\n");

    environ = env_args;
    execvp(args[0], &args[0]);
    fprintf(stderr, "Oops again!\n");

    return -1;
}

I get an 'Oops!' followed by the listing of my directory. When I create an executable ls in my current directory:

#!/bin/sh
echo "Haha!"

then I don't get the 'Oops!' and do get the 'Haha!'.