#include "cmd_exec.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include "builtins.h"
#include "sish.h"
#include "utils.h"
/* Taught in CS 392 to label for clairity's sake */
#define READ_FD 0
#define WRITE_FD 1
int
parse_commandline(char** token_args, CMD_Pipeline* p)
{
if (token_args == NULL || token_args[0] == NULL)
{
return -1;
}
int toffset = 0;
while (token_args[toffset] != NULL)
{
toffset++;
}
/* consume & if present */
if (toffset > 0 && strcmp(token_args[toffset - 1], "&") == 0)
{
p->run_in_background = true;
token_args[toffset - 1] = NULL;
toffset--;
/* if the user only provided a & well uh*/
if (toffset == 0)
{
/* message stolen from linux shell */
fprintf(stderr, "%s: syntax error near unexpected token '&'\n",
GET_PROGNAME_ERR);
return -1;
}
}
/* scan redirections */
int input_file_alr_given = 0;
int output_file_pos = -1;
int output_file_mode = M_TRUNC;
for (int i = 0; i < toffset; i++)
{
if (strcmp(token_args[i], "<") == 0)
{
input_file_alr_given++;
if (input_file_alr_given > 1)
{
fprintf(stderr,
"%s: multiple input redirections not supported\n",
GET_PROGNAME_ERR);
return -1;
}
if (token_args[i + 1] == NULL)
{
/* no input file specified - also mostly what the default linux
* shell says */
fprintf(stderr,
"%s: syntax error near unexpected token 'newline' "
"(input file not found)\n",
GET_PROGNAME_ERR);
return -1;
}
p->infile = token_args[i + 1];
}
if (strcmp(token_args[i], ">") == 0 || strcmp(token_args[i], ">>") == 0)
{
if (token_args[i + 1] == NULL)
{
fprintf(stderr,
"%s: syntax error near unexpected token 'newline' "
"(output file not found)\n",
GET_PROGNAME_ERR);
return -1;
}
output_file_pos = i;
if (token_args[i][1] == '>')
{
output_file_mode = M_APPEND;
}
else
{
output_file_mode = M_TRUNC;
}
}
}
if (output_file_pos != -1)
{
p->outfile = token_args[output_file_pos + 1];
p->append = output_file_mode;
}
int cmd = 0;
p->num_commands = 1;
for (int i = 0; i < toffset; i++)
{
if (strcmp(token_args[i], "<") == 0 ||
strcmp(token_args[i], ">") == 0 || strcmp(token_args[i], ">>") == 0)
{
i++;
continue;
}
if (strcmp(token_args[i], "|") == 0)
{
if (p->commands[cmd].cmd_and_args[0] == '\0')
{
/* also taken from linux shell */
fprintf(stderr, "%s: syntax error near unexpected token '|'\n",
GET_PROGNAME_ERR);
return -1;
}
cmd++;
if (cmd >= MAX_COMMANDS)
{
fprintf(stderr, "%s: too many commands in pipeline (max %d)\n",
GET_PROGNAME_ERR, MAX_COMMANDS);
return -1;
}
p->num_commands++;
continue;
}
if (p->commands[cmd].cmd_and_args[0] != '\0')
{
strncat(p->commands[cmd].cmd_and_args, " ",
sizeof(p->commands[cmd].cmd_and_args) - 1);
}
strncat(p->commands[cmd].cmd_and_args, token_args[i],
sizeof(p->commands[cmd].cmd_and_args) - 1);
}
if (p->commands[cmd].cmd_and_args[0] == '\0')
{
return -1;
}
/* apply redirection to commands */
if (p->infile != NULL)
{
p->commands[0].redir_stdin = p->infile;
}
if (p->outfile != NULL)
{
p->commands[p->num_commands - 1].redir_stdout = p->outfile;
p->commands[p->num_commands - 1].redir_out_mode =
p->append ? M_APPEND : M_TRUNC;
}
return 0;
}
void
get_cmd_name_from_cmd_and_args(const char* cmd_and_args, char* cmd_name,
int cmd_name_bufsiz)
{
int len = 0;
while (*(cmd_and_args + len) != '\0' &&
!isspace((unsigned char)(*(cmd_and_args + len))))
{
len++;
if (len >= cmd_name_bufsiz)
{
len = cmd_name_bufsiz - 1;
break;
}
}
memcpy(cmd_name, cmd_and_args, len);
cmd_name[len] = '\0';
return;
}
bool
should_run_builtin_in_parent(const CMD_Pipeline* p)
{
if (p->num_commands != 1)
{
return false;
}
const Command* c = &(p->commands[0]);
if (c->redir_stdin != NULL || c->redir_stdout != NULL)
{
return false;
}
char cmd_name[BUFSIZ] = {0};
get_cmd_name_from_cmd_and_args(c->cmd_and_args, cmd_name, sizeof(cmd_name));
return is_builtin(cmd_name);
}
/* for sole use with execv */
void
split_cmd_into_argv(const char* cmd, char** argv)
{
char* tmp = strdup(cmd);
int i = 0;
char* tok = strtok(tmp, " ");
while (tok != NULL && i < MAX_ARGS - 1)
{
argv[i] = tok;
i++;
tok = strtok(NULL, " ");
}
argv[i] = NULL;
/* tmp intentionally not freed as we would need to free after exec, there's
* a comment after the exec call with more info*/
}
void
try_run_builtin(const char* cmd_name, const char* cmd_and_args)
{
char* args_buf = strdup(cmd_and_args);
if (args_buf == NULL)
{
fprintf(stderr, "%s: strdup failed: %s\n", GET_PROGNAME_ERR,
strerror(errno));
return;
}
char* token_args[MAX_ARGS] = {0};
int argc = split_builtin_args(args_buf, token_args);
if (strcmp(cmd_name, "cd") == 0)
{
last_fg_exit_status = sish_cd(token_args, argc);
goto ret;
}
if (strcmp(cmd_name, "echo") == 0)
{
last_fg_exit_status = sish_echo(token_args, argc);
goto ret;
}
if (strcmp(cmd_name, "exit") == 0)
{
free(args_buf);
sish_exit(EXIT_SUCCESS);
/* unreachable */
}
ret:
free(args_buf);
return;
}
void
trace(CMD_Pipeline* p)
{
for (int i = 0; i < p->num_commands; i++)
{
fprintf(stderr, "+ %s\n", p->commands[i].cmd_and_args);
}
return;
}
void
run_commands(char** token_args)
{
CMD_Pipeline p = {0};
for (int i = 0; i < MAX_COMMANDS; i++)
{
memset(&p.commands[i], 0, sizeof(p.commands[i]));
}
int retval = parse_commandline(token_args, &p);
if (flag_tracing_enabled)
{
trace(&p);
}
if (retval != 0)
{
/* error propegation handled in parse_commandline */
return;
}
int num_commands = p.num_commands;
if (num_commands <= 0)
{
fprintf(stderr,
"%s: command pipeline could not run: Invalid number of "
"commands to process: %d\n",
GET_PROGNAME_ERR, num_commands);
return;
}
/* needed for io redirection to happen (nicely) with builtins; see below */
if (should_run_builtin_in_parent(&p))
{
char cmd_name[BUFSIZ] = {0};
get_cmd_name_from_cmd_and_args(p.commands[0].cmd_and_args, cmd_name,
sizeof(cmd_name));
try_run_builtin(cmd_name, p.commands[0].cmd_and_args);
return;
}
/* a | b | c; n - 1 pipes needed at most */
int pipes[num_commands - 1][2];
for (int i = 0; i < num_commands - 1; i++)
{
if (pipe(pipes[i]) < 0)
{
fprintf(stderr, "%s: pipe failed: %s\n", GET_PROGNAME_ERR,
strerror(errno));
exit(EXIT_FAILURE);
}
}
for (int i = 0; i < num_commands; i++)
{
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "%s: fork failed: %s\n", GET_PROGNAME_ERR,
strerror(errno));
exit(EXIT_FAILURE);
}
/* if parent and background command set last pid to the most recent
* background task. Unsure whether this should only apply to the first
* command, very last, or what -- but for now I am interpreting it as
* literally "the most recent" */
if (/*i == 0 && */ pid > 0 && p.run_in_background)
{
last_pid_bg = pid;
printf("[%d]\n", pid);
}
/* CHILD */
if (pid == 0)
{
if (i > 0)
{
if (dup2(pipes[i - 1][READ_FD], STDIN_FILENO) == -1)
{
fprintf(stderr, "%s: dup2 failed: %s\n", GET_PROGNAME_ERR,
strerror(errno));
exit(EXIT_FAILURE);
}
}
if (i < num_commands - 1)
{
if (dup2(pipes[i][WRITE_FD], STDOUT_FILENO) == -1)
{
fprintf(stderr, "%s: dup2 failed: %s\n", GET_PROGNAME_ERR,
strerror(errno));
exit(EXIT_FAILURE);
}
}
/* close now-useless pipes although it's kind of unnecessary */
for (int j = 0; j < num_commands - 1; j++)
{
(void)close(pipes[j][READ_FD]);
(void)close(pipes[j][WRITE_FD]);
}
/* input redirection */
if (p.commands[i].redir_stdin)
{
int fd = open(p.commands[i].redir_stdin, O_RDONLY);
if (fd < 0)
{
fprintf(stderr, "%s: unable to open %s: %s\n",
GET_PROGNAME_ERR, p.commands[i].redir_stdin,
strerror(errno));
exit(EXIT_FAILURE);
}
(void)dup2(fd, STDIN_FILENO);
(void)close(fd);
}
/* output redirection */
if (p.commands[i].redir_stdout)
{
int flags = O_WRONLY | O_CREAT;
if (p.commands[i].redir_out_mode == M_APPEND)
{
flags |= O_APPEND;
}
else
{
flags |= O_TRUNC;
}
int fd = open(p.commands[i].redir_stdout, flags, 0644);
if (fd < 0)
{
fprintf(stderr, "%s: unable to open %s: %s\n",
GET_PROGNAME_ERR, p.commands[i].redir_stdout,
strerror(errno));
exit(EXIT_FAILURE);
}
(void)dup2(fd, STDOUT_FILENO);
(void)close(fd);
}
/* run builtin or external */
char cmd_name[BUFSIZ] = {0};
get_cmd_name_from_cmd_and_args(p.commands[i].cmd_and_args, cmd_name,
sizeof(cmd_name));
if (is_builtin(cmd_name))
{
/* builtin modifies last_fg_exit_status */
try_run_builtin(cmd_name, p.commands[i].cmd_and_args);
exit(last_fg_exit_status);
}
/* external command */
char* argv[MAX_ARGS] = {0};
split_cmd_into_argv(p.commands[i].cmd_and_args, argv);
execvp(argv[0], argv);
/* normally unreachable */
/* at this point the malloced memory from split_cmd_into_argv is
* "leaked" but we exit immediately after anyway */
fprintf(stderr, "%s: \"%s\": command not found\n", GET_PROGNAME_ERR,
argv[0]);
exit(127); /* per the spec */
}
}
/* clean up in sish/parent */
for (int i = 0; i < num_commands - 1; i++)
{
(void)close(pipes[i][READ_FD]);
(void)close(pipes[i][WRITE_FD]);
}
if (!p.run_in_background)
{
for (int i = 0; i < num_commands; i++)
{
/* if we needed to keep track of return values/status of multiple
* sub processes and such (possibly simultaneously), an array of the
* result of fork + waitpid would work better but that's not
* required here, so wait waiting on as many child processes as were
* spawned works fine, overwriting the last exit status aside */
(void)wait(&last_fg_exit_status);
}
}
return;
}