Skip to content
Snippets Groups Projects
Commit 511058ba authored by Sander Mathijs van Veen's avatar Sander Mathijs van Veen
Browse files

Added shell history and implemented builtin commands.

parent eacd472b
No related branches found
No related tags found
No related merge requests found
CC=gcc
CC=clang # gcc
RM=rm -Rfv
LFLAGS= -lm
......@@ -21,6 +21,7 @@ shell: input.yacc.o input.lex.o main.o
test: shell
echo -n 'exit' | ./shell
echo -n 'cd ../' | ./shell
echo -n 'help' | ./shell
echo -n 'cat input.l' | ./shell
echo -n 'cat input.l | grep "id"' | ./shell
echo -n '!!' | ./shell
......@@ -33,7 +34,7 @@ input.yacc.h: input.yacc.c
input.lex.c: input.l
`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.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 @@
#include "input.l.h"
#include "input.yacc.h"
// Column / line counters (used in error reporting)
int line_no = 1, column_no = 0;
extern int current_line(void){ return line_no; }
extern int current_column(void){ return column_no; }
extern void reset_line(void){ line_no = 1; }
extern void reset_column(void){ column_no = 0; }
%}
%option nounput
%option noyywrap
%option nounput
%x id
%x num
......@@ -57,6 +58,12 @@ exit {
return EXIT;
}
/* Help shell builtin */
help {
column_no += 4;
return HELP;
}
/* Change directory builtin */
cd {
column_no += 2;
......@@ -83,14 +90,14 @@ cd {
}
/* Path or path with filename: ./a/, ../, config/, /tmp/, /tmp/file */
[^ ]*\/[^ ]* {
\.*{id}?\/[^\t\n ]* {
column_no += yyleng;
yylval.str = strdup(yytext);
return PATH;
}
/* Argument (filenames and such): hello.txt, .config */
[\/{id}][^ ]+ {
{id}[^\t\n ]+ {
column_no += yyleng;
yylval.str = strdup(yytext);
return ARGUMENT;
......@@ -98,22 +105,18 @@ cd {
#[^\n]+ { /* ignore comments */ }
/* End of file is a separator */
\0 {
line_no++;
reset_column();
return EOS;
}
/* White space */
\n {
line_no++;
reset_column();
shell_entry();
return EOL;
}
/* White space */
{wsp} { column_no++; }
{wsp} {
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_column(void);
extern void reset_line();
extern void reset_column();
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 <string.h>
// #include "input.func.h"
#include "input.yacc.h"
#include "input.lex.h"
#include "input.l.h"
void yyerror(const char *str) {
fflush(stdout);
fflush(stderr);
fprintf(stderr, "error: parser reported \"%s\" (line %d, column %d)\n", str,
current_line(), current_column());
......@@ -23,9 +26,10 @@ void yyerror(const char *str) {
%}
%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 {
char * str;
......@@ -35,29 +39,31 @@ void yyerror(const char *str) {
%%
Start: /* empty */
| Commands
Start: Commands
;
Commands: Command /*{ printf("cmd: %s\n", $1); }*/
/* | Command EOS*/
| Commands EOL { start_shell_entry(); } Command
| Commands '|' { start_shell_pipe(); } Command
Commands: Command
| Commands EOL Command
| Commands '|' { shell_pipe(); } Command
;
Command: Builtin
| IDENTIFIER
| IDENTIFIER Arguments
Command: /* Empty */
| Builtin
| 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"); }
| CD PATH { printf("CD %s\n", $2); }
| '!' '!' { puts("Repeat last"); }
| '!' INTEGER { printf("Repeat %d\n", atoi($2)); }
Builtin: EXIT { shell_exit(); }
| HELP { shell_help(); }
| CD Argument { shell_change_dir($2); }
| '!' '!' { shell_repeat(1); }
| '!' INTEGER { shell_repeat(atoi($2)); }
;
Arguments: Argument
| Arguments ' ' Argument
Arguments: Argument { shell_append_args($1); }
| Arguments Argument { shell_append_args($2); }
;
Argument: STRING
......
/*
* Shell implementation using flex/bison.
*
* Sander van Veen (6167969) / Taddeus Kroes (6054129).
* <sandervv@gmail.com> / <taddeuskroes@hotmail.com>
*/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <math.h>
#include "input.yacc.h"
#include "input.lex.h"
#include "input.l.h"
#define PATH_LENGTH 1024
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() {
printf("start pipe");
exit(EXIT_SUCCESS);
}
void start_shell_entry() {
printf(">>> ");
void shell_help() {
// 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) {
// Detect if terminal is attached
// Detect if terminal is attached.
struct stat stats;
fstat(0, &stats);
// 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;
start_shell_entry();
}
else if (S_ISFIFO(stats.st_mode))
// Looks like a pipe, so we're in non-interactive mode.
is_interactive = 0;
else
is_interactive = 1;
else {
is_interactive = 1;
start_shell_entry();
}
// Disable yacc debug mode
yyset_debug(1);
// Create the first shell entry.
shell_entry();
// Disable yacc debug mode.
yyset_debug(0);
// Start the shell parser
// Start the shell parser.
if( yyparse() ) {
fprintf(stderr, "Error: Parsing failed\n");
return EXIT_FAILURE;
}
// EOF reached: clean up the mess
// EOF reached: clean up the mess.
shell_history_clear();
yylex_destroy();
if( yyin )
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment