问题
这两天打算过一遍《Linux高性能服务器编程》,虽然都是些熟悉的知识,但我还是把该书的源码一个个运行了试了试。然后发现了6-5testtee.cpp存在bug。
代码如下:
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
int main( int argc, char* argv[] )
{
if ( argc != 2 )
{
printf( "usage: %s <file>\n", argv[0] );
return 1;
}
int filefd = open( argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666 );
assert( filefd > 0 );
int pipefd_stdout[2];
int ret = pipe( pipefd_stdout );
assert( ret != -1 );
int pipefd_file[2];
ret = pipe( pipefd_file );
assert( ret != -1 );
ret = splice( STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
assert( ret != -1 );
ret = tee( pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK );
assert( ret != -1 );
ret = splice( pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
assert( ret != -1 );
ret = splice( pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_GIFT );
printf("errno:%d\n",errno);
assert( ret != -1 );
close( filefd );
close( pipefd_stdout[0] );
close( pipefd_stdout[1] );
close( pipefd_file[0] );
close( pipefd_file[1] );
return 0;
}
该代码的第三次splice系统调用本应该将stdin的内容输出到stdout,但在我的terminal中运行时该系统调用失败了,错误码22。
起初我尝试用gdb调试,结果系统调用在gdb中成功了。。。很离谱。查了一圈,上stackoverflow开了个问题,结果那上面的老哥说复现不了。
又回头找朋友复现、讨论,在此过程中又发现:在我的manjaro下,clion的run console,dolphin的Terminal,gdb调试窗口都能正确运行该代码,clion的Terminal以及各种其他的自行启动的Terminal都无法正确运行该代码。排查半天得到结论的时候恨不得扇自己一巴掌。
解决办法
使用man 2 splice
查看splice的文档时,关于其Errors的描述有这么一条:EINVAL The target file is opened in append mode.
我的manjaro上所有无法正确运行该代码的Terminal的标准输出都处于Append模式下,因而导致了splice系统调用失败。
只需要在调用之前添加fcntl(STDOUT_FILENO, F_SETFL, fcntl(STDOUT_FILENO, F_GETFL) & ~O_APPEND);
取消stdout的Append模式就能解决该bug。
总结
其实发现bug后我第一时间就查阅了Linux Manual,然而却没有考虑到进程中所有文件描述符的状态,下意识忽略了STDOUT_FILENO这一特殊的并非手动打开的文件描述符,想当然地认为是系统调用本身的问题,白白浪费了大半天时间,需要吸取教训。
Last modified on 2021-01-17