Linux实现返回文件描述符的popen

2017-8-24 Plan C Linux

blog.kurukurumi.com原创,转载请注明出处。

E-mail:hubenchang0515@outlook.com


        popen是stdio.h里的一个函数,它的功能是打开一个进程来执行命令并与该进程建立一个管道。

#include <stdio.h>
FILE* popen(const char* cmd,const char* type);
// 打开进程并创建管道:成功返回FILE*,失败返回NULL

int pclose(FILE* fp);
// 关闭popen打开的文件指针:返回进程的返回值


        popen的原理是通过pipe创建一个管道,然后通过fork创建一个子进程,子进程通过dup2将自己的标准输入输出重定向到管道,然后通过exec函数族执行命令,父进程将管道作为返回值返回。


        根据上述原理可以实现一个返回值是文件描述符的版本:

/*
** Author : Plan C
** GitHub : 
** Blog   : blog.kurukurumi.com
** E-main : hubenchang0515@outlook.com
**
*/

#include <unistd.h>
#include <sys/wait.h>

/* 保存子进程的id */
#ifndef PIPE_MAX
#	define PIPE_MAX 128
	pid_t childpids[PIPE_MAX];
#endif

/* 创建进程并返回管道的描述符 */
int fdpopen(const char* cmd,const char* mode)
{
	/* 创建管道 */
	int pfd[2];
	if(pipe(pfd) == -1)
	{
		return -1;
	}
	
	/* 超过上限,关闭管道并返回 */
	if(pfd[0] >= PIPE_MAX || pfd[1] >= PIPE_MAX)
	{
		close(pfd[0]);
		close(pfd[1]);
		
		return -1;
	}
	
	pid_t pid = fork(); // 创建进程
	if(pid == 0) // 子进程
	{
		if(mode[0] == 'r')  // 父进程读,子进程写模式
		{
			close(pfd[0]); // 子进程关闭管道的读取端
			/* 将stdout重定向到管道的写端 */
			int out = dup2(pfd[1],STDOUT_FILENO); 
			if(out == -1)  // 重定向失败
			{	
				_exit(1);
			}
			close(pfd[1]); // 关闭旧的描述符
			/* 执行命令 */
			int rval = execl("/bin/sh","sh","-c",cmd,NULL); 
			close(out); // 关闭管道
			_exit(rval); // 子进程退出
		}
		else if(mode[0] == 'w') // 父进程写,子进程读模式
		{
			close(pfd[1]); // 子进程关闭写入端
			/* 将stdin重定向到管道的读取端 */
			int in = dup2(pfd[0],STDIN_FILENO);
			if(in == -1) // 重定向失败
			{
				_exit(1);
			}
			close(pfd[0]); // 关闭旧的描述符
			/* 执行命令 */
			int rval = execl("/bin/sh","sh","-c",cmd,NULL); 
			close(in);   // 关闭管道
			_exit(rval); // 子进程退出
		}
		else // 错误的模式
		{
			_exit(1);
		}
	}
	
	/* 父进程 */
	if(pid == -1) // fork失败 返回-1
	{
		return -1;
	}
	else if(mode[0] == 'r') // 父进程读模式
	{
		childpids[pfd[0]] = pid; // 保存子进程的pid
		return pfd[0]; // 返回管道读取端的描述符
	}
	else if(mode[0] == 'w') // 父进程写模式
	{
		childpids[pfd[1]] = pid; // 保存子进程的pid
		return pfd[1]; // 返回管道写入端的描述符
	}
	else
	{
		return -1;
	}

}

/* 关闭fdpopen打开的描述符 */
int fdpclose(int fd)
{
	int stat;
	close(fd); // 关闭管道
	waitpid(childpids[fd],&stat,0); // 等待子进程退出
	return stat; // 返回子进程的退出码
}


        测试一下:

int main()
{
	int ls = fdpopen("ls -l","r");
	int more = fdpopen("more","w");
	char data[128];
	ssize_t len;
	do
	{
		len = read(ls,data,128);
		write(more,data,len);
	}while(len == 128);
	
	fdpclose(ls);
	fdpclose(more);
	
	return 0;
	
}


发表评论:

Powered by emlog
鄂ICP备16003833号