:headdesk: - popen() in C



  • I have only myself to blame for this one, having spent 2 minutes writing it and 2 days on it figuring out undefined was causing the symtoms I was seeing, namely calling /usr/local/sbin/in-house-program (from the inner clause) returned data when called from the command line but was returning nothing from the code presented here:

        if( !(fp = popen("/usr/sbin/in-house-program","r")) ){ /* try /usr/sbin first */
            if( !(fp = popen("/usr/local/sbin/in-house-program","r")) ){ /* else try /usr/local/sbin */
                    /* log fact that both failed */
                    return;
            }
        }
    

    Spoilers:
    [spoiler]
    The first popen() always succeeds regardless of whether the program in quotes exists, since what popen() would fail on is calling the sh that tries to call the program in quotes.
    ­
    Thus the second, inner, clause never gets called under normal circumstances, and if it got that far it too would probably fail anyway for the same reason the first would - i.e. before it even considered what was in the quotes.
    ­
    Anyway - that was my main headdesk moment.... Others:
    WTF#2: the fact that the program could be in one of two places to begin with which I was trying to mitigate
    WTF#3: output from the program succeeding but having nothing to report, is the same as the program not running at all.
    [/spoiler]

    undefined



  • @PJH said:

    if( !(fp = popen("/usr/sbin/in-house-program","r")) ){ // try /usr/bin first

    I found TRWTF: /usr/sbin != /usr/bin!



  • Bugger. Got fscked in translation - the comment was wrong.

    OP Fixed.



  • @PJH said:

    the comment was wrong.

    I figured, but wanted to prove that I was paying attention.


  • kills Dumbledore

    That's a horrible bracing/commenting style to my eyes. Not helped by the parser not picking the comments out as such



  • @Jaloopa said:

    That's a horrible bracing/commenting style to my eyes.

    Agreed. Most compilers will throw a fit about unmatched braces. 🚎



  • @boomzilla said:

    unmatched braces🚎

    Any more distractions, before the idiocy intended to be highlighted in the code shines through?

    undefined


  • SockDev

    @PJH said:

    Any more distractions

    i hadn't planned on any, but i'm sure i could come up with some.



  • Your spoilered link doesn't work with middle click?

    Presumably, sbin isn't on the expected user's path. Is there a reason it's in there instead of plain old bin and then just let the $PATH take care of stuff?

    I guess you changed to stat the file or something instead?



  • @boomzilla said:

    Your spoilered link doesn't work with middle click?

    No Repro.



  • @boomzilla said:

    Is there a reason it's in there instead of plain old bin and then just let the $PATH take care of stuff?

    Raisins. And legacy stuff I don't want to get distracted by, the least of which is that $PATH might be empty, since the code is running in a bare-bones chroot.

    @boomzilla said:

    I guess you changed to stat the file or something instead?

    The intended route, yes.



  • Nasty. Probably not a popen() bug though, as that, like system(), works via a shell invocation that will succeed.

    You'll probably have to write your own pipe/fork/exec.



  • @PleegWat said:

    You'll probably have to write your own pipe/fork/exec.

    access("...", X_OK) before popen().


  • Discourse touched me in a no-no place

    @PleegWat said:

    You'll probably have to write your own pipe/fork/exec.

    That only really becomes worthwhile once you start wanting to pass arbitrary strings through, since going to the effort of doing it by hand is easier than figuring out quoting correctly.


  • Fake News

    @PJH said:

    The first popen() always succeeds regardless of whether the program in quotes exists, since what popen() would fail on is calling the sh that tries to call the program in quotes.

    While not immediately obvious, the man page you link to does have some nugget which should have told you that some funky stuff is going on:

    Failure to execute the shell is indistinguishable from the shell's failure to execute command, or an immediate exit of the command. The only hint is an exit status of 127.

    In other words, you need to call pclose() on the pipe to see if it actually worked at all...


    Filed under: Hindsight is 20/20, yada yada



  • ... why would Unix C library implementations spawn a shell just to forward stdin/stdout/stderr from that shell to the program?

    Also related: >having to clone your process address space before being able to start a new process, and doing that by unloading your cloned process - why? (and don't bring up 'spawn'; there's no such syscall on Linux and glibc implements that using fork/exec anyway - unlike NtCreateProcess in NT and some similar call to have fork compatibility...)



  • Because there are cases where you'd want to fork() without an exec(), and there may even be cases where you'd want to exec() without a fork()?

    (NtCreateProcess() is quite annoying in that it's both operations in one shot, which is nice for the 'run a program in a subprocess' case, but makes the 'create a subprocess of myself to do some task` case all sorts of ugly.)



  • @tarunik said:

    fork()

    undefined()



  • I believe fork() uses copy-on-write pages.

    I expect system() and popen() exist because they're convenient to use. The alternative, without shell inbetween, requires one of these:

    execlp( "rm", "rm", "-rf", "foo/bar", (char *)NULL );
    

    or

    char * args[4];
    args[0] = "rm";
    args[1] = "-rf";
    args[2] = "foo/bar";
    args[3] = NULL;
    execvp( "rm", args );
    


  • exec() can be useful by itself in certain circumstances.

    fork() even more so. Most specifically, a program can background itself from the terminal by calling fork(), setsid(), fork() in succession and letting the parents exit.

    In a fork/exec sequence, there can be other functions inbetween. Most importantly, this affects which file descriptors are inherited by the child process. The following does approximately the same as popen("zcat foo.gz"), without the error handling:

    int fds[2];
    
    pipe( fds );
    
    if( 0 == fork() )
    {
        dup2( fds[1], 1 );
        close( 0 );
        close( fds[0] );
        /* Close all other file descriptors */
        execvp( "zcat", "zcat", "foo.gz", (char *)NULL );
    }
    
    close( fds[1] );
    
    return fdopen( fds[0] );
    

Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.