C execvp usage, what am I missing?

102 views Asked by At

I am trying to make a program that uses shell commands for a school project. I am able to compile and run the code without errors, but when I input a command such as ls, noting happens. I think I am missing something with the execvp.

I have been trying to use various configurations of inputs.

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

#define MAX_LINE 80  /* Maximum length of a command */

int main(void) {
    char args[MAX_LINE / 2 + 1]; /* command line arguments */
    int should_run = 1; /* Flag to determine when to exit the program */
    pid_t pid;
    char *myCmd;
    char *tokens[40];
    pid = fork();

    while (should_run) {
        printf("osh>");
        fflush(stdout);
        scanf("%s", myCmd);
        
        int i = 0;
        char *token = strtok(myCmd, " ");
        
        while (token != NULL) {
            tokens[i] = token;
            i++;
            token = strtok(NULL, " ");
        }
        
        if (pid < 0) {
            printf("Fork Failed\n");
            //exit(1);
        } else
        if (pid == 0) {
            execvp(tokens[0], tokens);
            //exit(1);
        } else {
            if (strcmp(tokens[i - 1], "&")) {
                waitpid(pid, NULL, 0);
            }
        }
    }
    return 0;
}
2

There are 2 answers

0
Harith On

Writing to unallocated memory:

char* myCmd;

only allocates memory for the pointer. The pointer has an indeterminate value i.e. it may be pointing to anything and any attempt to dereference a pointer with a bad value would result in undefined behaviour.

Automatically and dynamically allocated objects are initialized only if an initial value is explicitly specified; otherwise they initially have indeterminate values (typically, whatever bit pattern happens to be present in the storage, which might not even represent a valid value for that type).

The subsequent call to scanf() then invokes undefined behaviour, because no memory was ever allocated for the string.

scanf("%s", myCmd);

Possible fix:

If no command is ever going to exceed 80 characters, as stated in this comment:

define MAX_LINE 80 /*Maximum length of a command*/

You can allocate an array[80] of char and then pass it to scanf().

And to limit input, you could specify a width specifier like so:

scanf("%79s", myCmd);

Note:

Don't use scanf(). Use fgets(). With scanf(), it will only grab the first whitespace separated token (e.g. for input of Hello World, scanf() will only return Hello). — @Craig Estey


The array of pointers to execvp shall be null-terminated:

The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a NULL pointer.

Code is missing:

tokens[i] = NULL;

after the while loop.


Aside: A shell in C - Tutorial and Beej's guide to UNIX Interprocess Communication might help to elaborate on what @John pointed out in his answer.

0
John Bollinger On

In addition to the serious issues with command input and formatting of execvp() arguments called out in another answer, you are fork()ing in the wrong place. You need to fork() a new child for each command the shell runs, so the fork call should go inside the loop. Moreover, it should go after printing the prompt and reading a command, because you want only the parent to do those things, not the children. And you do not need both parent and child to parse the command. Although you could rely on the child to do that, it would be more conventional to do it in the parent. But certainly not in both.

It is conceivable that your program would still seem to work despite the other errors (though that would not be any justification for failing to fix them), but the fork() placement issue is absolutely breaking. It will cause unexpected extra prompting, and the shell will never attempt to execute more than one command (though you might not notice, in part because you do not check the return values of several key function calls).