Я пытаюсь создать простую оболочку, но я не могу заставить эту функцию работать. Я не могу найти в этом ничего плохого. Я тестирую простую команду, например "ls | sort", но я вообще ничего не получаю.
void execute(std::vector<Command *> cmds)
{
int inp[2], out[2];
pipe(inp);
pipe(out);
int status, fd = 0;
bool switcher = true;
for (auto i = 0; i < cmds.size(); i++)
{
auto pid = fork();
if (pid == -1) {
throw std::runtime_error("Could not fork.");
} else if (pid == 0) {
if (i == 0) {
dup2(inp[1], 1);
} else if (i == cmds.size() - 1) {
dup2(out[0], 0);
} else {
if (switcher) {
dup2(inp[0], 0);
dup2(out[1], 1);
switcher = false;
} else {
dup2(inp[1], 1);
dup2(out[0], 0);
switcher = true;
}
}
close(inp[0]);
close(inp[1]);
close(out[0]);
close(out[1]);
if(execvp(cmds[i]->args_char[0], cmds[i]->args_char.data()) < 0) {
std::cout << "Command not found." << std::endl;
exit(1);
}
} else {
close(inp[0]);
close(inp[1]);
close(out[0]);
close(out[1]);
wait(&status);
}
}
}
Здесь Command
struct:
struct Command {
int identity;
std::vector<char *> args_char;
std::vector<std::string> args_string;
bool redirectin;
bool redirectout;
std::string filename;
Command(int id, std::vector<char *> cmds_char, std::vector<std::string> cmds_string)
{
identity = id;
args_string = cmds_string;
args_char = cmds_char;
redirectin = false;
redirectout = false;
for (auto i = 0; i < args_string.size(); i++) {
if (args_string[i] == ">") {
redirectout = true;
filename = args_string.back();
args_char.erase(args_char.end() - 3, args_char.end());
break;
}
if (args_string[i] == "<") {
redirectin = true;
filename = args_string.back();
args_char.erase(args_char.end() - 3, args_char.end());
break;
}
}
if (redirectin == true && redirectout == true)
throw std::runtime_error("Invalid redirection.");
}
};
Как я уже упоминал в описании, я создаю простую оболочку, которая может обрабатывать несколько каналов и, в будущем, перенаправлять.
Было бы весьма признательно мнение кого-то другого.
Ваши проблемы:
ls
прежде чем запускать sort
.sort
. В цикле for
вы обрабатываете только дочерние элементы; родительский элемент должен возвратиться назад и запустить следующего дочернего элемента.
После цикла for
вам нужно закрыть каналы в родительском. Затем вам понадобится цикл для сбора трупов обоих дочерних процессов (отмечая, что если оболочка ранее запускала некоторые процессы, выполняющиеся в фоновом режиме, может существовать несколько трупов, отличных от тех, которые находятся в текущем конвейере до завершения текущего конвейера).
Также обратите внимание, что правильное место для сообщения об ошибках - cerr
, а не cout
.
void execute(std::vector<Command *> cmds)
{
int inp[2], out[2]; // Correct use of 2
if (pipe(inp) != 0 || pipe(out) != 0)
throw std::runtime_error("Failed to create pipes."); // may leak descriptors
int status, fd = 0;
bool switcher = true;
pid_t pids[2]; // Inappropriate use of 2
for (auto i = 0; i < cmds.size(); i++)
{
auto pid = fork();
if (pid == -1)
throw std::runtime_error("Could not fork.");
else if (pid == 0)
{
if (i == 0)
dup2(inp[1], 1);
else if (i == cmds.size() - 1)
dup2(out[0], 0);
else if (switcher)
{
dup2(inp[0], 0);
dup2(out[1], 1);
switcher = false;
}
else
{
dup2(inp[1], 1);
dup2(out[0], 0);
switcher = true;
}
close(inp[0]);
close(inp[1]);
close(out[0]);
close(out[1]);
execvp(cmds[i]->args_char[0], cmds[i]->args_char.data();
std::cerr << "Command not found." << std::endl;
exit(1);
}
}
else
pids[i] = pid;
//} else {
// close(inp[0]);
// close(inp[1]);
// close(out[0]);
// close(out[1]);
// wait(&status);
}
}
// Children launched - wait for the kids to die (morbid business!)
close(inp[0]);
close(inp[1]);
close(out[0]);
close(out[1]);
int corpse;
int kids = 2; // Inappropriate use of 2
while (kids > 0 && (corpse = wait(&status)) > 0)
{
for (int i = 0; i < kids; i++)
{
if (corpse == pids[i])
{
for (j = i+1; j < kids; j++)
pids[i++] = pids[j];
kids--;
}
}
}
}
Вероятно, лучший способ обработать цикл wait()
в C++ с помощью vector<pid_t> kids
и подходящих алгоритмов STL, применяемых к нему, но это упражнение для вас. Показанный код имеет предположение о том, что в нем есть два дочерних процесса, в двух точках (отмечены); вам нужно будет отсортировать их для более общего случая N процессов в конвейере, и этот вектор также должен помочь этому.
Обратите внимание, что если исключение выбрасывается, трубы не закрываются. Это утечка ресурсов. Кроме того, если первый процесс запущен, но второй вилок выходит из строя, у вас есть процесс, который (вероятно) не умрет, потому что его входные каналы все еще открыты в родительском процессе. Прочная программа должна будет исправить это.
Предупреждение: код не был передан через компилятор;там могут быть проблемы.
О, Футц! Когда в конвейере есть две команды (как в "ls | sort"), требуется только один канал. Я был втянут в код в вопросе!
Я очистил большую часть мусора, который не нужен для случая "ls | sort", как в функции struct Command
и в функции execute()
. Я получил базовый main()
который жестко кодирует команду, которая должна быть выполнена. Это приводит к SSCCE (Short, Self-Contained, Correct Example), например:
#include <vector>
#include <iostream>
#include <stdexcept>
#include <unistd.h>
struct Command
{
std::vector<char *> args_char;
Command(std::vector<char *> cmds_char)
{
args_char = cmds_char;
}
};
void execute(std::vector<Command *> cmds)
{
int inp[2];
if (pipe(inp) != 0)
throw std::runtime_error("Failed to create pipes.");
pid_t pids[2]; // Inappropriate use of 2
for (auto i = 0U; i < cmds.size(); i++)
{
auto pid = fork();
if (pid == -1)
throw std::runtime_error("Could not fork.");
else if (pid == 0)
{
std::cerr << i << ": PID " << (int)getpid() << "\n";
if (i == 0)
{
std::cerr << i << ": DUP 1\n";
dup2(inp[1], 1);
}
else if (i == cmds.size() - 1)
{
std::cerr << i << ": DUP 2\n";
dup2(inp[0], 0);
}
close(inp[0]);
close(inp[1]);
std::cerr << i << ": CMD " << cmds[i]->args_char[0] << "\n";
execlp(cmds[i]->args_char[0], cmds[i]->args_char[0], (char *)0);
std::cerr << "Command " << cmds[i]->args_char[0] << " not found." << std::endl;
exit(1);
}
else
pids[i] = pid;
}
// Children launched - wait for the kids to die (morbid business!)
std::cerr << "Parent: waiting for the inevitable\n";
close(inp[0]);
close(inp[1]);
int status;
int corpse;
int kids = 2; // Inappropriate use of 2
while (kids > 0 && (corpse = wait(&status)) > 0)
{
std::cerr << "2: KID " << corpse << " status " << status << "\n";
for (int i = 0; i < kids; i++)
{
if (corpse == pids[i])
{
for (int j = i + 1; j < kids; j++)
pids[i++] = pids[j];
kids--;
}
}
}
std::cerr << "2: DONE\n";
}
int main()
{
char prg0[] = "ls";
char prg1[] = "sort";
std::vector<char *> cmd0 = { prg0, 0 };
std::vector<char *> cmd1 = { prg1, 0 };
std::vector<Command *> cmds =
{
new Command(cmd0),
new Command(cmd1),
};
try
{
execute(cmds);
}
catch (std::exception &bug)
{
std::cerr << "Exception: " << bug.what() << "thrown\n";
}
return 0;
}
Иллюстративный результат:
Parent: waiting for the inevitable
0: PID 36439
0: DUP 1
0: CMD ls
1: PID 36440
1: DUP 2
1: CMD sort
2: KID 36439 status 0
cmds
cmds.cpp
cmds.dSYM
makefile
2: KID 36440 status 0
2: DONE
Command
будет полезным, вы будете правы.