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\$
  • \$\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 '16 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 '16 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 '16 at 13:51
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.

| improve this answer | |
\$\endgroup\$
  • \$\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 '16 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.

| improve this answer | |
\$\endgroup\$
  • 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 '16 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.

| improve this answer | |
\$\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.

| improve this answer | |
\$\endgroup\$
  • 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 '16 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 '16 at 11:27
  • \$\begingroup\$ I wasn't aware that it replaces the current process. It all makes sense now. :) \$\endgroup\$ – sbecker Jul 27 '16 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.

| improve this answer | |
\$\endgroup\$
  • 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 '16 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\$ – chux - Reinstate Monica Jul 29 '16 at 20:36
  • 1
    \$\begingroup\$ I think that's my new favorite metaphor for malloc/free. \$\endgroup\$ – tonysdg Jul 29 '16 at 20:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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