Выглядит разумно, хотя на самом деле нужно исправить утечку std
и aux
для детей и после цикла, а исходный родительский stdin
потерян навсегда.
Это, вероятно, было бы лучше с цветом ...
./a.out foo bar baz <stdin >stdout
std = dup(stdout) || |+==========================std
|| || ||
pipe(fd) || || pipe1[0] -- pipe0[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| || || ||
fork+exec(foo) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
close(aux) || || XX ||
|| || ||
pipe(fd) || || pipe2[0] -- pipe2[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| || || ||
fork+exec(bar) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
close(aux) || || XX ||
|| || ||
pipe(fd) || || pipe3[0] -- pipe3[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| XX || ||
|| /-------++-----------------+|
dup2(std, 1) || // || ||
|| || || ||
fork+exec(baz) || || || ||
foo
получает stdin=stdin
, stdout=pipe1[1]
bar
получает stdin=pipe1[0]
, stdout=pipe2[1]
baz
получает stdin=pipe2[0]
, stdout=stdout
Мое предложение отличается тем, что он избегает калечить stdin
и stdout
родителей, а только манипулирует ими внутри ребенка и никогда не пропускает никаких FD. Хотя это немного сложнее для диаграммы.
for cmd in cmds
if there is a next cmd
pipe(new_fds)
fork
if child
if there is a previous cmd
dup2(old_fds[0], 0)
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
close(new_fds[0])
dup2(new_fds[1], 1)
close(new_fds[1])
exec cmd || die
else
if there is a previous cmd
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
old_fds = new_fds
parent
cmds = [foo, bar, baz]
fds = {0: stdin, 1: stdout}
cmd = cmds[0] {
there is a next cmd {
pipe(new_fds)
new_fds = {3, 4}
fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1]}
}
fork => child
there is a next cmd {
close(new_fds[0])
fds = {0: stdin, 1: stdout, 4: pipe1[1]}
dup2(new_fds[1], 1)
fds = {0: stdin, 1: pipe1[1], 4: pipe1[1]}
close(new_fds[1])
fds = {0: stdin, 1: pipe1[1]}
}
exec(cmd)
there is a next cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[1] {
there is a next cmd {
pipe(new_fds)
new_fds = {5, 6}
fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
}
fork => child
there is a previous cmd {
dup2(old_fds[0], 0)
fds = {0: pipe1[0], 1: stdout,
3: pipe1[0], 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
close(old_fds[0])
fds = {0: pipe1[0], 1: stdout,
4: pipe1[1],
5: pipe2[0] 6: pipe2[1]}
close(old_fds[1])
fds = {0: pipe1[0], 1: stdout,
5: pipe2[0], 6: pipe2[1]}
}
there is a next cmd {
close(new_fds[0])
fds = {0: pipe1[0], 1: stdout, 6: pipe2[1]}
dup2(new_fds[1], 1)
fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]}
close(new_fds[1])
fds = {0: pipe1[0], 1: pipe1[1]}
}
exec(cmd)
there is a previous cmd {
close(old_fds[0])
fds = {0: stdin, 1: stdout, 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
close(old_fds[1])
fds = {0: stdin, 1: stdout, 5: pipe2[0], 6: pipe2[1]}
}
there is a next cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[2] {
fork => child
there is a previous cmd {
dup2(old_fds[0], 0)
fds = {0: pipe2[0], 1: stdout,
5: pipe2[0], 6: pipe2[1]}
close(old_fds[0])
fds = {0: pipe2[0], 1: stdout,
6: pipe2[1]}
close(old_fds[1])
fds = {0: pipe2[0], 1: stdout}
}
exec(cmd)
there is a previous cmd {
close(old_fds[0])
fds = {0: stdin, 1: stdout, 6: pipe2[1]}
close(old_fds[1])
fds = {0: stdin, 1: stdout}
}
}
Редактировать
Ваш обновленный код исправляет предыдущие утечки FD & hellip; но добавляет одно: вы теперь просачиваете std0
детям. Как говорит Джон, это, вероятно, не опасно для большинства программ ... но вы все равно должны написать оболочку с лучшим поведением, чем эта.
Даже если это временно, я бы настоятельно рекомендовал не искажать стандартную оболочку собственной / in / out / err (0/1/2), только делать это внутри дочернего объекта непосредственно перед exec. Зачем? Предположим, вы добавили отладку printf
в середине, или вам нужно выручить из-за ошибки. У вас будут проблемы, если вы сначала не очистите свои испорченные стандартные файловые дескрипторы. Пожалуйста, ради того, чтобы вещи работали, как ожидалось, даже в неожиданных сценариях , не гадите с ними, пока вам не понадобится.
Редактировать
Как я уже упоминал в других комментариях, разбиение его на более мелкие части значительно облегчает понимание. Этот маленький помощник должен быть легко понятным и не содержать ошибок:
/* cmd, argv: passed to exec
* fd_in, fd_out: when not -1, replaces stdin and stdout
* return: pid of fork+exec child
*/
int fork_and_exec_with_fds(char *cmd, char **argv, int fd_in, int fd_out) {
pid_t child = fork();
if (fork)
return child;
if (fd_in != -1 && fd_in != 0) {
dup2(fd_in, 0);
close(fd_in);
}
if (fd_out != -1 && fd_in != 1) {
dup2(fd_out, 1);
close(fd_out);
}
execvp(cmd, argv);
exit(-1);
}
Как следует это:
void run_pipeline(int num, char *cmds[], char **argvs[], int pids[]) {
/* initially, don't change stdin */
int fd_in = -1, fd_out;
int i;
for (i = 0; i < num; i++) {
int fd_pipe[2];
/* if there is a next command, set up a pipe for stdout */
if (i + 1 < num) {
pipe(fd_pipe);
fd_out = fd_pipe[1];
}
/* otherwise, don't change stdout */
else
fd_out = -1;
/* run child with given stdin/stdout */
pids[i] = fork_and_exec_with_fds(cmds[i], argvs[i], fd_in, fd_out);
/* nobody else needs to use these fds anymore
* safe because close(-1) does nothing */
close(fd_in);
close(fd_out);
/* set up stdin for next command */
fd_in = fd_pipe[0];
}
}
Вы можете видеть, как Bash execute_cmd.c#execute_disk_command
вызывается из execute_cmd.c#execute_pipeline
, xsh process.c#process_run
вызывается из jobs.c#job_run
, и даже каждый из BusyBox * различные маленькие и минимальные оболочки разбивают их на части.