redux
redux

Reputation: 1167

execvp not working when converting from vector<string> to vector<char*> to char**

Going from a vector of strings to a vector of char* to a char**, was working when the argument came in as char**, but the conversion seems to have a problem and I'm not able to find the difference.

Is there a better way to do this?

    vector<string> args;

    /* code that correctly parses args from user input */

    pid_t kidpid = fork();
    if (kidpid < 0)
    {
        perror("Internal error: cannot fork.");
        return -1;
    }
    else if (kidpid == 0)
    {
        // I am the child.

        vector<char*>argcs;
        for(int i=1;i<args.size();i++)
        {
            char * temp = new char[args.at(i).length()];
            for(int k=0;k<args.at(i).length();k++)
            {
                temp[k] = args.at(i).at(k);
            }
            argcs.push_back(temp);
        }

        char** argv = new char*[argcs.size() + 1];
        for (int i = 0; i < argcs.size(); i++)
        {
            argv[i] = argcs[i];
        }
        argv[args.size()] = NULL;

        execvp(program, args);

        return -1;
    }

Upvotes: 1

Views: 2382

Answers (1)

rici
rici

Reputation: 241861

First, there's no point in copying the std::strings if the next thing you are going to do is call execvp.

If the execvp succeeds, then it will never return and the entire memory image will vanish into smoke (or, more accurately, be replaced by a completely new image). In the course of constructing the new image, exec* will copy the argv array (and the environment array) into it. In any event, the std::vector and std::string destructors will never be invoked.

If, on the other hand, the execvp fails, then the argument passed into it will not have been modified. (Posix: "The argv[] and envp[] arrays of pointers and the strings to which those arrays point shall not be modified by a call to one of the exec functions, except as a consequence of replacing the process image.")

In either case, there was no need to copy the character strings. You can use std::string::c_str() to extract a pointer to the underlying C string (as a const char*, but see below).

Second, if you're using C++11 or more recent, std::vector conveniently comes with a data() member function which returns a pointer to the underlying storage. So if you have std::vector<char*> svec, then svec.data() will be the underlying char*[], which is what you want to pass into execvp.

So the problem reduces to creating a std::vector<char*> from a std::vector<std::string>, which is straightforward:

else if (kidpid == 0) {
    // I am the child.
    std::vector<char*> argc;
    // const_cast is needed because execvp prototype wants an
    // array of char*, not const char*.
    for (auto const& a : args)
        argc.emplace_back(const_cast<char*>(a.c_str()));
    // NULL terminate
    argc.push_back(nullptr);
    // The first argument to execvp should be the same as the
    // first element in argc, but we'll assume the caller knew
    // what they were doing, and that program is a std::string. 
    execvp(program.c_str(), argc.data());
    // It's not clear to me what is returning here, but
    // if it is main(), you should return a small positive value
    // to indicate an error
    return 1;
}

Upvotes: 6

Related Questions