5

While I think I have the grasp on how fork(), exec(), wait() and pid work in C, I have yet to find a way how to run a personal program from within a program.

Here's my code:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> /* for fork() */
#include<sys/types.h> /* for pid_t */
#include<sys/wait.h> /* fpr wait() */

int main(int argc, char* argv[])
{
    char  fileName[255];
    pid_t pid;

    switch (pid = fork()) {
    case -1: //Did not fork properly
        perror("fork");
        break;

    case 0: //child
        execv(fileName[0],fileName);

        puts("Oh my. If this prints, execv() must have failed");
        exit(EXIT_FAILURE);
        break;
    default: //parent
        //Infinite Loop
        while (1) {
            printf(" %s > ", argv[0]);
            scanf("%s", fileName); // gets filename
            if (fileName[0] == '\0') continue;
            printf("\n Entered file: %s",fileName); // prints the fileName
            waitpid(pid,0,0); /* wait for child to exit() */
            break;
        }
    }

    return 0;
}

My questions are the following:

  1. I want to take a string as an input and I want to limit its scope to 255 characters. Is char fileName[255] and then scanf("%s", fileName); the way to go? Should I use getLine() or some other function instead?

  2. Let's say that the input is taken correctly. How do I execute say an existing hello world program. Will the input be stored in *argv[] ? I found out that in a different program I could use

    static char *argv[] = { "echo", "Foo is my name." , NULL };
    execv("/bin/echo", argv);
    

    in order to echo "Foo is my name.". Can I do something similar with a helloWorld program?

2 Answers 2

2

You're passing a single character as the command name, and the name string as the start of a list of arguments — as if the prototype for execv() were int execv(char cmd, char *args).

The actual prototype is: int execv(char *cmd, char **args), so you need:

char *args[2] = { fileName, 0 };

execv(args[0], args);

I assume you set fileName to a meaningful value somewhere — that isn't shown. For example, it might be "./local_program". It will be treated as the pathname of the executable.

If you want to read the name, then you can use fgets() or getline(), but you'll need to remove the newline:

if (fgets(fileName, sizeof(fileName), stdin) != 0)
{
    fileName[strcspn(fileName, "\n")] = '\0';
    …as before…
}

or:

char *fileName = 0;
size_t length = 0;
if (getline(&fileName, &length, stdin) != -1)  /* Not EOF! */
{
    fileName[strcspn(fileName, "\n")] = '\0';
    …as before…
}
free(fileName);

The use of strcspn() avoids having to special case overly long command lines such that there isn't a newline in the fileName. Note that this does not attempt to split the input line into a command name and arguments at spaces or anything fancy like that. That's the next level of shell implementation:

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

int main(void)
{
    char fileName[256];
    if (fgets(fileName, sizeof(fileName), stdin) != 0)
    {
        fileName[strcspn(fileName, "\n")] = '\0';
        char *args[129];
        char **argv = args;
        char *cmd = fileName;
        const char *whisp = " \t\f\r\b\n";
        /* Ugh — strtok()?  OK while only handling white space separators */
        char *token;
        while ((token = strtok(cmd, whisp)) != 0)
        {
            *argv++ = token;
            cmd = 0;
        }
        *argv = 0;

        execv(args[0], args);
        fprintf(stderr, "Oops!\n");
    }
    return 1;
}

I don't need to check for overflow of the args array because 256 characters of input, minus terminating null, cannot be split to produce more than 128 single-character arguments, each separated from the adjacent ones by a single white space character. Using strtok() is a temporary band-aid. As soon as you need to deal with real shell syntax (pipes, I/O redirections, quoted strings with spaces, etc), strtok() is woefully the wrong tool. (It — strtok() — is also the wrong function to use in any library function whatsoever. Use POSIX strtok_r() on Unix or Microsoft's strtok_s() on Windows if you must use a strtok()-like function in library code.)

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your quick and thorough answer. The only part of your question that I couldn't understand completely was the assumption that I set fileName to a value. Do you mean initialization? In that case, I haven't . Do I need to? In my mind when the program ran, it would prompt the user and the user would type something like "./helloWorld" or "/bin/./helloWorld". And this would be the value of fileName.
The typing in of the name is what I meant by 'setting fileName to a value'. As written, you were using an uninitialized character string as a command, which is unlikely to work well. As long as you set fileName to something faintly appropriate somehow, you should be OK. Both ./helloWorld and /bin/./helloWorld would work, though the ./ is not needed (execv() is unaffected by $PATH, and if you specify just helloWorld, it will be treated the same as ./helloWorld). The /bin/./helloWorld is iffy; the /./ could be just / and you should not be putting software in /bin.
0

As it stands, you'll need to fix the compilation errors from your code:

g++ -std=c++11 -g -Wall -Wextra -Wwrite-strings     36501711.cpp   -o 36501711
36501711.cpp: In function ‘int main(int, char**)’:
36501711.cpp:18:25: error: invalid conversion from ‘char’ to ‘const char*’ [-fpermissive]
         execv(fileName[0],fileName);
                         ^
36501711.cpp:18:35: error: cannot convert ‘char*’ to ‘char* const*’ for argument ‘2’ to ‘int execv(const char*, char* const*)’
         execv(fileName[0],fileName);
                                   ^
36501711.cpp: At global scope:
36501711.cpp:7:5: warning: unused parameter ‘argc’ [-Wunused-parameter]
 int main(int argc, char* argv[])
     ^

The warning (about unused argc) is harmless, but the errors are real, and need to be fixed.

You need to initialise fileName before you fork, otherwise the child won't have any valid data there:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> /* for fork() */
#include<sys/types.h> /* for pid_t */
#include<sys/wait.h> /* fpr wait() */

int main(int argc, char* argv[])
{
    char  fileName[255];
    pid_t pid;

    //Infinite Loop
    while (1) {
        printf(" %s > ", argv[0]);
        scanf("%s", fileName);
        if (fileName[0] == '\0')
            break;
        printf("\n Entered file: %s\n", fileName);

        pid = fork();
        switch (pid) {
        case -1: //Did not fork properly
            perror("fork");
            break;

        case 0: //child
            execl(fileName, fileName, 0);
            perror("exec");
            exit(EXIT_FAILURE);
            break;

        default: //parent
            waitpid(pid,0,0); /* wait for child to exit() */
            break;
        }
    }

    return EXIT_SUCCESS;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.