15
\$\begingroup\$

I need to know (for monitoring purposes) the PID of a program immediately before I start it. There could be multiple of the same program launched at the same time, so monitoring ps or top isn't really an option. It dawned on me as I was exploring various bash-related options that I could make use of C's exec functions to try and pull this off. With that in mind, I whipped up this little piece of code:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    char **argv_copy;
    int i;

    if (argc >= 2)
    {
        argv_copy = malloc(argc * sizeof *argv_copy);

        for (i = 1; i < argc; i++)
        {
            argv_copy[i-1] = argv[i];
        }
        argv_copy[argc] = (char *) NULL;

        printf("%lu\n", (long unsigned) getpid());

        execvp(argv[1], argv_copy);
    }
    return 0;
}

It works as expected - it immediately prints out the PID of the process and then loads the actual process I want to run. What I'm not convinced of is the security or robustness of this little hack. I don't need it to be 100% bullet-proof security-wise, but if you can drive an SUV through it I'd like to know.

Could I get some advice on these two areas specifically?

\$\endgroup\$
3
  • \$\begingroup\$ I have rolled back the last edit. Please see what you may and may not do after receiving answers. What this means is that if you wish to share the updated version... you should post it as a self-answer (as a proper review), or as a github link in the question, or perhaps as a new question to be reviewed? \$\endgroup\$
    – Pimgd
    Jul 27, 2016 at 13:50
  • \$\begingroup\$ Hello! Please don't make changes to the original post once it has been reviewed, as that invalidates the current answers. Please see our meta side on performing iterative reviews for more information! \$\endgroup\$
    – syb0rg
    Jul 27, 2016 at 13:50
  • 2
    \$\begingroup\$ @Pimgd and syb0rg: Oops, didn't see that when I read the "how to ask a good question" FAQs. Sorry!! \$\endgroup\$
    – tonysdg
    Jul 27, 2016 at 13:51

5 Answers 5

12
\$\begingroup\$

Bug

This line:

    argv_copy[argc] = (char *) NULL;

should be:

    argv_copy[argc-1] = NULL;

You are removing one argument so you need to terminate the array at the right place.

Copy unneeded

Instead of:

    execvp(argv[1], argv_copy);

you could do:

    execvp(argv[1], &argv[1]);

and avoid making a copy.

\$\endgroup\$
1
  • \$\begingroup\$ I completely forgot that C arrays are contiguous in memory... this makes perfect sense. Seems I've been working in Python a little too long haha. Thanks! \$\endgroup\$
    – tonysdg
    Jul 27, 2016 at 13:36
10
\$\begingroup\$

It fails silently when you pass it gibberish. Not quite a bug, but still not the best.

$ ./a.out nemo
8620
$ echo $?
0

$ nemo
bash: nemo: command not found
$ echo $?
127

An easy fix would be to print an error message and return 127 after the execvp.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ It's also a silent no-op when run without any arguments, when one would expect it to error out with a usage message instead. \$\endgroup\$
    – jwodder
    Jul 28, 2016 at 5:05
7
\$\begingroup\$

Note that you can also do this trivially with a shell script:

#!/bin/sh

echo "PID: $$"
exec "$@"

That's a lot simpler and less bug-prone than doing it in C.

\$\endgroup\$
0
\$\begingroup\$

Can't comment, sorry.

I might be totally wrong here but the man page for getpid() states that it returns the PID of the calling process. This would be your application instead of the one you want to execute.

So instead of printing the PID of a program immediately before it runs, you print the PID of a program immediatly before another program runs.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ From what I recall, exec runs the new binary in the process that calls it, it doesn't spawn a new one, which is why you often see a fork/exec pattern. \$\endgroup\$
    – forsvarir
    Jul 27, 2016 at 8:26
  • 2
    \$\begingroup\$ On Unix-like systems execvp() (and friends) do not start a new process, they replace the current one. fork() is the call to start a new process, it copies the calling process. Of course one often calls fork and then almost immediately exec, but the fact that they are separate allows for some interesting things. Like this one. \$\endgroup\$
    – ilkkachu
    Jul 27, 2016 at 11:27
  • \$\begingroup\$ I wasn't aware that it replaces the current process. It all makes sense now. :) \$\endgroup\$
    – sbecker
    Jul 27, 2016 at 11:55
0
\$\begingroup\$

pid_t is certainly some integer type.

Looking forward, little reason to assume an unsigned long is sufficient range to encompass a pid_t.

So why be stingy? Use a very wide type when trying to print types of unknown width.

//printf("%lu\n", (long unsigned) getpid());
printf("%jd\n", (intmax_t) getpid());
// or 
printf("%lld\n", (long long) getpid());

If absolutely certain, use the unsigned counter parts.


For completeness, code should free() memory it allocates.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ If the call succeeds, you don't actually need to free this memory, as the exec call would wipe out the heap/data section. But I guess if it fails, then you're right! \$\endgroup\$
    – tonysdg
    Jul 29, 2016 at 20:25
  • 1
    \$\begingroup\$ @tonysdg Agree with little "need" to call free(). It is just that I look at all good code as re-usable for other tasks - there free() may be needed. Putting your toys away when done playing is good house-keeping and good coding style. \$\endgroup\$ Jul 29, 2016 at 20:36
  • 1
    \$\begingroup\$ I think that's my new favorite metaphor for malloc/free. \$\endgroup\$
    – tonysdg
    Jul 29, 2016 at 20:39

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.