Pipes '|' support
- cd without arguments moves to $HOME - updated README
This commit is contained in:
parent
afbcaac1ae
commit
86ecaef72d
2 changed files with 239 additions and 56 deletions
|
|
@ -2,11 +2,13 @@
|
||||||
#+options: toc:nil
|
#+options: toc:nil
|
||||||
Simple shell for Unix-like systems written in C, that has a funny name (for germans).
|
Simple shell for Unix-like systems written in C, that has a funny name (for germans).
|
||||||
* Features
|
* Features
|
||||||
- Can run one command at a given time (no | , &&, ||, functions, etc)
|
- Can run commands
|
||||||
- ~cd~ builtin command
|
- You can pipe stdout of one command to stdin of another (ex. ~cat main.c | wc --lines~)
|
||||||
|
- ~cd~ builtin command (~cd~ without arguments moves you to ~$HOME~)
|
||||||
- ~exit~ builtin command
|
- ~exit~ builtin command
|
||||||
- ~CTRL+C~ stops running command
|
- ~CTRL+C~ stops running command
|
||||||
- Custom ~readline~ function
|
- Custom ~readline~ function
|
||||||
|
- Run scripts with ~arsh /path/to/script~ or by putting ~#!/usr/bin/env arsh~ at the first line of a script and making it executable.
|
||||||
* Build
|
* Build
|
||||||
To build it, you don't need any external dependencies. Example with ~gcc~:
|
To build it, you don't need any external dependencies. Example with ~gcc~:
|
||||||
#+begin_src shell
|
#+begin_src shell
|
||||||
|
|
|
||||||
289
main.c
289
main.c
|
|
@ -7,8 +7,11 @@
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
|
|
||||||
|
constexpr size_t max_lenght = 1024;
|
||||||
|
const char* token_separators = " \t\n\r";
|
||||||
|
|
||||||
static sigjmp_buf restart_jmp_buf;
|
static sigjmp_buf restart_jmp_buf;
|
||||||
static const int restart_value = 69;
|
static constexpr int restart_value = 69;
|
||||||
static volatile sig_atomic_t jump_active = 0;
|
static volatile sig_atomic_t jump_active = 0;
|
||||||
|
|
||||||
void sigint_handler(int signo)
|
void sigint_handler(int signo)
|
||||||
|
|
@ -19,6 +22,19 @@ void sigint_handler(int signo)
|
||||||
}
|
}
|
||||||
siglongjmp(restart_jmp_buf, restart_value);
|
siglongjmp(restart_jmp_buf, restart_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*Checked calloc: if allocation fails -> exists*/
|
||||||
|
void* ccalloc(size_t size, size_t n)
|
||||||
|
{
|
||||||
|
void* ret = calloc(size, n);
|
||||||
|
if(!ret)
|
||||||
|
{
|
||||||
|
perror("calloc failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr size_t max_line = 1024;
|
static constexpr size_t max_line = 1024;
|
||||||
static char string_buffer[max_line];
|
static char string_buffer[max_line];
|
||||||
char* readline(char* print, FILE* stream)
|
char* readline(char* print, FILE* stream)
|
||||||
|
|
@ -51,27 +67,186 @@ char* readline(char* print, FILE* stream)
|
||||||
return string_buffer;
|
return string_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
char** split_input(char* input)
|
/**
|
||||||
|
* Struct to represent a command and its arguments.
|
||||||
|
*/
|
||||||
|
struct command
|
||||||
{
|
{
|
||||||
const size_t alloc_size = ((strlen(input) + 1) / 2) + 1;
|
/**
|
||||||
char** command = malloc(alloc_size * sizeof(char*));
|
* IO redirections; redirect[i] should be used as fd i in the child.
|
||||||
if (command == NULL)
|
* A value of -1 indicates no redirect.
|
||||||
|
*/
|
||||||
|
int redirect[2];
|
||||||
|
/** The arguments; must be NULL-terminated. */
|
||||||
|
char* argv[];
|
||||||
|
};
|
||||||
|
/** The name of the executable. */
|
||||||
|
char* command_name(struct command* cmd)
|
||||||
|
{
|
||||||
|
return cmd->argv[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Struct to represent a pipeline of commands. The intention is that cmd[i]'s
|
||||||
|
* output goes to cmd[i+1]'s input.
|
||||||
|
*/
|
||||||
|
struct pipeline
|
||||||
|
{
|
||||||
|
/** The total number of commands. */
|
||||||
|
size_t n_cmds;
|
||||||
|
struct command* cmds[];
|
||||||
|
};
|
||||||
|
|
||||||
|
void* parser_memory[max_lenght] = {0};
|
||||||
|
size_t parser_memory_index = 0;
|
||||||
|
void parser_allocated(void* ptr)
|
||||||
|
{
|
||||||
|
parser_memory[parser_memory_index] = ptr;
|
||||||
|
if(parser_memory_index == max_lenght)
|
||||||
{
|
{
|
||||||
perror("malloc failed");
|
fprintf(stderr, "Not enough memory for parser: Memory will leak!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
const char* separator = " ";
|
parser_memory_index++;
|
||||||
char* parsed;
|
}
|
||||||
uint32_t index = 0;
|
|
||||||
parsed = strtok(input, separator);
|
void parser_free()
|
||||||
while (parsed != NULL)
|
{
|
||||||
|
for(size_t i = 0; i < parser_memory_index; i++)
|
||||||
{
|
{
|
||||||
command[index] = parsed;
|
free(parser_memory[i]);
|
||||||
index++;
|
}
|
||||||
parsed = strtok(NULL, separator);
|
memset(parser_memory, 0, max_lenght);
|
||||||
|
parser_memory_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For internal use. Returns the next non-empty token according in *line,
|
||||||
|
* splitting on characters in TOKEN_SEP. Returns NULL if no token remains.
|
||||||
|
* Updates line to point to remainder after removal of the token. Modifies the
|
||||||
|
* string *line.
|
||||||
|
*/
|
||||||
|
char* next_non_empty(char **line)
|
||||||
|
{
|
||||||
|
char *tok;
|
||||||
|
|
||||||
|
/* Consume empty tokens. */
|
||||||
|
while ((tok = strsep(line, token_separators)) && !*tok);
|
||||||
|
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses str into a freshly allocated command struct and returns a pointer to it.
|
||||||
|
* The redirects in the returned command will be set to -1, ie no redirect.
|
||||||
|
*/
|
||||||
|
struct command* parse_command(char* str)
|
||||||
|
{
|
||||||
|
/* Copy the input line in case the caller wants it later. */
|
||||||
|
char* copy = strndup(str, max_lenght);
|
||||||
|
parser_allocated(copy);
|
||||||
|
char* token;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Being lazy and allocating way too much memory for the args array.
|
||||||
|
* Using calloc to ensure it's zero-initialised, which is important because
|
||||||
|
* execvp expects a NULL-terminated array of arguments.
|
||||||
|
*/
|
||||||
|
struct command* ret = ccalloc(sizeof(struct command) + strlen(copy) * sizeof(char*), 1);
|
||||||
|
parser_allocated(ret);
|
||||||
|
|
||||||
|
while ((token = next_non_empty(©)))
|
||||||
|
{
|
||||||
|
ret->argv[i++] = token;
|
||||||
|
}
|
||||||
|
ret->redirect[0] = ret->redirect[1] = -1;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses str into a freshly allocated pipeline_struct and returns a pointer to
|
||||||
|
* it. All commands in cmds will also be freshy allocated, and have their
|
||||||
|
* redirects set to -1, ie no redirect.
|
||||||
|
*/
|
||||||
|
struct pipeline* parse_pipeline(char *str)
|
||||||
|
{
|
||||||
|
char* copy = strndup(str, max_lenght);
|
||||||
|
parser_allocated(copy);
|
||||||
|
char* cmd_str;
|
||||||
|
int n_cmds = 0;
|
||||||
|
int i = 0;
|
||||||
|
struct pipeline* ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Count pipe characters that appear in pipeline to know how much space to
|
||||||
|
* allocate for the cmds array.
|
||||||
|
*/
|
||||||
|
for (char* cur = str; *cur; cur++)
|
||||||
|
{
|
||||||
|
if (*cur == '|') ++n_cmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
++n_cmds; /* There is one more command than there are pipe characters. */
|
||||||
|
|
||||||
|
ret = ccalloc(sizeof(struct pipeline) + n_cmds * sizeof(struct command*), 1);
|
||||||
|
parser_allocated(ret);
|
||||||
|
ret->n_cmds = n_cmds;
|
||||||
|
|
||||||
|
while((cmd_str = strsep(&str, "|")))
|
||||||
|
{
|
||||||
|
ret->cmds[i++] = parse_command(cmd_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_ALL_the_pipes(int n_pipes, int (*pipes)[2])
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n_pipes; ++i)
|
||||||
|
{
|
||||||
|
close(pipes[i][0]);
|
||||||
|
close(pipes[i][1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int exec_with_redir(struct command* command, int n_pipes, int (*pipes)[2])
|
||||||
|
{
|
||||||
|
int fd = -1;
|
||||||
|
if ((fd = command->redirect[0]) != -1)
|
||||||
|
{
|
||||||
|
dup2(fd, STDIN_FILENO);
|
||||||
|
}
|
||||||
|
if ((fd = command->redirect[1]) != -1)
|
||||||
|
{
|
||||||
|
dup2(fd, STDOUT_FILENO);
|
||||||
|
}
|
||||||
|
close_ALL_the_pipes(n_pipes, pipes);
|
||||||
|
return execvp(command_name(command), command->argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sigaction s_old;
|
||||||
|
|
||||||
|
pid_t run_with_redir(struct command* command, int n_pipes, int (*pipes)[2])
|
||||||
|
{
|
||||||
|
pid_t child_pid = fork();
|
||||||
|
|
||||||
|
if(child_pid < 0)
|
||||||
|
{
|
||||||
|
perror("Fork failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (child_pid)
|
||||||
|
{ /* We are the parent. */
|
||||||
|
return child_pid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // We are the child. */
|
||||||
|
sigaction(SIGINT, &s_old, NULL);
|
||||||
|
exec_with_redir(command, n_pipes, pipes);
|
||||||
|
perror(command_name(command));
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
command[index] = NULL;
|
|
||||||
return command;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int cd(char* path)
|
int cd(char* path)
|
||||||
|
|
@ -97,7 +272,6 @@ int main(int argc, const char* argv[])
|
||||||
s.sa_handler = sigint_handler;
|
s.sa_handler = sigint_handler;
|
||||||
sigemptyset(&s.sa_mask);
|
sigemptyset(&s.sa_mask);
|
||||||
s.sa_flags = SA_RESTART;
|
s.sa_flags = SA_RESTART;
|
||||||
struct sigaction s_old;
|
|
||||||
sigaction(SIGINT, &s, &s_old);
|
sigaction(SIGINT, &s, &s_old);
|
||||||
|
|
||||||
int stat_loc;
|
int stat_loc;
|
||||||
|
|
@ -121,54 +295,61 @@ int main(int argc, const char* argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
// After that line you need cleanup
|
// After that line you need cleanup
|
||||||
char** command = split_input(input);
|
struct pipeline* pipeline = parse_pipeline(input);
|
||||||
if(command[0] == NULL)
|
int n_pipes = pipeline->n_cmds - 1;
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
if(strlen(command[0]) >= 1 && command[0][0] == '#')
|
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(strcmp(command[0], "cd") == 0)
|
/* pipes[i] redirects from pipeline->cmds[i] to pipeline->cmds[i+1]. */
|
||||||
|
int (*pipes)[2] = ccalloc(sizeof(int[2]), n_pipes);
|
||||||
|
parser_allocated(pipes);
|
||||||
|
pid_t* pids = ccalloc(sizeof(pid_t), pipeline->n_cmds);
|
||||||
|
parser_allocated(pids);
|
||||||
|
for (int i = 1; i < pipeline->n_cmds; ++i)
|
||||||
{
|
{
|
||||||
if (cd(command[1]) < 0)
|
pipe(pipes[i-1]);
|
||||||
|
pipeline->cmds[i]->redirect[STDIN_FILENO] = pipes[i-1][0];
|
||||||
|
pipeline->cmds[i-1]->redirect[STDOUT_FILENO] = pipes[i-1][1];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < pipeline->n_cmds; ++i)
|
||||||
|
{
|
||||||
|
struct command* cmd = pipeline->cmds[i];
|
||||||
|
char* cmd_name = command_name(cmd);
|
||||||
|
if(!cmd_name)
|
||||||
{
|
{
|
||||||
perror(command[1]);
|
continue;
|
||||||
}
|
}
|
||||||
|
if(strlen(cmd_name) >= 1 && cmd_name[0] == '#')
|
||||||
|
{
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strcmp(cmd_name, "cd") == 0)
|
||||||
|
{
|
||||||
|
char* path = cmd->argv[1] ? cmd->argv[1] : getenv("HOME");
|
||||||
|
if (cd(path) < 0)
|
||||||
|
{
|
||||||
|
char err_buf[max_lenght] = {0};
|
||||||
|
sprintf(err_buf, "cd: %s", path);
|
||||||
|
perror(err_buf);
|
||||||
|
}
|
||||||
|
|
||||||
goto cleanup; //skip fork()
|
goto cleanup; //skip fork()
|
||||||
}
|
|
||||||
if(strcmp(command[0], "exit") == 0)
|
|
||||||
{
|
|
||||||
int32_t status_code = command[1] != NULL ? atoi(command[1]) : 0;
|
|
||||||
exit(status_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t child_pid = fork();
|
|
||||||
if (child_pid < 0)
|
|
||||||
{
|
|
||||||
perror("Fork failed");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (child_pid == 0)
|
|
||||||
{
|
|
||||||
sigaction(SIGINT, &s_old, NULL);
|
|
||||||
//never returns if call is successful
|
|
||||||
if(execvp(command[0], command) < 0)
|
|
||||||
{
|
|
||||||
perror(command[0]);
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
if(strcmp(cmd_name, "exit") == 0)
|
||||||
|
{
|
||||||
|
int32_t status_code = cmd->argv[1] != NULL ? atoi(cmd->argv[1]) : 0;
|
||||||
|
exit(status_code);
|
||||||
|
}
|
||||||
|
pids[i] = run_with_redir(cmd, n_pipes, pipes);
|
||||||
}
|
}
|
||||||
else
|
close_ALL_the_pipes(n_pipes, pipes);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < pipeline->n_cmds; i++)
|
||||||
{
|
{
|
||||||
waitpid(child_pid, &stat_loc, WUNTRACED);
|
waitpid(pids[i], &stat_loc, WUNTRACED);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
|
||||||
free(command);
|
parser_free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue