perlapio - PerlIO_findFILE() 有效但将 errno 设置为 "illegal seek" (ESPIPE 29)

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

这个问题或多或少与 embedded perl in C, perlapio - interoperability with STDIO 有关,我认为我已经为 Windows 环境解决了这个问题。如果这个新问题也解决了,我将post一个完整的解决方案。

在链接的问题中, 讲故事的人 gave me the hint 使用 PerlIO_findFILE() 解决了眼前的问题,但是 Linux 上的相同代码表现得很奇怪。

Perl 的 dup2() 在 Win32 上似乎有不同的行为,其中 dup2()win32_dup2() 的宏,据我所知它只是使用 dup2()来自 io.h .

在 Win32 上,Perl 的版本 returns 成功时为零,错误时为非零,但在 Linux 上将使用默认的 ANSI dup2(),而不是 returns 新的文件描述符。然后,我必须检查 errno 是否一切正常。

如果对 PerlIO_findFILE() 的调用将 errno 设置为 "illegal seek"(错误号 29 - ESPIPE),则在 dupdup2 之后、pipe 等。errno 仍设置为 "illegal seek",对 errno 的任何进一步检查仍会看到相同的错误。

(实际上一切都对我有用,因为没有实际错误。另外,通过检查 errno 的解决方案不是线程安全的,因为在系统调用和检查之间,另一个胎面可能会重置 errno。)

注意我有

#define PERLIO_NOT_STDIO 0

有效并且我使用的是 Perl5.14.1。

我真的做错了什么吗?

这是一个简化的代码片段:

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
            }
        }
    }
}

errno是没有意义的,除非C库调用或者系统调用报错,所以不能用来判断是否有错误发生。值得注意的是,这些调用不需要(通常也不需要)在成功时重置 errno。在调用之前清除 errno 甚至是不安全的,因为即使没有发生错误,调用也可能设置 errno

据我所知,Perl 的 emulation of dup2 returns 与 POSIX 的值相同(-1 出错,newfd 成功)。

#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

(从 5.24.1 开始)

这意味着我们可以 platform-independent 方式检测错误,尽管您的说法与此相反。因此,正确的用法是

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