Opt*_*Pwn 4 c unix shell pipeline pipe
所以我正在制作一个UNIX minishell,我正在尝试添加管道,所以我可以这样做:
ps aux | grep dh | grep -v grep | cut -c1-5
Run Code Online (Sandbox Code Playgroud)
但是我把头缠绕在管道部分上时遇到了麻烦.我替换所有的"|" 字符为0,然后将每一行作为法线运行.但是,我试图转移输出和输入.命令的输入需要是前一个命令的输出,命令的输出需要是下一个命令的输入.
我正在使用管道这样做,但我无法弄清楚在哪里调用pipe()以及在哪里关闭它们.从主处理函数,processline(),我有这个代码:
if((pix = findUnquotChar(line_itr, '|')))
{
line_itr[pix++] = 0;
if(pipe (fd) < 0) perror("pipe");
processline(line_itr, inFD, fd[1], pl_flags);
line_itr = &(line_itr[pix]);
while((pix = findUnquotChar(line_itr, '|')) && pix < line_len)
{
line_itr[pix++] = 0;
//? if(pipe (fd) < 0) perror("pipe");
processline(line_itr, fd[0], fd[1] pl_flags);
line_itr = &(line_itr[pix]);
//? close(fd[0]);
//? close(fd[1]);
}
return;
}
Run Code Online (Sandbox Code Playgroud)
所以,我是递归的(上面的代码在processline中)在"|"之间发送命令 由流程线处理.你可以看到我在上面的代码注释,我不知道如何使它工作.processline的第二个和第三个参数分别是inputFD和outputFD,所以我需要处理命令,将输出写入管道,然后在下一个命令上再次调用processline,但是这次上一个命令的输出是输入.这似乎并不像它可以工作,因为每次我关闭fd [0]我失去了以前的输出.我需要两个独立的管道,我可以来回翻转吗?
如果您需要任何其他信息,我只是想知道如何使用单个管道实现这一点.这是整个流程线功能,以备您查看:
编辑:如果有人有一个实现管道的shell的例子,我希望链接到源代码,到目前为止我还没能在谷歌上找到一个.
编辑2:这是我的困境的一个例子:
echo a | echo b | echo c
Run Code Online (Sandbox Code Playgroud)
所以首先我会像这样调用shell:
processline("echo a", 0, fd[1], flags);
....
processline("echo b", fd[0], NOT_SURE_GOES_HERE[1], flags);
....
processline("echo c", NOT_SURE_GOES_HERE[0], NOT_SURE_EITHER[1], flags);
Run Code Online (Sandbox Code Playgroud)
每次迭代都会发生一次,正如您所看到的,我无法弄清楚第二次和第三次(等等)迭代的输入文件描述符和输出文件描述符要传递什么.
Jon*_*ler 10
这是一些适度但通用的简单代码来执行管道,这是我正在调用的程序pipeline
.它是一个单独的文件中的SSCCE,虽然我有文件stderr.h
和stderr.c
库中的单独文件与我的所有程序链接.(其实,我有一个更复杂的在我的"真实"的功能stderr.c
和stderr.h
,但是这是一个很好的起点.)
代码以两种方式运行.如果您不提供参数,那么它将运行内置管道:
who | awk '{print $1}' | sort | uniq -c | sort -n
Run Code Online (Sandbox Code Playgroud)
这计算每个人登录系统的次数,按照会话数量增加的顺序显示列表.或者,您可以使用一系列参数来调用,这些参数是您要调用的命令行,使用带引号的管道'|'
(或"|"
)来分隔命令:
有效:
pipeline
pipeline ls '|' wc
pipeline who '|' awk '{print $1}' '|' sort '|' uniq -c '|' sort -n
pipeline ls
Run Code Online (Sandbox Code Playgroud)
无效:
pipeline '|' wc -l
pipeline ls '|' '|' wc -l
pipeline ls '|' wc -l '|'
Run Code Online (Sandbox Code Playgroud)
最后三次调用强制"管道作为分隔符".代码没有错误检查每个系统调用; 它错误检查fork()
,execvp()
和pipe()
,但不会检查的dup2()
和close()
.它不包括生成的命令的诊断打印; 一个-x
选项pipeline
是一个明智的补充,使它打印出它的作用的痕迹.它也不会以管道中最后一个命令的退出状态退出.
请注意,代码以分叉的子项开头.子项将成为管道中的最后一个进程,但首先创建一个管道并分叉另一个进程来运行管道中的早期进程.相互递归函数是不太可能整理出来的东西的唯一途径,但他们留下最少的代码重复(代码前几稿了的内容exec_nth_command()
在很大程度上是重复exec_pipeline()
和exec_pipe_command()
).
这里的流程结构使得原始流程只知道流水线中的最后一个流程.可以以原始进程是管道中每个进程的父进程的方式重新设计事物,因此原始进程可以单独报告管道中每个命令的状态.我还没有修改代码以允许该结构; 它会有点复杂,虽然不是那么可怕.
/* One way to create a pipeline of N processes */
/* stderr.h */
#ifndef STDERR_H_INCLUDED
#define STDERR_H_INCLUDED
static void err_setarg0(const char *argv0);
static void err_sysexit(char const *fmt, ...);
static void err_syswarn(char const *fmt, ...);
#endif /* STDERR_H_INCLUDED */
/* pipeline.c */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
/*#include "stderr.h"*/
typedef int Pipe[2];
/* exec_nth_command() and exec_pipe_command() are mutually recursive */
static void exec_pipe_command(int ncmds, char ***cmds, Pipe output);
/* With the standard output plumbing sorted, execute Nth command */
static void exec_nth_command(int ncmds, char ***cmds)
{
assert(ncmds >= 1);
if (ncmds > 1)
{
pid_t pid;
Pipe input;
if (pipe(input) != 0)
err_sysexit("Failed to create pipe");
if ((pid = fork()) < 0)
err_sysexit("Failed to fork");
if (pid == 0)
{
/* Child */
exec_pipe_command(ncmds-1, cmds, input);
}
/* Fix standard input to read end of pipe */
dup2(input[0], 0);
close(input[0]);
close(input[1]);
}
execvp(cmds[ncmds-1][0], cmds[ncmds-1]);
err_sysexit("Failed to exec %s", cmds[ncmds-1][0]);
/*NOTREACHED*/
}
/* Given pipe, plumb it to standard output, then execute Nth command */
static void exec_pipe_command(int ncmds, char ***cmds, Pipe output)
{
assert(ncmds >= 1);
/* Fix stdout to write end of pipe */
dup2(output[1], 1);
close(output[0]);
close(output[1]);
exec_nth_command(ncmds, cmds);
}
/* Execute the N commands in the pipeline */
static void exec_pipeline(int ncmds, char ***cmds)
{
assert(ncmds >= 1);
pid_t pid;
if ((pid = fork()) < 0)
err_syswarn("Failed to fork");
if (pid != 0)
return;
exec_nth_command(ncmds, cmds);
}
/* Collect dead children until there are none left */
static void corpse_collector(void)
{
pid_t parent = getpid();
pid_t corpse;
int status;
while ((corpse = waitpid(0, &status, 0)) != -1)
{
fprintf(stderr, "%d: child %d status 0x%.4X\n",
(int)parent, (int)corpse, status);
}
}
/* who | awk '{print $1}' | sort | uniq -c | sort -n */
static char *cmd0[] = { "who", 0 };
static char *cmd1[] = { "awk", "{print $1}", 0 };
static char *cmd2[] = { "sort", 0 };
static char *cmd3[] = { "uniq", "-c", 0 };
static char *cmd4[] = { "sort", "-n", 0 };
static char **cmds[] = { cmd0, cmd1, cmd2, cmd3, cmd4 };
static int ncmds = sizeof(cmds) / sizeof(cmds[0]);
static void exec_arguments(int argc, char **argv)
{
/* Split the command line into sequences of arguments */
/* Break at pipe symbols as arguments on their own */
char **cmdv[argc/2]; // Way too many
char *args[argc+1];
int cmdn = 0;
int argn = 0;
cmdv[cmdn++] = &args[argn];
for (int i = 1; i < argc; i++)
{
char *arg = argv[i];
if (strcmp(arg, "|") == 0)
{
if (i == 1)
err_sysexit("Syntax error: pipe before any command");
if (args[argn-1] == 0)
err_sysexit("Syntax error: two pipes with no command between");
arg = 0;
}
args[argn++] = arg;
if (arg == 0)
cmdv[cmdn++] = &args[argn];
}
if (args[argn-1] == 0)
err_sysexit("Syntax error: pipe with no command following");
args[argn] = 0;
exec_pipeline(cmdn, cmdv);
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc == 1)
{
/* Run the built in pipe-line */
exec_pipeline(ncmds, cmds);
}
else
{
/* Run command line specified by user */
exec_arguments(argc, argv);
}
corpse_collector();
return(0);
}
/* stderr.c */
/*#include "stderr.h"*/
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
static const char *arg0 = "<undefined>";
static void err_setarg0(const char *argv0)
{
arg0 = argv0;
}
static void err_vsyswarn(char const *fmt, va_list args)
{
int errnum = errno;
fprintf(stderr, "%s:%d: ", arg0, (int)getpid());
vfprintf(stderr, fmt, args);
if (errnum != 0)
fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
putc('\n', stderr);
}
static void err_syswarn(char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
err_vsyswarn(fmt, args);
va_end(args);
}
static void err_sysexit(char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
err_vsyswarn(fmt, args);
va_end(args);
exit(1);
}
Run Code Online (Sandbox Code Playgroud)
POSIX 信号概念部分讨论了SIGCHLD:
在SIG_DFL下:
如果默认操作是忽略该信号,则信号的传递不应对该过程产生影响.
在SIG_IGN下:
如果SIGCHLD信号的操作设置为SIG_IGN,则调用进程的子进程在终止时不应转换为僵尸进程.如果调用进程随后等待其子进程,并且进程没有被等待转换为僵尸进程的子进程,它将阻塞直到其所有子进程终止,并且wait(),waitid()和waitpid()将失败并将errno设置为
[ECHILD]
.
描述<signal.h>
有一个信号的默认配置表,对于SIGCHLD,默认为I(SIG_IGN).
我在上面的代码中添加了另一个函数:
#include <signal.h>
typedef void (*SigHandler)(int signum);
static void sigchld_status(void)
{
const char *handling = "Handler";
SigHandler sigchld = signal(SIGCHLD, SIG_IGN);
signal(SIGCHLD, sigchld);
if (sigchld == SIG_IGN)
handling = "Ignored";
else if (sigchld == SIG_DFL)
handling = "Default";
printf("SIGCHLD set to %s\n", handling);
}
Run Code Online (Sandbox Code Playgroud)
我在调用之后立即调用它err_setarg0()
,并在Mac OS X 10.7.5和Linux(RHEL 5,x86/64)上报告"默认".我通过运行验证了它的操作:
(trap '' CHLD; pipeline)
Run Code Online (Sandbox Code Playgroud)
在两个平台上,报告"忽略",pipeline
命令不再报告孩子的退出状态; 它没有得到它.
因此,如果程序忽略了SIGCHLD,它不会生成任何僵尸,但会等到"所有"子节点终止.也就是说,直到所有直接子女终止; 一个过程不能等待它的孙子或更远的后代,也不能等待它的兄弟姐妹,也不能等待它的祖先.
另一方面,如果SIGCHLD的设置是默认设置,则忽略信号,并创建僵尸.
这是编写本程序最方便的行为.该corpse_collector()
函数有一个循环,用于收集来自任何子节点的状态信息.这个代码一次只有一个孩子; 管道的其余部分作为管道中最后一个进程的子进程(子进程,子进程,...)运行.
但是我遇到了僵尸/尸体的麻烦.我的老师让我按照你的方式实现它,因为
cmd1
不是cmd2
以下情况的父母:"cmd1 | cmd2 | cmd3
".除非我告诉我的shell等待每个进程(cmd1
,cmd2
和cmd3
),而不是只等待最后一个进程(cmd3
),整个管道在输出到达结束之前关闭.我无法找到一个等待它们的好方法; 我的老师说要用WNOHANG.
我不确定我是否理解这个问题.使用我提供的代码,cmd3
它是父命令cmd2
,并且cmd2
是cmd1
3命令管道中的父代(并且shell是其父代cmd3
),因此shell只能等待cmd3
.我原先说的是:
这里的流程结构使得原始流程只知道流水线中的最后一个流程.可以以原始进程是管道中每个进程的父进程的方式重新设计事物,因此原始进程可以单独报告管道中每个命令的状态.我还没有修改代码以允许该结构; 它会有点复杂,虽然不是那么可怕.
如果您的shell能够等待管道中的所有三个命令,那么您必须使用备用组织.
该waitpid()
描述包括:
pid参数指定请求其状态的一组子进程.waitpid()函数只能从这个集合中返回子进程的状态:
如果pid等于(pid_t)-1,则为任何子进程请求状态.在这方面,waitpid()等同于wait().
如果pid大于0,则它指定请求其状态的单个子进程的进程ID.
如果pid为0,则为进程组ID等于调用进程ID的任何子进程请求状态.
如果pid小于(pid_t)-1,则为进程组ID等于pid绝对值的任何子进程请求状态.
options参数是从头部中定义的零个或多个以下标志的按位包含OR构造的:
...
WNOHANG
waitpid()
如果状态不能立即用于pid指定的子进程之一,则该函数不应暂停执行调用线程....
这意味着如果您正在使用进程组并且shell知道正在运行管道的哪个进程组(例如,因为管道在第一个进程中被放入其自己的进程组中),那么父进程可以等待相应的孩子要终止.
...漫无目的...我认为这里有一些有用的信息; 可能应该有更多我正在写作,但我的思绪一片空白.