Shell: Implement redirections
This works well enough. At the moment redirections can appear anywhere in the command **after** environment variables and possible alias command. This means commands like `>foo echo hello` are not supported. I don't think this is big enough problem for now (99% time redirections are given after arguments) that i would rewrite the environment and alias parsing.
This commit is contained in:
parent
05affda20c
commit
c1978f9133
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <BAN/String.h>
|
||||
|
||||
#define COMMAND_GET_MACRO(_0, _1, _2, NAME, ...) NAME
|
||||
#define COMMAND_GET_MACRO(_0, _1, _2, _3, NAME, ...) NAME
|
||||
|
||||
#define COMMAND_MOVE_0(class) \
|
||||
class(class&& o) { } \
|
||||
|
@ -13,7 +13,10 @@
|
|||
#define COMMAND_MOVE_2(class, var1, var2) \
|
||||
class(class&& o) { var1 = BAN::move(o.var1); var2 = BAN::move(o.var2); } \
|
||||
class& operator=(class&& o) { var1 = BAN::move(o.var1); var2 = BAN::move(o.var2); return *this; }
|
||||
#define COMMAND_MOVE(class, ...) COMMAND_GET_MACRO(_0 __VA_OPT__(,) __VA_ARGS__, COMMAND_MOVE_2, COMMAND_MOVE_1, COMMAND_MOVE_0)(class, __VA_ARGS__)
|
||||
#define COMMAND_MOVE_3(class, var1, var2, var3) \
|
||||
class(class&& o) { var1 = BAN::move(o.var1); var2 = BAN::move(o.var2); var3 = BAN::move(o.var3); } \
|
||||
class& operator=(class&& o) { var1 = BAN::move(o.var1); var2 = BAN::move(o.var2); var3 = BAN::move(o.var3); return *this; }
|
||||
#define COMMAND_MOVE(class, ...) COMMAND_GET_MACRO(_0 __VA_OPT__(,) __VA_ARGS__, COMMAND_MOVE_3, COMMAND_MOVE_2, COMMAND_MOVE_1, COMMAND_MOVE_0)(class, __VA_ARGS__)
|
||||
|
||||
#define COMMAND_RULE5(class, ...) \
|
||||
class() = default; \
|
||||
|
@ -67,9 +70,19 @@ struct SingleCommand
|
|||
CommandArgument value;
|
||||
};
|
||||
|
||||
COMMAND_RULE5(SingleCommand, environment, arguments);
|
||||
struct Redirection
|
||||
{
|
||||
CommandArgument destination;
|
||||
int source_fd;
|
||||
bool append;
|
||||
bool duplicate;
|
||||
bool input;
|
||||
};
|
||||
|
||||
COMMAND_RULE5(SingleCommand, environment, arguments, redirections);
|
||||
BAN::Vector<EnvironmentVariable> environment;
|
||||
BAN::Vector<CommandArgument> arguments;
|
||||
BAN::Vector<Redirection> redirections;
|
||||
};
|
||||
|
||||
struct PipedCommand
|
||||
|
|
|
@ -99,6 +99,47 @@ BAN::ErrorOr<Execute::ExecuteResult> Execute::execute_command_no_wait(const Inte
|
|||
CHECK_FD_OR_PERROR_AND_EXIT(command.fd_in, STDIN_FILENO);
|
||||
CHECK_FD_OR_PERROR_AND_EXIT(command.fd_out, STDOUT_FILENO);
|
||||
|
||||
for (const auto& redirection : command.redirections)
|
||||
{
|
||||
int dst_fd = -1;
|
||||
|
||||
if (redirection.duplicate)
|
||||
{
|
||||
if (!redirection.path.empty())
|
||||
{
|
||||
dst_fd = 0;
|
||||
|
||||
for (char ch : redirection.path)
|
||||
{
|
||||
if (!isdigit(ch))
|
||||
{
|
||||
dst_fd = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
dst_fd = (dst_fd * 10) + (ch - '0');
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int flags = O_CREAT
|
||||
| (redirection.input ? O_RDONLY : O_WRONLY)
|
||||
| (redirection.append ? O_APPEND : O_TRUNC);
|
||||
dst_fd = open(redirection.path.data(), flags, 0644);
|
||||
if (dst_fd == -1)
|
||||
{
|
||||
perror("open");
|
||||
exit(errno);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_FD_OR_PERROR_AND_EXIT(dst_fd, redirection.source_fd);
|
||||
|
||||
if (!redirection.duplicate)
|
||||
close(dst_fd);
|
||||
}
|
||||
|
||||
execv(command.command.get<BAN::String>().data(), const_cast<char* const*>(exec_args.data()));
|
||||
perror("execv");
|
||||
exit(errno);
|
||||
|
@ -125,6 +166,7 @@ BAN::ErrorOr<int> Execute::execute_command_sync(BAN::Span<const BAN::String> arg
|
|||
.command = {},
|
||||
.arguments = arguments,
|
||||
.environments = {},
|
||||
.redirections = {},
|
||||
.fd_in = fd_in,
|
||||
.fd_out = fd_out,
|
||||
.background = false,
|
||||
|
@ -194,6 +236,22 @@ BAN::ErrorOr<void> Execute::execute_command(const PipedCommand& piped_command)
|
|||
return result;
|
||||
};
|
||||
|
||||
const auto evaluate_redirections =
|
||||
[this](BAN::Span<const SingleCommand::Redirection> redirections) -> BAN::ErrorOr<BAN::Vector<InternalCommand::Redirection>>
|
||||
{
|
||||
BAN::Vector<InternalCommand::Redirection> result;
|
||||
TRY(result.reserve(redirections.size()));
|
||||
for (const auto& redirection : redirections)
|
||||
TRY(result.push_back({
|
||||
.path = TRY(redirection.destination.evaluate(*this)),
|
||||
.source_fd = redirection.source_fd,
|
||||
.append = redirection.append,
|
||||
.duplicate = redirection.duplicate,
|
||||
.input = redirection.input,
|
||||
}));
|
||||
return result;
|
||||
};
|
||||
|
||||
const int stdin_flags = fcntl(STDIN_FILENO, F_GETFL);
|
||||
if (stdin_flags == -1)
|
||||
perror("fcntl");
|
||||
|
@ -221,11 +279,13 @@ BAN::ErrorOr<void> Execute::execute_command(const PipedCommand& piped_command)
|
|||
|
||||
const auto arguments = TRY_OR_PERROR_AND_BREAK(evaluate_arguments(piped_command.commands[i].arguments.span()));
|
||||
const auto environments = TRY_OR_PERROR_AND_BREAK(evaluate_environment(piped_command.commands[i].environment.span()));
|
||||
const auto redirections = TRY_OR_PERROR_AND_BREAK(evaluate_redirections(piped_command.commands[i].redirections.span()));
|
||||
|
||||
InternalCommand command {
|
||||
.command = {},
|
||||
.arguments = arguments.span(),
|
||||
.environments = environments.span(),
|
||||
.redirections = redirections.span(),
|
||||
.fd_in = fd_in,
|
||||
.fd_out = fd_out,
|
||||
.background = piped_command.background,
|
||||
|
|
|
@ -12,6 +12,7 @@ class Execute
|
|||
public:
|
||||
Execute() = default;
|
||||
|
||||
// FIXME: remove this, this is only used by *builtin* time which should not be a builtin
|
||||
BAN::ErrorOr<int> execute_command_sync(BAN::Span<const BAN::String> arguments, int fd_in, int fd_out);
|
||||
BAN::ErrorOr<void> execute_command(const SingleCommand&, int fd_in, int fd_out, bool background, pid_t pgrp = 0);
|
||||
BAN::ErrorOr<void> execute_command(const PipedCommand&);
|
||||
|
@ -39,9 +40,19 @@ private:
|
|||
BAN::String value;
|
||||
};
|
||||
|
||||
struct Redirection
|
||||
{
|
||||
BAN::String path;
|
||||
int source_fd;
|
||||
bool append;
|
||||
bool duplicate;
|
||||
bool input;
|
||||
};
|
||||
|
||||
Command command;
|
||||
BAN::Span<const BAN::String> arguments;
|
||||
BAN::Span<const Environment> environments;
|
||||
BAN::Span<Redirection> redirections;
|
||||
int fd_in;
|
||||
int fd_out;
|
||||
bool background;
|
||||
|
|
|
@ -21,6 +21,8 @@ BAN::ErrorOr<BAN::Vector<Token>> tokenize_string(BAN::StringView string)
|
|||
case ')': return Token::Type::CloseParen;
|
||||
case '$': return Token::Type::Dollar;
|
||||
case '"': return Token::Type::DoubleQuote;
|
||||
case '>': return Token::Type::GreaterThan;
|
||||
case '<': return Token::Type::LessThan;
|
||||
case '{': return Token::Type::OpenCurly;
|
||||
case '(': return Token::Type::OpenParen;
|
||||
case '|': return Token::Type::Pipe;
|
||||
|
|
|
@ -27,6 +27,12 @@ void Token::debug_dump() const
|
|||
case Type::DoubleQuote:
|
||||
dprintln("Token <DoubleQuote>");
|
||||
break;
|
||||
case Type::GreaterThan:
|
||||
dprintln("Token <GreaterThan>");
|
||||
break;
|
||||
case Type::LessThan:
|
||||
dprintln("Token <LessThan>");
|
||||
break;
|
||||
case Type::OpenCurly:
|
||||
dprintln("Token <OpenCurly>");
|
||||
break;
|
||||
|
|
|
@ -19,6 +19,8 @@ public:
|
|||
CloseParen,
|
||||
Dollar,
|
||||
DoubleQuote,
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
OpenCurly,
|
||||
OpenParen,
|
||||
Pipe,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <BAN/ScopeGuard.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static constexpr bool can_parse_argument_from_token_type(Token::Type token_type)
|
||||
{
|
||||
|
@ -18,6 +19,8 @@ static constexpr bool can_parse_argument_from_token_type(Token::Type token_type)
|
|||
case Token::Type::Ampersand:
|
||||
case Token::Type::CloseCurly:
|
||||
case Token::Type::CloseParen:
|
||||
case Token::Type::GreaterThan:
|
||||
case Token::Type::LessThan:
|
||||
case Token::Type::OpenCurly:
|
||||
case Token::Type::OpenParen:
|
||||
case Token::Type::Pipe:
|
||||
|
@ -43,6 +46,8 @@ static constexpr char token_type_to_single_character(Token::Type type)
|
|||
case Token::Type::CloseParen: return ')';
|
||||
case Token::Type::Dollar: return '$';
|
||||
case Token::Type::DoubleQuote: return '"';
|
||||
case Token::Type::GreaterThan: return '>';
|
||||
case Token::Type::LessThan: return '<';
|
||||
case Token::Type::OpenCurly: return '{';
|
||||
case Token::Type::OpenParen: return '(';
|
||||
case Token::Type::Pipe: return '|';
|
||||
|
@ -74,6 +79,10 @@ static constexpr BAN::Error unexpected_token_error(Token::Type type)
|
|||
return BAN::Error::from_literal("unexpected token $");
|
||||
case Token::Type::DoubleQuote:
|
||||
return BAN::Error::from_literal("unexpected token \"");
|
||||
case Token::Type::GreaterThan:
|
||||
return BAN::Error::from_literal("unexpected token >");
|
||||
case Token::Type::LessThan:
|
||||
return BAN::Error::from_literal("unexpected token <");
|
||||
case Token::Type::OpenCurly:
|
||||
return BAN::Error::from_literal("unexpected token {");
|
||||
case Token::Type::Pipe:
|
||||
|
@ -185,6 +194,8 @@ BAN::ErrorOr<CommandArgument::ArgumentPart> TokenParser::parse_backslash(bool is
|
|||
case Token::Type::CloseParen:
|
||||
case Token::Type::Dollar:
|
||||
case Token::Type::DoubleQuote:
|
||||
case Token::Type::GreaterThan:
|
||||
case Token::Type::LessThan:
|
||||
case Token::Type::OpenCurly:
|
||||
case Token::Type::OpenParen:
|
||||
case Token::Type::Pipe:
|
||||
|
@ -277,6 +288,8 @@ BAN::ErrorOr<CommandArgument::ArgumentPart> TokenParser::parse_dollar()
|
|||
case Token::Type::CloseCurly:
|
||||
case Token::Type::CloseParen:
|
||||
case Token::Type::DoubleQuote:
|
||||
case Token::Type::GreaterThan:
|
||||
case Token::Type::LessThan:
|
||||
case Token::Type::Pipe:
|
||||
case Token::Type::Semicolon:
|
||||
case Token::Type::SingleQuote:
|
||||
|
@ -363,6 +376,8 @@ BAN::ErrorOr<CommandArgument::ArgumentPart> TokenParser::parse_single_quote()
|
|||
case Token::Type::CloseParen:
|
||||
case Token::Type::Dollar:
|
||||
case Token::Type::DoubleQuote:
|
||||
case Token::Type::GreaterThan:
|
||||
case Token::Type::LessThan:
|
||||
case Token::Type::OpenCurly:
|
||||
case Token::Type::OpenParen:
|
||||
case Token::Type::Pipe:
|
||||
|
@ -403,6 +418,8 @@ BAN::ErrorOr<CommandArgument> TokenParser::parse_argument()
|
|||
case Token::Type::Ampersand:
|
||||
case Token::Type::CloseCurly:
|
||||
case Token::Type::CloseParen:
|
||||
case Token::Type::GreaterThan:
|
||||
case Token::Type::LessThan:
|
||||
case Token::Type::OpenCurly:
|
||||
case Token::Type::OpenParen:
|
||||
case Token::Type::Pipe:
|
||||
|
@ -466,6 +483,56 @@ BAN::ErrorOr<CommandArgument> TokenParser::parse_argument()
|
|||
return result;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<SingleCommand::Redirection> TokenParser::parse_redirection()
|
||||
{
|
||||
int source_fd = -1;
|
||||
|
||||
if (peek_token().type() == Token::Type::String)
|
||||
{
|
||||
const auto string = read_token().string();
|
||||
|
||||
source_fd = 0;
|
||||
for (char ch : string)
|
||||
source_fd = (source_fd * 10) + (ch - '0');
|
||||
}
|
||||
|
||||
const auto token_type = peek_token().type();
|
||||
consume_token();
|
||||
|
||||
switch (token_type)
|
||||
{
|
||||
case Token::Type::GreaterThan:
|
||||
if (source_fd == -1)
|
||||
source_fd = STDOUT_FILENO;
|
||||
break;
|
||||
case Token::Type::LessThan:
|
||||
if (source_fd == -1)
|
||||
source_fd = STDIN_FILENO;
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
const bool append = (peek_token().type() == token_type);
|
||||
if (append)
|
||||
consume_token();
|
||||
|
||||
const bool duplicate = (peek_token().type() == Token::Type::Ampersand);
|
||||
if (duplicate)
|
||||
consume_token();
|
||||
|
||||
while (peek_token().type() == Token::Type::Whitespace)
|
||||
consume_token();
|
||||
|
||||
return SingleCommand::Redirection {
|
||||
.destination = TRY(parse_argument()),
|
||||
.source_fd = source_fd,
|
||||
.append = append,
|
||||
.duplicate = duplicate,
|
||||
.input = (token_type == Token::Type::LessThan),
|
||||
};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<SingleCommand> TokenParser::parse_single_command()
|
||||
{
|
||||
SingleCommand result;
|
||||
|
@ -527,6 +594,8 @@ BAN::ErrorOr<SingleCommand> TokenParser::parse_single_command()
|
|||
case Token::Type::CloseCurly:
|
||||
case Token::Type::Dollar:
|
||||
case Token::Type::DoubleQuote:
|
||||
case Token::Type::GreaterThan:
|
||||
case Token::Type::LessThan:
|
||||
case Token::Type::OpenCurly:
|
||||
case Token::Type::OpenParen:
|
||||
case Token::Type::SingleQuote:
|
||||
|
@ -562,18 +631,56 @@ BAN::ErrorOr<SingleCommand> TokenParser::parse_single_command()
|
|||
consume_token();
|
||||
}
|
||||
|
||||
const auto can_parse_redirection =
|
||||
[this]() -> bool
|
||||
{
|
||||
const auto& token = peek_token();
|
||||
|
||||
if (token.type() == Token::Type::GreaterThan)
|
||||
return true;
|
||||
if (token.type() == Token::Type::LessThan)
|
||||
return true;
|
||||
if (token.type() != Token::Type::String)
|
||||
return false;
|
||||
if (token.string().empty())
|
||||
return false;
|
||||
|
||||
bool is_number = true;
|
||||
for (size_t i = 0; i < token.string().size() && is_number; i++)
|
||||
is_number = isdigit(token.string()[i]);
|
||||
if (!is_number)
|
||||
return false;
|
||||
|
||||
auto temp = read_token();
|
||||
const bool is_redir =
|
||||
(peek_token().type() == Token::Type::GreaterThan) ||
|
||||
(peek_token().type() == Token::Type::LessThan);
|
||||
MUST(unget_token(BAN::move(temp)));
|
||||
if (!is_redir)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
while (peek_token().type() != Token::Type::EOF_)
|
||||
{
|
||||
while (peek_token().type() == Token::Type::Whitespace)
|
||||
consume_token();
|
||||
|
||||
auto argument = TRY(parse_argument());
|
||||
TRY(result.arguments.push_back(BAN::move(argument)));
|
||||
|
||||
while (peek_token().type() == Token::Type::Whitespace)
|
||||
consume_token();
|
||||
if (!can_parse_argument_from_token_type(peek_token().type()))
|
||||
if (can_parse_redirection())
|
||||
{
|
||||
auto redirection = TRY(parse_redirection());
|
||||
TRY(result.redirections.push_back(BAN::move(redirection)));
|
||||
}
|
||||
else if (can_parse_argument_from_token_type(peek_token().type()))
|
||||
{
|
||||
auto argument = TRY(parse_argument());
|
||||
TRY(result.arguments.push_back(BAN::move(argument)));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -44,6 +44,8 @@ private:
|
|||
BAN::ErrorOr<CommandArgument::ArgumentPart> parse_single_quote();
|
||||
BAN::ErrorOr<CommandArgument> parse_argument();
|
||||
|
||||
BAN::ErrorOr<SingleCommand::Redirection> parse_redirection();
|
||||
|
||||
BAN::ErrorOr<SingleCommand> parse_single_command();
|
||||
BAN::ErrorOr<PipedCommand> parse_piped_command();
|
||||
BAN::ErrorOr<CommandTree> parse_command_tree();
|
||||
|
|
Loading…
Reference in New Issue