咸鱼白的窝
一些奇奇怪怪的东西
关于splice调用中易出现的一个bug的记录
总想着发现Linux的bug,明明十分普通却又辣么自信

问题

这两天打算过一遍《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