mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03:00
sh: Add basic command piping support.
This commit is contained in:
parent
aa84e2fc64
commit
07de1147ad
Notes:
sideshowbarker
2024-07-19 14:35:44 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/07de1147ad6
215
Userland/sh.cpp
215
Userland/sh.cpp
@ -143,6 +143,122 @@ static int try_exec(const char* path, char** argv)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Redirection {
|
||||||
|
enum Type { Pipe, File, Rewire };
|
||||||
|
Type type;
|
||||||
|
int fd;
|
||||||
|
int rewire_fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Subcommand {
|
||||||
|
Vector<String> args;
|
||||||
|
Vector<Redirection> redirections;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ParseState {
|
||||||
|
Free,
|
||||||
|
InSingleQuotes,
|
||||||
|
InDoubleQuotes,
|
||||||
|
};
|
||||||
|
|
||||||
|
static Vector<Subcommand> parse(const char* command)
|
||||||
|
{
|
||||||
|
ParseState state = ParseState::Free;
|
||||||
|
Vector<Subcommand> subcommands;
|
||||||
|
|
||||||
|
Vector<String> current_command;
|
||||||
|
Vector<char> current_token;
|
||||||
|
|
||||||
|
auto commit_token = [&] {
|
||||||
|
if (current_token.is_empty())
|
||||||
|
return;
|
||||||
|
current_command.append(String::copy(current_token));
|
||||||
|
current_token.clear_with_capacity();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto commit_subcommand = [&] {
|
||||||
|
if (current_command.is_empty())
|
||||||
|
return;
|
||||||
|
subcommands.append({ current_command });
|
||||||
|
current_command.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto begin_pipe = [&] {
|
||||||
|
Vector<Redirection> redirections;
|
||||||
|
redirections.append({ Redirection::Pipe, STDOUT_FILENO });
|
||||||
|
subcommands.append({ current_command, move(redirections) });
|
||||||
|
current_command.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* c = command; *c; ++c) {
|
||||||
|
switch (state) {
|
||||||
|
case ParseState::Free:
|
||||||
|
if (*c == ' ') {
|
||||||
|
commit_token();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (*c == '|') {
|
||||||
|
commit_token();
|
||||||
|
if (current_command.is_empty()) {
|
||||||
|
fprintf(stderr, "Syntax error: Nothing before pipe (|)\n");
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
begin_pipe();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
if (*c == '>') {
|
||||||
|
begin_redirect(STDOUT_FILENO);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (*c == '<') {
|
||||||
|
begin_redirect(STDIN_FILENO);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (*c == '\'') {
|
||||||
|
state = ParseState::InSingleQuotes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (*c == '\"') {
|
||||||
|
state = ParseState::InDoubleQuotes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current_token.append(*c);
|
||||||
|
break;
|
||||||
|
case ParseState::InSingleQuotes:
|
||||||
|
if (*c == '\'') {
|
||||||
|
commit_token();
|
||||||
|
state = ParseState::Free;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current_token.append(*c);
|
||||||
|
break;
|
||||||
|
case ParseState::InDoubleQuotes:
|
||||||
|
if (*c == '\"') {
|
||||||
|
commit_token();
|
||||||
|
state = ParseState::Free;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current_token.append(*c);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
commit_token();
|
||||||
|
commit_subcommand();
|
||||||
|
|
||||||
|
if (!subcommands.is_empty()) {
|
||||||
|
for (auto& redirection : subcommands.last().redirections) {
|
||||||
|
if (redirection.type == Redirection::Pipe) {
|
||||||
|
fprintf(stderr, "Syntax error: Nothing after last pipe (|)\n");
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subcommands;
|
||||||
|
}
|
||||||
|
|
||||||
static int runcmd(char* cmd)
|
static int runcmd(char* cmd)
|
||||||
{
|
{
|
||||||
if (cmd[0] == 0)
|
if (cmd[0] == 0)
|
||||||
@ -150,41 +266,101 @@ static int runcmd(char* cmd)
|
|||||||
char buf[128];
|
char buf[128];
|
||||||
memcpy(buf, cmd, 128);
|
memcpy(buf, cmd, 128);
|
||||||
|
|
||||||
char* argv[32];
|
auto subcommands = parse(cmd);
|
||||||
size_t argc = 1;
|
|
||||||
argv[0] = &buf[0];
|
|
||||||
size_t buflen = strlen(buf);
|
|
||||||
for (size_t i = 0; i < buflen; ++i) {
|
|
||||||
if (buf[i] == ' ') {
|
|
||||||
buf[i] = '\0';
|
|
||||||
argv[argc++] = &buf[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
argv[argc] = nullptr;
|
|
||||||
|
|
||||||
int retval = 0;
|
#ifdef PARSE_DEBUG
|
||||||
if (handle_builtin(argc, argv, retval)) {
|
for (int i = 0; i < subcommands.size(); ++i) {
|
||||||
|
for (int j = 0; j < i; ++j)
|
||||||
|
printf(" ");
|
||||||
|
for (auto& arg : subcommands[i].args) {
|
||||||
|
printf("<%s> ", arg.characters());
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (subcommands.is_empty())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
Vector<int> fds;
|
||||||
|
|
||||||
|
for (int i = 0; i < subcommands.size(); ++i) {
|
||||||
|
auto& subcommand = subcommands[i];
|
||||||
|
for (auto& redirection : subcommand.redirections) {
|
||||||
|
if (redirection.type == Redirection::Pipe) {
|
||||||
|
int pipefd[2];
|
||||||
|
int rc = pipe(pipefd);
|
||||||
|
if (rc < 0) {
|
||||||
|
perror("pipe");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
subcommand.redirections.append({ Redirection::Rewire, STDOUT_FILENO, pipefd[1] });
|
||||||
|
auto& next_command = subcommands[i + 1];
|
||||||
|
next_command.redirections.append({ Redirection::Rewire, STDIN_FILENO, pipefd[0] });
|
||||||
|
fds.append(pipefd[0]);
|
||||||
|
fds.append(pipefd[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct termios trm;
|
struct termios trm;
|
||||||
tcgetattr(0, &trm);
|
tcgetattr(0, &trm);
|
||||||
|
|
||||||
|
Vector<pid_t> children;
|
||||||
|
|
||||||
|
for (int i = 0; i < subcommands.size(); ++i) {
|
||||||
|
auto& subcommand = subcommands[i];
|
||||||
|
Vector<const char*> argv;
|
||||||
|
for (auto& arg : subcommand.args)
|
||||||
|
argv.append(arg.characters());
|
||||||
|
argv.append(nullptr);
|
||||||
|
|
||||||
|
int retval = 0;
|
||||||
|
if (handle_builtin(argv.size() - 1, (char**)argv.data(), retval))
|
||||||
|
return retval;
|
||||||
|
|
||||||
pid_t child = fork();
|
pid_t child = fork();
|
||||||
if (!child) {
|
if (!child) {
|
||||||
setpgid(0, 0);
|
setpgid(0, 0);
|
||||||
tcsetpgrp(0, getpid());
|
tcsetpgrp(0, getpid());
|
||||||
int ret = try_exec(argv[0], argv);
|
for (auto& redirection : subcommand.redirections) {
|
||||||
if (ret < 0) {
|
if (redirection.type == Redirection::Rewire) {
|
||||||
printf("exec failed: %s (%s)\n", cmd, strerror(errno));
|
dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), redirection.rewire_fd, redirection.fd);
|
||||||
|
int rc = dup2(redirection.rewire_fd, redirection.fd);
|
||||||
|
if (rc < 0) {
|
||||||
|
perror("dup2");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& fd : fds)
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
int rc = execvp(argv[0], (char* const*)argv.data());
|
||||||
|
if (rc < 0) {
|
||||||
|
perror("execvp");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
// We should never get here!
|
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
children.append(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbgprintf("Closing fds in shell process:\n");
|
||||||
|
for (auto& fd : fds) {
|
||||||
|
dbgprintf(" %d\n", fd);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbgprintf("Now we gotta wait on children:\n");
|
||||||
|
for (auto& child : children)
|
||||||
|
dbgprintf(" %d\n", child);
|
||||||
|
|
||||||
int wstatus = 0;
|
int wstatus = 0;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
for (auto& child : children) {
|
||||||
do {
|
do {
|
||||||
rc = waitpid(child, &wstatus, 0);
|
rc = waitpid(child, &wstatus, 0);
|
||||||
if (rc < 0 && errno != EINTR) {
|
if (rc < 0 && errno != EINTR) {
|
||||||
@ -192,6 +368,7 @@ static int runcmd(char* cmd)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while(errno == EINTR);
|
} while(errno == EINTR);
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Should I really have to tcsetpgrp() after my child has exited?
|
// FIXME: Should I really have to tcsetpgrp() after my child has exited?
|
||||||
// Is the terminal controlling pgrp really still the PGID of the dead process?
|
// Is the terminal controlling pgrp really still the PGID of the dead process?
|
||||||
@ -202,6 +379,7 @@ static int runcmd(char* cmd)
|
|||||||
if (WIFEXITED(wstatus)) {
|
if (WIFEXITED(wstatus)) {
|
||||||
if (WEXITSTATUS(wstatus) != 0)
|
if (WEXITSTATUS(wstatus) != 0)
|
||||||
printf("Exited with status %d\n", WEXITSTATUS(wstatus));
|
printf("Exited with status %d\n", WEXITSTATUS(wstatus));
|
||||||
|
return WEXITSTATUS(wstatus);
|
||||||
} else {
|
} else {
|
||||||
if (WIFSIGNALED(wstatus)) {
|
if (WIFSIGNALED(wstatus)) {
|
||||||
switch (WTERMSIG(wstatus)) {
|
switch (WTERMSIG(wstatus)) {
|
||||||
@ -214,9 +392,10 @@ static int runcmd(char* cmd)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf("Exited abnormally\n");
|
printf("Exited abnormally\n");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return retval;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
|
Loading…
Reference in New Issue
Block a user