Commit ed1d3aeb authored by Sander Mathijs van Veen's avatar Sander Mathijs van Veen

Merge branch 'master' of ssh://vo20.nl/git/uva

parents 76dd1345 04b26d4e
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,13 +58,19 @@ exit {
return EXIT;
}
/* Help shell builtin */
help {
column_no += 4;
return HELP;
}
/* Change directory builtin */
cd {
column_no += 2;
return CD;
}
/* Identifiers: source */
/* Identifiers: source, cat, echo */
{id} {
column_no += yyleng;
yylval.str = strdup(yytext);
......@@ -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);
void shell_exec_process(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 <signal.h>
#include <sys/wait.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) {
shell_debug("argument: `%s'", cur->arg);
cur = cur->next;
}
// Fork the process and handle forking error.
pid_t pid = fork();
switch( pid ) {
case -1:
shell_error("Error when forking");
exit(EXIT_FAILURE);
case 0:
shell_debug("Child process #%d is ready.", getpid());
shell_exec_process(args);
break;
default:
shell_debug("Parent launched child process #%d.", pid);
shell_history_append(FN_EXEC, args);
}
}
/*
* Helper function for shell_exec(), which executes the process using execv()
* and handles all occurred errors caused by execv().
*/
void shell_exec_process(shell_args *args) {
assert(args && args->arg);
char *bin = malloc((5 + strlen(args->arg) + 1) * sizeof(char));
bin = strcpy(strcpy(bin, "/bin/")+5, args->arg)-5;
// Count the arguments.
int argc = 0;
for( shell_args *arg = args; arg; arg = arg->next, argc++);
// Convert argument structure to list of strings. And add a NULL pointer as
// a marker for the end of the list. The first string should point to the
// binary being executed.
char **argv = malloc((argc+1) * sizeof(char *));
argv[0] = bin;
shell_args *cur = args + sizeof(shell_args *);
for( int a = 0; a < argc && cur; cur = args->next, a++)
argv[a] = strdup(cur->arg);
argv[argc] = NULL;
// Function execvp() searches in PATH-environment, which is more flexible
// (user can set its own preferences), but the assignment states execv()
// should be used, because only binaries in /bin should be loaded.
shell_debug("execv(%s, [...]) called.\n", bin);
execv(bin, argv);
// If the execv() call returns, an error occurred and errno contains the
// error number corresponding with the error occurred.
switch( errno ) {
case E2BIG:
shell_error("The total number of bytes in the environment (envp)"
" and argument list (argv) is too large.");
break;
case EACCES:
shell_error("Search permission is denied on a component of the"
" path prefix of filename or the name of a script "
" interpreter. The file or a script interpreter is not a"
" regular file. Execute permission is denied for the file"
" or a script or ELF interpreter. The file system is"
" mounted noexec.");
break;
case EFAULT:
shell_error("Filename points outside accessible address space.");
break;
case EINVAL:
shell_error("An ELF executable had more than one PT_INTERP segment"
" (i.e., tried to name more than one interpreter).");
break;
case EIO:
shell_error("An I/O error occurred.");
break;
case EISDIR:
shell_error("An ELF interpreter was a directory.");
break;
case ELIBBAD:
shell_error("An ELF interpreter was not in a recognized format.");
break;
case ELOOP:
shell_error("Too many symbolic links were encountered in resolving"
" filename or the name of a script or ELF interpreter.");
break;
case EMFILE:
shell_error("The process has the maximum number of files open.");
break;
case ENAMETOOLONG:
shell_error("Filename is too long.");
break;
case ENFILE:
shell_error("The system limit on the total number of open files"
" has been reached.");
break;
case ENOENT:
shell_error("The file filename or a script or ELF interpreter does"
" not exist, or a shared library needed for file or"
" interpreter cannot be found.");
break;
case ENOEXEC:
shell_error("An executable is not in a recognized format, is for"
" the wrong architecture, or has some other format error"
" that means it cannot be executed.");
break;
case ENOMEM:
shell_error("Insufficient kernel memory was available.");
break;
case ENOTDIR:
shell_error("A component of the path prefix of filename or a"
" script or ELF interpreter is not a directory.");
break;
case EPERM:
shell_error("The file system is mounted nosuid, the user is"
" not the superuser, and the file has the set-user-ID or"
" set-group-ID bit set. The process is being traced, the"
" user is not the superuser and the file has the "
" set-user-ID or set-group-ID bit set.");
break;
case ETXTBSY:
shell_error("Executable was open for writing by one or more"
" processes.");
break;
}
}
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);
exit(EXIT_SUCCESS);
}
void shell_help() {
// TODO: implement help text.
puts("Builtin commands / macros:");
puts(" cd PATH - Change current working directory to PATH.");
puts(" exit - Exit this shell (also CTRL + D).");
puts(" help - This help text.");
puts(" !! - Repeat previous shell command.");
puts(" !N - Repeat Nth previous shell command.");
void start_shell_pipe() {
printf("start pipe");
// Append to command history.
shell_history_append(FN_HELP, NULL);
}
void start_shell_entry() {
printf(">>> ");
/*
* 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);
shell_debug("skipping [%d]: %s\n", prev, cur->cmd->args->arg);
cur = cur->prev;
}
if( cur )
shell_debug("found [%d]: %s\n", prev, cur->cmd->args->arg);
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);
}
/*
* Process number of the parent shell.
*/
pid_t parent_pid;
/*
* Signal handler for SIGINT, SIGTERM and SIGCHLD.
*/
void signal_handler(int signum) {
char *sig = "TERM";
pid_t pid;
// TODO: implement signal handling
switch( signum ) {
case SIGINT:
sig = "INT";
case SIGTERM:
if( (pid = getpid()) == parent_pid )
printf("Parent process received SIG%s and will exit.\n", sig);
else
printf("Process #%d received SIG%s and will exit.\n", pid, sig);
exit(1);
case SIGCHLD:
puts("Parent process received SIGCHLD.");
pid = wait(NULL);
printf("Child with id %d exited.\n", pid);
break;
default:
puts("This handler does not understand this type of signal.");
}
}
/*
* Main function, initiates the program.
*/
int main(void) {
// Detect if terminal is attached
struct sigaction action = {.sa_handler = signal_handler};
parent_pid = getpid();
// Bind signal handlers
if( sigaction(SIGINT, &action, NULL)
|| sigaction(SIGTERM, &action, NULL)
|| sigaction(SIGCHLD, &action, NULL) )
{
perror("An error occured while binding the signal handlers.\n");
exit(1);
}
// 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 )
......
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