8

I'm trying to pass arguments entered by the user to execvp().

So far I've split up the string. If the user types ls -a, temp is saved as "ls" and "-a" followed by a NULL character. I'm not quite sure how to point to this properly in execvp. In examples I've seen it using execvp(temp[position], temp). I know the way I'm trying to do it at the moment is wrong, but I'm not sure how to do it properly! At the moment I'm getting a segmentation fault.

int main(int argc, char *argv[]) 
{
    char line[124];
    int size = 124;
    char *temp = NULL;

    while(fgets(line, size, stdin) != NULL ) {
        if (strcmp(line, "exit\n") == 0) {
            exit(EXIT_SUCCESS);
        }
        temp = strtok(line, " ");
        while (temp != NULL) {
            printf("%s\n", temp);
            temp = strtok(NULL, " ");
        }
        execvp(temp, &temp);    
    }
    return EXIT_SUCCESS;
}
joce
  • 9,624
  • 19
  • 56
  • 74
caerulean
  • 95
  • 1
  • 2
  • 9
  • 2
    Since you're not using either `argc` or `argv`, you could use `int main(void)`. With the compiler options I use habitually, that prevents a couple of warnings. – Jonathan Leffler Mar 21 '13 at 04:56
  • @JonathanLeffler I'll be using them later on. Just not at this point in time. – caerulean Mar 21 '13 at 05:01
  • 1
    Fair enough — but for an SSCCE ([Short, Self-Contained, Correct Example](http://sscce.org/)), you strive to eliminate everything that is immaterial to the reduced example. It is a minor issue — very minor. Most programs provide far more opportunities for sniping than yours does. – Jonathan Leffler Mar 21 '13 at 05:12

3 Answers3

7

Your problem is that temp is a single pointer, and you need to pass an array of pointers to execvp().

Something like:

    enum { MAX_ARGS = 64 };
    char *args[MAX_ARGS];
    char **next = args;

    temp = strtok(line, " ");
    while (temp != NULL)
    {
        *next++ = temp;
        printf("%s\n", temp);
        temp = strtok(NULL, " ");
    }
    *next = NULL;
    execvp(args[0], args);

Note that the argument list has been given a null pointer as a terminator, just like argv[argc] == NULL in main(). Clearly, I've skimped on the error checking (if you pass more than 63 arguments, you're going to overflow args array). But this contains the core idea.


With this example, I can't seem to get the simple command of ls to work, I've tried mkdir and echo and they seem to work fine. Passing ls returns -1 from execvp().

I'm not sure what the problem might be — all of these work for me:

  • ls
  • ls -l
  • ls -l madump.c (where madump.c happens to be a file in the directory I'm testing in)

The code I used was:

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

int main(void) 
{
    char line[1024];

    while (fgets(line, sizeof(line), stdin) != NULL)
    {
        if (strcmp(line, "exit\n") == 0)
            exit(EXIT_SUCCESS);

        char *args[64];
        char **next = args;
        char *temp = strtok(line, " \n");
        while (temp != NULL)
        {
            *next++ = temp;
            printf("%s\n", temp);
            temp = strtok(NULL, " \n");
        }
        *next = NULL;

        puts("Checking:");
        for (next = args; *next != 0; next++)
            puts(*next);

        execvp(args[0], args);
    }

    return EXIT_SUCCESS;
}

Note that I added \n to the strtok() token list, after creating a directory with a newline at the end of its name. Good for annoying friends and baffling semi-educated enemies, but a nuisance from most other perspectives. Note how I print out the data that's going to be passed to execvp() just before actually doing so. Often, I'd use printf("<<%s>>\n", *next); instead of just puts() to get an unambiguous indication of where the arguments start and end.

The output from running the command (doit) was:

$ ./doit
ls -l madump.c
ls
-l
madump.c
Checking:
ls
-l
madump.c
-rw-r--r--  1 jleffler  staff  2352 Jul 28  2011 madump.c
$

What did you get from your version?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Wouldn't I also need to add whatever is in temp to args before calling on execvp? – caerulean Mar 21 '13 at 05:08
  • 1
    That (adding what is in `temp` to `args`) is being done via `next`. Initially, `*next` points at `args[0]`; the value in `temp` is copied to `args[0]` and `next` is incremented to point to `args[1]`; rinse and repeat. Incidentally, there'd normally be a `fork()` in the code, too, as otherwise your program will stop when it first successfully executes a command. – Jonathan Leffler Mar 21 '13 at 05:11
  • I've kept my args array to size of 16 for now. After I get this working I'll be implementing fork() so I can handle multiple processes. – caerulean Mar 21 '13 at 05:24
  • That's all fine; get the simple stuff working (check it out with `valgrind`) and then move onto the harder bits. – Jonathan Leffler Mar 21 '13 at 05:28
  • With this example, I can't seem to get the simple command of "ls" to work, I've tried mkdir and echo and they seem to work fine. Passing "ls" returns -1 from execvp. – caerulean Mar 21 '13 at 05:33
  • 1
    @caerulean, When `execvp()` returns -1, call [`perror`](http://pubs.opengroup.org/onlinepubs/009695399/functions/perror.html) and exit. This way, you will know *why* `execvp()` exited. – Anish Ramaswamy Mar 21 '13 at 05:51
  • Yes it was the pesky newline character that caused it to error. Adding the " \n" to the strtok made it work for ls, thank you for the help :)! – caerulean Mar 21 '13 at 05:59
3

As your code looks currently, after your while(temp != NULL) finishes, temp will be NULL!

execvp expects the first argument to be the path of the file which will be the new process image. The second argument is expected to be an array of NULL terminated strings where the last member of that array is a NULL pointer and the first member is the filename of the file specified in the first argument.

To implement this in your code, consider the following while loop instead:

char **argList = NULL;
unsigned int numArgs = 0;
while (temp != NULL) {
    numArgs++;

    /* Reallocate space for your argument list */
    argList = realloc(argList, numArgs * sizeof(*argList));

    /* Copy the current argument */
    argList[numArgs - 1] = malloc(strlen(temp) + 1, 1); /* The +1 for length is for the terminating '\0' character */
    snprintf(argList[numArgs - 1], strlen(temp) + 1, "%s", temp);

    printf("%s\n", temp);
    temp = strtok(NULL, " ");
}

/* Store the last NULL pointer */
numArgs++;
argList = realloc(argList, numArgs * sizeof(*argList));
argList[numArgs - 1] = NULL;

/* Finally, pass this to execvp */
execvp(argList[0], argList);
/* If you reach here, execvp() failed */

The code I provided above does not do some error-checking (such as when realloc or malloc fails), but basically keep these points in mind:

  1. Argument 1: Path name of the file which is going to be put in memory.
  2. Argument 2: A list of arguments where first member of that list = file name, last member = NULL pointer.

Please look at the documentation for better clarity and a very simple example.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Anish Ramaswamy
  • 2,326
  • 3
  • 32
  • 63
  • You don't need to copy the strings because `temp` points to parts of the array `line`. If you do copy the strings, then I recommend using `strdup()`. The `snprintf()` is interesting. The explicit `\0` is really unnecessary; there's already going to be a null at the end anyway. The last `sprintf()` doesn't do what you think it does. It either crashes or writes a string such as `(null)` into the variable where you've only allocated enough space for a `'\0'`. – Jonathan Leffler Mar 21 '13 at 05:27
  • Oh wow I had no idea about `strdup()`. Seems much cleaner to use that instead. Oh yeah `snprintf()` does copy the '\0', my bad. I did not understand your last point though. Actually, I just realized that `strlen((char *)0)` = crash. `(char *)0` = `(null)` so I'm kind of clueless how to allocate memory for that. Any suggestions? – Anish Ramaswamy Mar 21 '13 at 05:36
  • Use: `argList[numArgs - 1] = NULL;` (or `0` in place of `NULL`) instead of the final post-loop `sprintf()`. The last value in the array must be a null pointer. Theoretically, you should check that the memory allocations succeed, too. Also, although it makes the code a little harder, it is a good idea to allocate the memory for `argList` in blocks (of say 64 pointers at a time), to avoid quadratic behaviour as you grow the block one entry at a time. However, that's a refinement for later. – Jonathan Leffler Mar 21 '13 at 05:49
  • @JonathanLeffler, Haha I knew I over-complicated things somewhere. Thanks – Anish Ramaswamy Mar 21 '13 at 05:53
  • I just noticed one more thing: if `execvp()` returns, it failed. If it was successful, it doesn't return — the other program is running instead. – Jonathan Leffler Mar 21 '13 at 06:15
1

I'm guessing the segfault is because you are passing a NULL pointer to execvp. The loop immediately above the call ensures this.

To do what you're trying to do, you'll need to create an array of string pointers for what you currently have called temp. This becomes the argv array for the program you call. That's why the command is usually used as execvp (temp[0], temp) - argv[0] is usually the name of the program.

So try creating an array of string pointers, and have each one point to a tokenized word from line. You may need to use malloc, although if you want to be clever you might be able to point directly into line. If you do that, you'll need to set the character immediately after every 'word' to \0.

lxop
  • 7,596
  • 3
  • 27
  • 42