execv vs execvp, why just one of them require the exact file's path?

10,782

If the program name argument contains no slashes, the execvp() function looks for the program to execute in the directories listed on your PATH environment variable. If you don't have . (the current directory) on your PATH and you aren't in one of the directories listed on your path, a plain name like b will not be executed, even if b is in the current directory. If the name contains a slash, it can be relative (./b) or absolute (/home/someone/src/programs/b) and it will be interpreted as a file name to be executed without consulting the PATH environment variable.

By contrast, execv() treats a plain b in the program name argument as ./b — the name of the file in the current directory and executes it if it is present, and fails if it is located somewhere else.


At one time, there was a comment that asked:

Are you saying if you have an executable b in . and you do execv("b", b_args), it will get executed?

On a normal Unix box, yes.

Code b.c:

#include <stdio.h>
int main(void)
{
    puts("Hello");
    return 0;
}

Code a.c:

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

int main(void)
{
    char *argv[] = { "b", 0 };
    execv(argv[0], argv);
    fprintf(stderr, "failed to execute '%s'\n", argv[0]);
    return 1;
}

Running these:

$ (PATH=$(clnpath "$PATH" ".:$PWD"); echopath PATH; ./a)
/Users/jleffler/bin
/opt/informix/12.10.FC6/bin
/Users/jleffler/oss/bin
/Users/jleffler/oss/rcs/bin
/usr/local/mysql/bin
/opt/gcc/v7.3.0/bin
/Users/jleffler/perl/v5.24.0/bin
/usr/local/bin
/usr/bin
/bin
/opt/gnu/bin
/usr/sbin
/sbin
Hello
$

The clnpath script modifies the string provided as its first argument ("$PATH") by removing any occurrences of any of the directory names listed in its second path-like argument (".:$PWD") — it's how I edit my PATH on the fly when I need to. The echopath script echoes the directories on PATH (or any other path-like variable, or it will process the result of expanding a pathlike variable, such as "$PATH"), one per line — the output shows that neither . nor /Users/jleffler/soq (which is where I run the program) is on $PATH in the sub-shell. The ./a runs the code from a.c (it would not be executed without that ./ in front), which in turn runs the code from b.c, which produces the Hello. (If there is some system where this does not work, please identify it.)

I could also arrange for b.c to be:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    puts("Hello");
    const char *env = "PATH";
    char *val = getenv(env);
    if (val == 0)
        val = "<nothing>";
    printf("%s=%s\n", env, val);
    return 0;
}

which would print the value of $PATH directly from the executable (to verify that neither . nor the value of the current working directory is listed).

Share:
10,782

Related videos on Youtube

ikeDiM
Author by

ikeDiM

Updated on June 04, 2022

Comments

  • ikeDiM
    ikeDiM about 2 years

    I have two files in the same directory.

    directory/  
    | a.c  
    | b.c
    

    a.c

    #include <stdio.h>                         
    #include <string.h>                        
    #include <sys/types.h>                     
    #include <unistd.h>                        
    
    int main(int argc, char *argv[])           
    {                                          
       pid_t pid;                              
       int status;                             
       int wret;                               
    
       if ((pid = fork()) < 0)                 
          printf("error");                     
       else if(pid == 0)                       
       {                                       
          printf("%s", argv[1]);               
          execv(argv[1], &argv[1]);            
       }                                       
       else                                    
       {  
          /* respawn */                          
          if ((wret = wait(&status)) != -1)    
          execv(argv[1], &argv[1]);            
       }                                       
    
       return 0;                               
    }       
    

    b.c is just a simple program that print "hello".

    I want to run ./a b from the command line to make the a program call exexXX to execute the b program.

    I don't understand why if I use execv I can write just ./a b in the command line, instead if I use execvp I have to write ./a ./b.

    The man exec page is not clear because it reports

    "The initial argument for these functions is the name of a file that is to be executed."

    Thanks

    • chux - Reinstate Monica
      chux - Reinstate Monica over 6 years
      Hmmm, environment variable PATH likely missing from execvp(). It would make more sense to post the exact failing code than the working one.
    • user253751
      user253751 over 6 years
      Well what do you think is the difference between execv and execvp?
    • Jonathan Leffler
      Jonathan Leffler over 6 years
      After execv() returns, you should probably be reporting an error (if execv() returns, it failed) and taking steps to exit, In this code, you aren't in a loop, so it doesn't hurt much, but in more complex code, not handling the error explicitly will lead to gross confusion. (Have you ever tried typing when two processes are both trying to read your terminal at the same time? It isn't fun!) Your messages should have newlines at the end to ensure they appear — add a fflush(stdout) if you might pipe the output to tee or anything like that. And errors should be reported to stderr.
    • Jonathan Leffler
      Jonathan Leffler over 6 years
      Also, which call is using execvp() — I see two calls to execv().