Search code examples
csocketspipenonblocking

pipe2(...) vs pipe() + fcntl(...), why different?


I'm trying to build a redistributable binary to put on an old NAS that only has glibc 2.3. So pipe2() isn't available on that machine, but the code I'm trying to build has the following line:

if (pipe2(info_pipe, O_CLOEXEC | O_NONBLOCK) < 0)
    goto info_pipe_err;

My understanding is that the reason pipe2() exists is to avoid race conditions by taking the O_CLOEXEC | O_NONBLOCK at the time of opening vs. doing it in two steps. But there are no threads in the case I'm looking at, so I thought I might just substitute in:

if (pipe(info_pipe) < 0)
    goto info_pipe_err;

int direction; // 0=READ, 1=WRITE
for (direction = 0; direction < 2; ++direction) {
    int oldflags;
    oldflags = fcntl(info_pipe[direction], F_GETFL);

    if (oldflags < 0)
        goto info_pipe_err;

    if (fcntl(info_pipe[direction], 
        F_SETFL, oldflags | O_NONBLOCK | O_CLOEXEC) < 0)
        goto info_pipe_err;
}

But it's seemingly not interchangeable, because the code isn't working. Why isn't this equivalent?


Solution

  • (Answering my own question because I figured it out, just posting here for posterity.)

    If you're building a binary on a newer compiler for an older system, that runtime probably doesn't know the value for O_CLOEXEC as that flag was introduced with pipe2(). If it knows anything, it knows FD_CLOEXEC. And you don't set that using F_SETFL, you use F_SETFD which is a separate fcntl() call.

    The following substitution should work:

    if (pipe(info_pipe) < 0)
        goto info_pipe_err;
    
    int direction; // 0=READ, 1=WRITE
    for (direction = 0; direction < 2; ++direction) {
        int oldflags;
        oldflags = fcntl(info_pipe[direction], F_GETFL);
    
        if (oldflags < 0)
            goto info_pipe_err;
    
        if (fcntl(info_pipe[direction],
            F_SETFL, oldflags | O_NONBLOCK) < 0)
            goto info_pipe_err;
    
        oldflags = fcntl(info_pipe[direction], F_GETFD);
        if (oldflags < 0)
            goto info_pipe_err;
    
        if (fcntl(info_pipe[direction],
            F_SETFD, oldflags | FD_CLOEXEC) < 0)
            goto info_pipe_err;
    }
    

    As noted, this does not have the thread-safety aspect offered by pipe2() allowing all this to be done at once.