Search code examples
cexecvp

Why is no output produced when executing ls?


I am attempting to write a program that executes whatever is given in argv. However, when I actually run execvp, it is not giving any output, and it isn't producing any errors. This is my program.

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

int main (int argc, char * argv[]) {
    char * args[argc];
    for (int i = 0; i < argc - 1; ++i) 
        args[i] = strdup(argv[i+1]);
    args[argc-1] = malloc(1);
    *args[argc-1] = '\0';
    printf("%s\n", args[argc-1]);
    int rc = fork();
    if (rc == 0)
        execvp(args[0], args);
}

The output produced when I run ./exec ls is just a single empty line. However, if I run a program which has ls hardcoded into the array, it works fine. Why is this program not listing the contents of the directory?


Solution

  • Pretending that it is necessary to duplicate the arguments and to use fork(), and observing that if you run:

    ./exec ls
    

    then you only want to use arguments from index 1 in the call to execvp(), then you end up with code like this:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main (int argc, char *argv[])
    {
        char *args[argc];
        for (int i = 0; i < argc - 1; ++i)
        {
            if ((args[i] = strdup(argv[i+1])) == NULL)
            {
                fprintf(stderr, "%s: failed to allocate memory for '%s'\n",
                        argv[0], argv[i+1]);
                exit(EXIT_FAILURE);
            }
        }
        args[argc-1] = NULL;
    
        int rc = fork();
        if (rc == 0)
        {
            execvp(args[0], args);
            fprintf(stderr, "%s: failed to execute '%s'\n", argv[0], args[0]);
            exit(EXIT_FAILURE);
        }
        else if (rc < 0)
        {
            fprintf(stderr, "%s: failed to fork\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        return 0;
    }
    

    The parent process should really wait() for the child before exiting itself. The code should probably include a function to dump the contents of an argument list. Here's a revised version of the code which doesn't use strdup() though it does still fork. It uses some code that is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory. These simplify the error handling — error reports use a single function call instead of multiple lines of code as before.

    #include <unistd.h>
    #include <sys/wait.h>
    #include "stderr.h"
    
    int main (int argc, char *argv[])
    {
        err_setarg0(argv[0]);
        if (argc <= 1)
            err_usage("cmd [arg ...]");
    
        int rc = fork();
        if (rc == 0)
        {
            execvp(argv[1], &argv[1]);
            err_syserr("failed to execute '%s': ", argv[1]);
        }
        else if (rc < 0)
            err_syserr("failed to fork: ");
    
        int corpse;
        int status;
        while ((corpse = wait(&status)) > 0)
        {
            if (corpse != rc)
                err_remark("unexpected child PID %d status 0x%.4X\n", corpse, status);
        }
        if (WIFEXITED(status))
            rc = WEXITSTATUS(status);
        else if (WIFSIGNALED(status))
            rc = 128 + WTERMSIG(status);
        else
            rc = 255;   /* Something weird happened! */
    
        return rc;
    }
    

    Of course, if you don't use fork(), you don't need to wait() either. This reduces the code radically:

    #include <unistd.h>
    #include "stderr.h"
    
    int main (int argc, char *argv[])
    {
        err_setarg0(argv[0]);
        if (argc <= 1)
            err_usage("cmd [arg ...]");
        execvp(argv[1], &argv[1]);
        err_syserr("failed to execute '%s': ", argv[1]);
    }