Search code examples
cperlperl-io

perlapio - PerlIO_findFILE() works but sets errno to "illegal seek" (ESPIPE 29)


This issue is more or less related to embedded perl in C, perlapio - interoperability with STDIO which I think I have solved for the Windows environment. I will post a complete solution if this new issue is solved too.

In the linked question, StoryTeller gave me the hint to use PerlIO_findFILE() which solved the immediate problem, but the same code on Linux behaves strangely.

Perl's dup2() seems to have a different behaviour on Win32, where dup2() is a macro for win32_dup2(), which as far as I understand simply uses the dup2() from io.h .

On Win32, Perl's version returns zero on success and non-zero on error, but on Linux the default ANSI dup2() will be used, which instead returns the new file descriptor. Then, I'll have to check errno if everything went fine.

If a call to PerlIO_findFILE() sets errno to "illegal seek" (errno 29 - ESPIPE), then after dup, dup2, pipe etc. errno is still set to "illegal seek", and any further checks on errno still see the same error.

(In practice everything has worked for me because there was no actual error. Also, the solution by checking errno is not thread safe, since between the syscall and the check another tread may reset errno.)

Note that I have

#define PERLIO_NOT_STDIO 0

in effect and I'm using Perl5.14.1.

Am I doing something really wrong here?

Here's a simplified code snippet:

stdOutFILE = PerlIO_findFILE(PerlIO_stderr()); // convert Perl's stdout to stdio FILE handle

fdStdOutOriginal = fileno(stdOutFILE);         // get descriptor

if ( fdStdOutOriginal >= 0 ) {

    relocatedStdOut = dup(fdStdOutOriginal);   // relocate stdOut for external writing

    if ( relocatedStdOut >= 0 )
    {
        if ( pipe(fdPipeStdOut) == 0 )         // create pipe for forwarding to stderr
        {
            // this has to be done on win32:
            // if ( dup2(fdPipeStdOut[1], fdStdOutOriginal)  == 0 ) // hang pipe on stdOut

            dup2(fdPipeStdOut[1], fdStdOutOriginal);

            if( errno == 0 ) {
                // do some funny stuff
            } else {
                // report error
            }
        }
    }
}

Solution

  • errno is meaningless unless a C library call or a system call reports an error, so it can't be used to determine if an error occurred. Notably, these calls aren't required to (and usually don't) reset errno on success. It's not even safe to clear errno before the call, because the call may set errno even if no error occurred.

    As best as I can tell, Perl's emulation of dup2 returns the same value as the POSIX one (-1 on error, newfd on success).

    #ifndef HAS_DUP2
    int
    dup2(int oldfd, int newfd)
    {
    #if defined(HAS_FCNTL) && defined(F_DUPFD)
        if (oldfd == newfd)
            return oldfd;
        PerlLIO_close(newfd);
        return fcntl(oldfd, F_DUPFD, newfd);
    #else
    #define DUP2_MAX_FDS 256
        int fdtmp[DUP2_MAX_FDS];
        I32 fdx = 0;
        int fd;
    
        if (oldfd == newfd)
            return oldfd;
        PerlLIO_close(newfd);
        /* good enough for low fd's... */
        while ((fd = PerlLIO_dup(oldfd)) != newfd && fd >= 0) {
            if (fdx >= DUP2_MAX_FDS) {
                PerlLIO_close(fd);
                fd = -1;
                break;
            }
            fdtmp[fdx++] = fd;
        }
        while (fdx > 0)
            PerlLIO_close(fdtmp[--fdx]);
        return fd;
    #endif
    }
    #endif
    

    (From 5.24.1)

    This means that we can detect an error in a platform-independent fashion, despite your claim to the contrary. As such, the proper usage is

    if ( dup2(fdPipeStdOut[1], fdStdOutOriginal) >= 0 ) {
        //do some funny stuff
    } else {
        //report error
    }