Added shell history and implemented builtin commands.

parent eacd472b
CC=gcc CC=clang # gcc
RM=rm -Rfv RM=rm -Rfv
LFLAGS= -lm LFLAGS= -lm
...@@ -21,6 +21,7 @@ shell: input.yacc.o input.lex.o main.o ...@@ -21,6 +21,7 @@ shell: input.yacc.o input.lex.o main.o
test: shell test: shell
echo -n 'exit' | ./shell echo -n 'exit' | ./shell
echo -n 'cd ../' | ./shell echo -n 'cd ../' | ./shell
echo -n 'help' | ./shell
echo -n 'cat input.l' | ./shell echo -n 'cat input.l' | ./shell
echo -n 'cat input.l | grep "id"' | ./shell echo -n 'cat input.l | grep "id"' | ./shell
echo -n '!!' | ./shell echo -n '!!' | ./shell
...@@ -33,7 +34,7 @@ input.yacc.h: input.yacc.c ...@@ -33,7 +34,7 @@ input.yacc.h: input.yacc.c
input.lex.c: input.l input.lex.c: input.l
`which flex35 2>/dev/null || which flex 2>/dev/null` \ `which flex35 2>/dev/null || which flex 2>/dev/null` \
-C --header-file=input.lex.h -o $@ -d $< --interactive --header-file=input.lex.h -o $@ -d $<
input.lex.h: input.lex.c input.lex.h: input.lex.c
......
/* /*
* input.l * Shell input lexer (flex format).
* *
* Shell input lexer * Sander van Veen (6167969) / Taddeus Kroes (6054129).
* <sandervv@gmail.com> / <taddeuskroes@hotmail.com>
*/ */
%{ %{
...@@ -13,18 +14,18 @@ ...@@ -13,18 +14,18 @@
#include "input.l.h" #include "input.l.h"
#include "input.yacc.h" #include "input.yacc.h"
// Column / line counters (used in error reporting)
int line_no = 1, column_no = 0; int line_no = 1, column_no = 0;
extern int current_line(void){ return line_no; } extern int current_line(void){ return line_no; }
extern int current_column(void){ return column_no; } extern int current_column(void){ return column_no; }
extern void reset_line(void){ line_no = 1; } extern void reset_line(void){ line_no = 1; }
extern void reset_column(void){ column_no = 0; } extern void reset_column(void){ column_no = 0; }
%} %}
%option nounput
%option noyywrap %option noyywrap
%option nounput
%x id %x id
%x num %x num
...@@ -57,6 +58,12 @@ exit { ...@@ -57,6 +58,12 @@ exit {
return EXIT; return EXIT;
} }
/* Help shell builtin */
help {
column_no += 4;
return HELP;
}
/* Change directory builtin */ /* Change directory builtin */
cd { cd {
column_no += 2; column_no += 2;
...@@ -83,14 +90,14 @@ cd { ...@@ -83,14 +90,14 @@ cd {
} }
/* Path or path with filename: ./a/, ../, config/, /tmp/, /tmp/file */ /* Path or path with filename: ./a/, ../, config/, /tmp/, /tmp/file */
[^ ]*\/[^ ]* { \.*{id}?\/[^\t\n ]* {
column_no += yyleng; column_no += yyleng;
yylval.str = strdup(yytext); yylval.str = strdup(yytext);
return PATH; return PATH;
} }
/* Argument (filenames and such): hello.txt, .config */ /* Argument (filenames and such): hello.txt, .config */
[\/{id}][^ ]+ { {id}[^\t\n ]+ {
column_no += yyleng; column_no += yyleng;
yylval.str = strdup(yytext); yylval.str = strdup(yytext);
return ARGUMENT; return ARGUMENT;
...@@ -98,22 +105,18 @@ cd { ...@@ -98,22 +105,18 @@ cd {
#[^\n]+ { /* ignore comments */ } #[^\n]+ { /* ignore comments */ }
/* End of file is a separator */
\0 {
line_no++;
reset_column();
return EOS;
}
/* White space */ /* White space */
\n { \n {
line_no++; line_no++;
reset_column(); reset_column();
shell_entry();
return EOL; return EOL;
} }
/* White space */ /* White space */
{wsp} { column_no++; } {wsp} {
column_no++;
}
. { . {
column_no++; column_no++;
......
/*
* Shell function declarations.
*
* Sander van Veen (6167969) / Taddeus Kroes (6054129).
* <sandervv@gmail.com> / <taddeuskroes@hotmail.com>
*/
#include "input.yacc.h"
/*
* Helper functions for line/column counter manipulation (which are used for the
* lexer's error reporting).
*/
extern int current_line(void); extern int current_line(void);
extern int current_column(void); extern int current_column(void);
extern void reset_line();
extern void reset_column();
extern int yyparse(); extern int yyparse();
extern void reset_line(); /*
extern void reset_column(); * Helper functions for shell output messages.
*/
void shell_debug(const char *msg, ...);
void shell_error(const char *msg);
void shell_entry();
/*
* Singly linked list of arguments.
*/
typedef struct shell_args {
char *arg;
struct shell_args *next;
} shell_args;
/*
* A shell command contains a function identifier and an argument list (list
* could be empty).
*/
typedef enum {FN_CD, FN_HELP, FN_EXIT, FN_EXEC} shell_func;
typedef struct shell_cmd {
shell_func func;
shell_args *args;
} shell_cmd;
/*
* Helper functions for creating / manipulating argument objects.
*/
void shell_push_args(const char *value);
void shell_append_args(const char *value);
shell_args *shell_pop_args();
void shell_free_args(shell_args *args);
/*
* Builtin shell commands.
*/
void shell_change_dir(const char *path);
void shell_exit();
void shell_help();
void shell_pipe();
void shell_repeat(int prev);
void shell_exec(shell_args *args);
/*
* Doubly linked list containing all executed commands.
*/
typedef struct shell_history_log {
shell_cmd *cmd;
struct shell_history_log *prev;
struct shell_history_log *next;
} shell_history_log;
/*
* Shell history manipulation functions.
*/
void shell_history_add(shell_cmd *cmd);
shell_cmd *shell_history_get(int prev);
void shell_history_clear();
void shell_history_append(shell_func type, shell_args *args);
void start_shell_entry();
void start_shell_pipe();
%{ %{
/* /*
* Shell input grammar (yacc/bison format) * Shell input grammar (yacc/bison format).
*
* Sander van Veen (6167969) / Taddeus Kroes (6054129).
* <sandervv@gmail.com> / <taddeuskroes@hotmail.com>
*/ */
#define YYDEBUG 1
#define YYERROR_VERBOSE 1
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
// #include "input.func.h"
#include "input.yacc.h" #include "input.yacc.h"
#include "input.lex.h" #include "input.lex.h"
#include "input.l.h" #include "input.l.h"
void yyerror(const char *str) { void yyerror(const char *str) {
fflush(stdout);
fflush(stderr);
fprintf(stderr, "error: parser reported \"%s\" (line %d, column %d)\n", str, fprintf(stderr, "error: parser reported \"%s\" (line %d, column %d)\n", str,
current_line(), current_column()); current_line(), current_column());
...@@ -23,9 +26,10 @@ void yyerror(const char *str) { ...@@ -23,9 +26,10 @@ void yyerror(const char *str) {
%} %}
%token COMMENT IDENTIFIER ARGUMENT STRING INTEGER %token COMMENT IDENTIFIER ARGUMENT STRING INTEGER
%token EOL EOS CD EXIT PATH %token EOL EOS CD EXIT PATH HELP
%type <str> PATH INTEGER %type <str> PATH INTEGER IDENTIFIER ARGUMENT STRING
%type <str> Argument
%union { %union {
char * str; char * str;
...@@ -35,29 +39,31 @@ void yyerror(const char *str) { ...@@ -35,29 +39,31 @@ void yyerror(const char *str) {
%% %%
Start: /* empty */ Start: Commands
| Commands
; ;
Commands: Command /*{ printf("cmd: %s\n", $1); }*/ Commands: Command
/* | Command EOS*/ | Commands EOL Command
| Commands EOL { start_shell_entry(); } Command | Commands '|' { shell_pipe(); } Command
| Commands '|' { start_shell_pipe(); } Command
; ;
Command: Builtin Command: /* Empty */
| IDENTIFIER | Builtin
| IDENTIFIER Arguments | IDENTIFIER { shell_push_args($1); }
{ shell_exec(shell_pop_args()); }
| IDENTIFIER { shell_push_args($1); }
Arguments { shell_exec(shell_pop_args()); }
; ;
Builtin: EXIT { puts("Exit shell"); } Builtin: EXIT { shell_exit(); }
| CD PATH { printf("CD %s\n", $2); } | HELP { shell_help(); }
| '!' '!' { puts("Repeat last"); } | CD Argument { shell_change_dir($2); }
| '!' INTEGER { printf("Repeat %d\n", atoi($2)); } | '!' '!' { shell_repeat(1); }
| '!' INTEGER { shell_repeat(atoi($2)); }
; ;
Arguments: Argument Arguments: Argument { shell_append_args($1); }
| Arguments ' ' Argument | Arguments Argument { shell_append_args($2); }
; ;
Argument: STRING Argument: STRING
......
/* /*
* Shell implementation using flex/bison. * Shell implementation using flex/bison.
*
* Sander van Veen (6167969) / Taddeus Kroes (6054129).
* <sandervv@gmail.com> / <taddeuskroes@hotmail.com>
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <math.h>
#include "input.yacc.h" #include "input.yacc.h"
#include "input.lex.h" #include "input.lex.h"
#include "input.l.h" #include "input.l.h"
#define PATH_LENGTH 1024
int is_interactive = 0; int is_interactive = 0;
int is_debug_mode = 1;
/*
* Display a non-fatal error message in stderr.
*/
void shell_error(const char *msg) {
fprintf(stderr, "error: %s\n", msg);
}
/*
* Return the length of an unsigned integer.
*/
inline int base10len(unsigned int n) {
int len = 0;
/* n < 10^16 */
if (n >= 100000000) { n /= 100000000; len += 8; }
/* n < 100000000 = 10^8 */
if (n >= 10000) { n /= 10000; len += 4; }
/* n < 10000 */
if (n >= 100) { n /= 100; len += 2; }
/* n < 100 */
if (n >= 10)
return len + 2;
else
return len + 1;
}
/*
* Display a debug message, when running in debug mode, in stderr.
*/
void shell_debug(const char *msg, ...) {
if( !is_debug_mode )
return;
// Count all '%' chars in message
int var_count = 0;
int buf_size = strlen(msg) + 1;
char *var_types = malloc(buf_size * sizeof(char));
for( int c = 0; c < buf_size; c++ )
if( msg[c] == '%' ) {
assert(c+1 < buf_size);
var_types[var_count] = msg[c+1];
var_count++;
}
// Calculate message buffer size.
va_list ap;
va_start(ap, msg);
int v;
for( int i = 0; i < var_count; i++ ) {
switch( var_types[i] ) {
case 's': buf_size += strlen(va_arg(ap, char *)); break;
case 'd':
v = va_arg(ap, int);
if( v == 0 )
buf_size++;
else if( v > 0 )
buf_size += base10len((unsigned int) v);
else
buf_size += base10len((unsigned int) (v*-1));
break;
default: perror("Unsupported variable type used in debug message.");
}
}
va_end(ap);
// Assemble the debug message.
va_start(ap, msg);
char *buf = malloc(buf_size * sizeof(char));
if( vsnprintf(buf, buf_size, msg, ap) >= buf_size )
shell_error("shell_debug() failed to assemble message.");
else
fprintf(stderr, "debug: %s\n", buf);
// Clean up used memory.
free(var_types);
free(buf);
}
shell_args *cmd_args = NULL;
shell_args *last_arg = NULL;
void shell_push_args(const char *value) {
assert(cmd_args == NULL && last_arg == NULL);
last_arg = malloc(sizeof(shell_args));
last_arg->arg = strdup(value);
last_arg->next = NULL;
cmd_args = last_arg;
}
void shell_append_args(const char *value) {
assert(cmd_args != NULL && last_arg != NULL);
shell_args *next_arg = malloc(sizeof(shell_args));
next_arg->arg = strdup(value);
next_arg->next = NULL;
last_arg->next = next_arg;
last_arg = next_arg;
}
shell_args *shell_pop_args() {
assert(cmd_args && last_arg != NULL);
shell_args *args = cmd_args;
cmd_args = last_arg = NULL;
return args;
}
void shell_exec(shell_args *args) {
assert(args && args->arg);
shell_debug("exec %s", args->arg);
// TODO: handle arguments.
shell_args *cur = args->next;
while(cur) {
assert(cur->arg);
shell_debug("argument: %s", cur->arg);
cur = cur->next;
}
// Append to command history.
shell_history_append(FN_EXEC, args);
}
void shell_pipe() {
shell_debug("start pipe");
}
/*
* Gracefully exit the shell.
*/
void shell_exit() {
shell_debug("Exiting shell");
// Append to command history.
shell_history_append(FN_EXIT, NULL);
void start_shell_pipe() { exit(EXIT_SUCCESS);
printf("start pipe");
} }
void start_shell_entry() { void shell_help() {
printf(">>> "); // TODO: implement help text.
// Append to command history.
shell_history_append(FN_HELP, NULL);
}
/*
* Change the current working directory of the shell. This function handles
* absolute and relative paths and will return 0 when the directory is
* successfully changed, or otherwise 1 in case of an error (e.g. path does not
* exist).
*/
void shell_change_dir(const char *path) {
shell_debug("change directory: %s", path);
char *cur = malloc((PATH_LENGTH+1) * sizeof(char));
shell_debug("before: %s", getcwd(cur, PATH_LENGTH));
// Depending on the file system, other errors can be returned.
switch( chdir(path) ) {
case 0:
// No error, always a good sign :-).
break;
case EACCES:
shell_error("Search permissions denied for path.");
break;
case EFAULT:
shell_error("Path points outside your accessible address space.");
break;
case EIO:
shell_error("An I/O error occurred.");
break;
case ELOOP:
shell_error("Too many symbolic links while resolving path.");
break;
case ENAMETOOLONG:
shell_error("Path is too long.");
break;
case ENOENT:
shell_error("The file does not exist.");
break;
case ENOMEM:
shell_error("Insufficient kernel memory was available.");
break;
case ENOTDIR:
shell_error("Path is not a directory.");
break;
default:
shell_error("Unknown error ocurred while changing directory.");
break;
}
shell_debug("after: %s", getcwd(cur, PATH_LENGTH));
// Append to command history.
shell_push_args(path);
shell_history_append(FN_EXEC, shell_pop_args());
}
/*
* Each new line will start a new shell entry ("$ "), which is an indicator to
* the user that he is able to enter another command. This function will not do
* anything when there is no terminal attached to the shell (but, for example, a
* pipe to prevent the printing of redundant characters).
*/
void shell_entry() {
if( !is_interactive )
return;
printf("$ ");
fflush(stdout);
}
shell_history_log *shell_history = NULL;
int shell_history_count = 0;
shell_cmd *shell_history_get(int prev) {
assert(prev >= 1);
if( prev >= shell_history_count )
return NULL;
shell_history_log *cur = shell_history;
while( cur && cur->prev && --prev ) {
assert(cur);
assert(cur->cmd);
assert(cur->cmd->args);
printf("skipping [%d]: %s\n", prev, cur->cmd->args->arg);
cur = cur->prev;
}
return cur ? cur->cmd : NULL ;
}
void shell_history_add(shell_cmd *cmd) {
assert(cmd);
if( !shell_history ) {
shell_history = malloc(sizeof(shell_history_log));
shell_history->cmd = cmd;
shell_history->prev = shell_history->next = NULL;
shell_history_count = 1;
}
else {
shell_history_log *history = malloc(sizeof(shell_history_log));
history->cmd = cmd;
history->next = NULL;
history->prev = shell_history;
shell_history->next = history;
shell_history = history;
shell_history_count++;
}
}
void shell_history_clear() {
// TODO: free shell command history.
}
void shell_history_append(shell_func type, shell_args *args) {
shell_cmd *cmd = malloc(sizeof(shell_cmd));
cmd->func = type;
cmd->args = args;
shell_history_add(cmd);
}
void shell_repeat(int prev) {
shell_debug("repeat %d", prev);
assert(prev > 0);
// TODO: implement repeat builtin.
shell_cmd *cmd;
if( !(cmd = shell_history_get(prev)) ) {
shell_error("Cannot repeat command before recorded history.");
return;
}
shell_history_add(cmd);
} }
int main(void) { int main(void) {
// Detect if terminal is attached // Detect if terminal is attached.
struct stat stats; struct stat stats;
fstat(0, &stats); fstat(0, &stats);
// Looks like a tty, so we're in interactive mode. // Looks like a tty, so we're in interactive mode.
if (S_ISCHR(stats.st_mode)) { if (S_ISCHR(stats.st_mode))
is_interactive = 1; is_interactive = 1;
start_shell_entry();
}
else if (S_ISFIFO(stats.st_mode)) else if (S_ISFIFO(stats.st_mode))
// Looks like a pipe, so we're in non-interactive mode. // Looks like a pipe, so we're in non-interactive mode.
is_interactive = 0;
else
is_interactive = 1; is_interactive = 1;
else {
is_interactive = 1;
start_shell_entry();
}
// Disable yacc debug mode // Create the first shell entry.
yyset_debug(1); shell_entry();
// Disable yacc debug mode.
yyset_debug(0);
// Start the shell parser // Start the shell parser.
if( yyparse() ) { if( yyparse() ) {
fprintf(stderr, "Error: Parsing failed\n"); fprintf(stderr, "Error: Parsing failed\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// EOF reached: clean up the mess // EOF reached: clean up the mess.
shell_history_clear();
yylex_destroy(); yylex_destroy();
if( yyin ) if( yyin )
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment