// MIT License
//
// Copyright (c) 1998 Nikolaos D. Bougalis <nikb@bougalis.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>
#include <term.h>
#include <assert.h>
#include <string.h>
#include <strings.h>
#include <pwd.h>
#include <ctype.h>
#define COLOR_BOLD 1
#define COLOR_LITE 2
#define COLOR_BLACK 30
#define COLOR_RED 31
#define COLOR_GREEN 32
#define COLOR_YELLOW 33
#define COLOR_BLUE 34
#define COLOR_MAGENTA 35
#define COLOR_CYAN 36
#define COLOR_GREY 37
#define KEY_ARROW 0x5B
#define KEY_ARROW_UP 0x41
#define KEY_ARROW_DOWN 0x42
#define KEY_ARROW_LEFT 0x44
#define KEY_ARROW_RIGHT 0x43
// We store the username of the user we are running under so that
// we can display it when necessary:
char * shell_user = NULL ;
// We store the "current working directory" so that we can display
// it when we have to print a prompt.
char * shell_cwd = NULL ;
// We store the user's home directory so that we can reference it
// when necessary.
char * shell_home = NULL ;
// A couple of special-purpose buffers:
char shell_ampsnd_str [ 2 ] = { '&' , 0 };
char shell_pipeln_str [ 2 ] = { '|' , 0 };
// We keep track of the last SHELL_HIST_MAX input buffers that the
// user has specified using a circular array of character pointers.
#define SHELL_HIST_MAX 12
char * shell_hist_cmd [ SHELL_HIST_MAX ];
int shell_hist_idx = 0 ;
// This variables keep track of how many times that "up" and
// "down" keys have been pressed respectively while the user navigates
// the command history. They are automatically reset every time the
// shell prepares to grab a new line.
int shell_hist_updn = 0 ;
// We keep track of SHELL_BGPROC_MAX processes using this structure:
#define SHELL_BGPROC_MAX 12
struct BGPROC_TRACK
{
pid_t pid ;
char * cmd ;
};
struct BGPROC_TRACK shell_bgproc [ SHELL_BGPROC_MAX ];
void internal_shell_abort ( const char * s , const char * f , int l )
{ // Print the current error, along with where it occurred and then exit
printf ( " \x1B [%d;%dmA fatal error has occurred! \x1B [0m \n " , COLOR_LITE , COLOR_RED );
if (( s != NULL ) && ( * s != 0 ))
printf ( " \t Error Location: %s:%d (%s) \n " , f , l , s );
else
printf ( " \t Error Location: %s:%d \n " , f , l );
printf ( " \t Error Message: %s \n " , strerror ( errno ));
printf ( " \x1B [%d;%dmThe shell will now exit. Goodbye! \x1B [0m \n " , COLOR_LITE , COLOR_RED );
// This isn't strictly necessary; stdout is line-buffered, so putting a newline
// automatically flushes the output. But be extra conservative and make sure
// we flush.
fflush ( stdout );
_exit ( - 1 );
}
// This macro is what we call to abort the shell. It automatically passes the
// correct filename and line number at which the error occurred.
#define shell_abort(s) internal_shell_abort(s, __FILE__, __LINE__)
void internal_shell_error ( const char * s , int err , const char * f , int l )
{ // Print the string in cmd
printf ( " \x1B [%d;%dmAn error has occurred! \x1B [0m \n " , COLOR_LITE , COLOR_RED );
if (( s != NULL ) && ( * s != 0 ))
printf ( " \t Error Location: %s:%d (%s) \n " , f , l , s );
else
printf ( " \t Error Location: %s:%d \n " , f , l );
printf ( " \t Error Message: (%d): %s \n " , errno , strerror ( errno ));
// This isn't strictly necessary; stdout is line-buffered, so putting a newline
// automatically flushes the output. But be extra conservative and make sure
// we flush.
fflush ( stdout );
}
// This macro is what we call to report errors from the shell. It automatically
// passes the correct filename and line number at which the error occurred.
#define shell_error(s, n) internal_shell_error(s, n, __FILE__, __LINE__)
void shell_syntax_error ( const char * s )
{ // Print the string in cmd
printf ( " \x1B [%d;%dmSyntax error \x1B [0m" , COLOR_LITE , COLOR_RED );
if (( s != NULL ) && ( * s != 0 ))
printf ( ": %s" , s );
printf ( " \n " );
// This isn't strictly necessary; stdout is line-buffered, so putting a newline
// automatically flushes the output. But be extra conservative and make sure
// we flush.
fflush ( stdout );
}
void print_prompt ()
{
const char * cwd = shell_cwd ;
int color = COLOR_GREEN ;
if ( cwd == NULL )
{ // If the directory isn't known, highlight it in color
cwd = "[???] > " ;
color = COLOR_RED ;
}
// We print a fancy colored prompt using ANSI
// escape sequences.
printf ( " \x1B [%d;%dm%s \x1B [0m > " , COLOR_LITE , color , cwd );
fflush ( stdout );
}
char * shell_getcwd ()
{ // Get the current working directory from the system.
// The pointer returned must be freed.
char * cwd = NULL ;
size_t len = 0 ;
do
{
len += 512 ;
cwd = ( char * ) malloc ( len + 1 );
if ( cwd == NULL )
shell_abort ( "shell_getcwd" );
if ( getcwd ( cwd , len ) == NULL )
{ // We couldn't get the current directory. First, free
// the memory buffer that we have allocated, then check
// to see what the problem was.
if ( cwd != NULL )
free ( cwd );
cwd = NULL ;
if ( errno == ENOENT )
{ // The current directory does not exist. Instead of
// panicking, let's simply switch to the user's home
// directory and get that instead.
if (( shell_home != NULL ) && ( chdir ( shell_home ) != - 1 ))
{ // Success! We've switched to the home directory, so
// we can strdup the shell_home pointer and return it:
cwd = strdup ( shell_home );
if ( cwd == NULL )
shell_abort ( "getcwd" );
return cwd ;
}
}
if ( errno != ERANGE ) // This is an error we don't know how to deal with.
shell_abort ( "getcwd" );
}
} while ( cwd == NULL );
return cwd ;
}
int parse_command ( char * linebuf , char *** argv_ptr , int * amp , int * pp )
{ // We parse the command in 'linebuf' into an array of tokens
// that are separated by spaces. We mangle the 'linebuf'
// contents in the process.
char empty [ 1 ];
if (( amp == NULL ) || ( pp == NULL ))
shell_abort ( "parse_command" );
if ( linebuf == NULL )
linebuf = empty ;
// We skip multiple whitespaces at the beginning of an input
// string since they are irrelevant.
while ( * linebuf == ' ' )
linebuf ++ ;
// We allocate an array of character pointers into which
// we store the tokenized string. We expand the array as
// necessary, when we run out of space.
int argc = 0 , alloc_argc = 0 ;
char ** argv = ( char ** ) malloc ( sizeof ( char * ));
* amp = 0 ;
* pp = 0 ;
while ( * linebuf != 0 )
{
if ( argc == alloc_argc )
{ // If we don't have enough memory to store the new
// token, allocate some extra now.
alloc_argc += 8 ;
// 'argv' will be NULL during the first call, which is
// fine, because then realloc behaves like 'malloc'
//
// We add one extra item to allow space for the terminating
// NULL pointer we will add.
argv = ( char ** ) realloc ( argv , sizeof ( char * ) * ( alloc_argc + 1 ));
if ( argv == NULL )
shell_abort ( "parse_command" );
}
if ( * linebuf == '&' )
{ // An ampersand character! Track it and put it in a token by itself:
argv [ argc ++ ] = shell_ampsnd_str ;
* linebuf ++ = 0 ;
* amp = 1 + * amp ;
}
else if ( * linebuf == '|' )
{ // A pipeline character! Track it and put it in a token by itself:
argv [ argc ++ ] = shell_pipeln_str ;
* linebuf ++ = 0 ;
* pp = 1 + * pp ;
}
else
{
// Now, store the pointer into the current token into our
// token array, and increment the array pointer.
argv [ argc ++ ] = linebuf ;
// Now advance until we get a space, ampersand, pipeline
// or NULL character.
while (( * linebuf != 0 ) && ( * linebuf != ' ' ) &&
( * linebuf != '|' ) && ( * linebuf != '&' ))
* linebuf ++ ;
}
// If it's a space, we replace it with a NULL character
// which effectively terminates the current token. We
// increment our pointer to get to the next non-space
// character, and then continue with the main loop.
while ( * linebuf == ' ' )
* linebuf ++ = 0 ;
}
// We are guaranteed to have enough space to put the terminating
// NULL pointer, so this is always safe:
argv [ argc ] = NULL ;
// Now, we want to give our caller back a pointer to the tokenized
// string array.
* argv_ptr = argv ;
// And also how many arguments can found.
return argc ;
}
int check_builtin ( int argc , char ** argv )
{ // Checks whether a particular command is internally handled by our
// shell. If so, executes the appropriate code and returns a status
// code that is greater than or equal to 0.
//
// If we don't handle the command, we simply return -1.
if (( strcasecmp ( argv [ 0 ], "exit" ) == 0 ) ||
( strcasecmp ( argv [ 0 ], "quit" ) == 0 ))
{ // First, check if the user typed 'exit' or 'quit' which
// indicate that we are done.
printf ( "Goodbye! \n " );
// Returning 0 to our caller will exit the shell gracefully
return 0 ;
}
// Now, check what the user wants us to do. First, determine if
// this is one of the built-in commands that we support.
if (( strcasecmp ( argv [ 0 ], "hist" ) == 0 ) ||
( strcasecmp ( argv [ 0 ], "history" ) == 0 ))
{
int hasone = 0 , i ;
for ( i = 0 ; i < SHELL_HIST_MAX - 1 ; i ++ )
{
int idx = ( shell_hist_idx + i ) % SHELL_HIST_MAX ;
if ( idx < 0 )
idx += SHELL_HIST_MAX ;
if ( shell_hist_cmd [ idx ] != NULL )
{
if ( hasone ++ == 0 )
printf ( "Command history: \n " );
printf ( " \x1B [%d;%dm%s \x1B [0m \n " , COLOR_LITE , COLOR_BLUE , shell_hist_cmd [ idx ]);
}
}
if ( hasone == 0 )
printf ( "The command history is empty. \n " );
return 1 ;
}
if (( strcasecmp ( argv [ 0 ], "cd" ) == 0 ) ||
( strcasecmp ( argv [ 0 ], "chdir" ) == 0 ))
{
if ( argc == 1 )
{ // No argument specified; print the current directoy (if we can)
if ( shell_cwd == NULL )
printf ( "The current directory is \x1B [%d;%dmunknown \x1B [0m! \n " ,
COLOR_LITE , COLOR_RED );
else
printf ( "The current directory is \"\x1B [%d;%dm%s \x1B [0m \"\n " ,
COLOR_LITE , COLOR_GREEN , shell_cwd );
return 1 ;
}
if ( argc != 2 )
{ // We don't really support paths with spaces for our
// simple shell.
printf ( " \x1B [%d;%dm%s \x1B [0m: Paths with spaces are not supported \n " ,
COLOR_LITE , COLOR_RED , argv [ 0 ]);
return 1 ;
}
const char * cd = argv [ 1 ];
if ( strcmp ( cd , "~" ) == 0 ) // User convenience. ~ maps to home directory
cd = shell_home ;
if ( chdir ( cd ) == - 1 )
{ // An error occurred. Print an error message, and if the error
// was fatal, abort.
if (( errno == ENOENT ) || ( errno == ENOTDIR ) || ( errno == EACCES ))
{
printf ( " \x1B [%d;%dm%s \x1B [0m: %s \n " , COLOR_LITE , COLOR_RED ,
argv [ 0 ], strerror ( errno ));
return 1 ;
}
shell_abort ( "user_chdir" );
// We never get here, since shell_abort exits the process
return 1 ;
}
if ( shell_cwd != NULL )
free ( shell_cwd );
shell_cwd = shell_getcwd ();
if ( shell_cwd == NULL )
shell_abort ( "user_chdir_getcwd" );
// We want to continue doing things
return 1 ;
}
if ( strcasecmp ( argv [ 0 ], "bg" ) == 0 )
{
int hasone = 0 , i ;
for ( i = 0 ; i < SHELL_BGPROC_MAX ; i ++ )
{
if ( shell_bgproc [ i ]. pid == - 1 )
continue ;
if ( hasone ++ == 0 )
printf ( "Tracked background processes: \n " );
printf ( " [ \x1B [%d;%dm%6d \x1B [0m ] %s \n " ,
COLOR_LITE , COLOR_BLUE , shell_bgproc [ i ]. pid , shell_bgproc [ i ]. cmd );
}
if ( hasone == 0 )
printf ( "No tracked background processes are running. \n " );
return 1 ;
}
// We don't know how to handle this command. Try to see if there
// is an external program.
return - 1 ;
}
int launch_process ( char * file , char ** argv , int bg )
{
pid_t kid = fork ();
if ( kid == - 1 )
{ // Ouch, an error occurred. Print a somewhat informative
// error message and return.
shell_error ( file , errno );
return 1 ;
}
// OK, we successfully forked.
if ( kid == 0 )
{ // The child gets to exec the new process now. Technically,
// execvp will only return to us if there is an error, so
// when that happens, we will simply print our an error
// message and exit.
if ( execvp ( file , argv ) == - 1 )
_exit ( errno ); // Ack! An error happened; pass the code to our parent!
// We will never really get here.
_exit ( 0 );
}
if ( bg == 0 )
{ // We are the parent and we want to run this in the foreground,
// so we must wait for our child.
int kid_status = 0 ;
if ( waitpid ( kid , & kid_status , 0 ) == - 1 )
shell_error ( file , errno );
if ( WIFEXITED ( kid_status ))
{
int err = WEXITSTATUS ( kid_status );
if ( err != 0 )
{
if (( err == ENOENT ) || ( err == ENOEXEC ))
printf ( " \x1B [%d;%dm%s \x1B [0m: %s \n " , COLOR_LITE , COLOR_RED ,
file , strerror ( err ));
else
shell_error ( file , errno );
}
}
return 1 ;
}
// Let's add this process in the array that keeps track of background processes
// if there's space:
int bgidx = 0 ;
while ( bgidx != SHELL_BGPROC_MAX )
{
if ( shell_bgproc [ bgidx ]. pid != - 1 )
{
bgidx ++ ;
continue ;
}
shell_bgproc [ bgidx ]. pid = kid ;
shell_bgproc [ bgidx ]. cmd = strdup ( file );
break ;
}
if ( bgidx == SHELL_BGPROC_MAX )
{ // There was no space left to track the execution of this process in the
// background process tracking array. Warn the user and continue.
printf ( " \x1B [%d;%dm%s \x1B [0m: Unable to track background execution (out of space) \n " ,
COLOR_LITE , COLOR_RED , file , strerror ( errno ));
}
return 1 ;
}
int process_arrow_keys ( char ** linebuf )
{
if ( linebuf == NULL )
{
shell_error ( "invalid line buffer" , EINVAL );
return 0 ;
}
char extra [ 2 ] = { 0 , 0 };
if (( read ( 0 , extra , 2 ) == 2 ) && ( extra [ 0 ] == KEY_ARROW ))
{ // Cool, this is a code we're interested in.
if ( extra [ 1 ] == KEY_ARROW_UP )
{
* linebuf = NULL ;
if ( shell_hist_updn == SHELL_HIST_MAX )
{ // We're at the top -- sound the bell
printf ( " \a " );
fflush ( stdout );
}
if ( shell_hist_updn != SHELL_HIST_MAX )
{ // We still have 'space' in our circular buffer to
// go up one more command in the history.
shell_hist_updn ++ ;
int idx = ( shell_hist_idx - shell_hist_updn ) % SHELL_HIST_MAX ;
if ( idx < 0 )
idx += SHELL_HIST_MAX ;
* linebuf = shell_hist_cmd [ idx ];
}
// We aren't done with the processing of the current command.
return 0 ;
}
if ( extra [ 1 ] == KEY_ARROW_DOWN )
{
* linebuf = NULL ;
if ( shell_hist_updn == 1 )
{ // We're at the bottom -- sound the bell
printf ( " \a " );
fflush ( stdout );
}
if ( shell_hist_updn > 1 )
{ // We still have 'space' to go down one more command in
// our circular buffer history.
shell_hist_updn -- ;
int idx = ( shell_hist_idx - shell_hist_updn ) % SHELL_HIST_MAX ;
if ( idx < 0 )
idx += SHELL_HIST_MAX ;
* linebuf = shell_hist_cmd [ idx ];
}
// We aren't done with the processing of the current command.
return 0 ;
}
}
return 0 ;
}
int launch_process_redirect ( const char * file , char ** argv , int oldfd , int newfd )
{
pid_t kid = fork ();
if ( kid == - 1 )
{ // Ouch, an error occurred. Print a somewhat informative
// error message and return.
shell_error ( file , errno );
return - 1 ;
}
// OK, we successfully forked.
if ( kid == 0 )
{ // Cool, this is the child; first, redirect the appropriate
// file descriptor to the new file descriptor.
if (( oldfd != - 1 ) && ( newfd != - 1 ))
{ // We need to remap oldfd so that it points to newfd:
if (( close ( oldfd ) != - 1 ) && ( dup ( newfd ) != oldfd ))
_exit ( EBADFD ); // Ack!
}
// The child gets to exec the new process now. Technically,
// execvp will only return to us if there is an error, so
// when that happens, we will simply print our an error
// message and exit.
if ( execvp ( file , argv ) == - 1 )
_exit ( errno );
// We will never really get here.
_exit ( 0 );
}
// We are the parent and we want to run this in the foreground,
// so we must wait for our child.
int kid_status = 0 ;
if ( waitpid ( kid , & kid_status , 0 ) == - 1 )
shell_error ( file , errno );
// We need to close the fd that we redireted to avoid leaking
// file handles.
close ( newfd );
// And print an appropriate error message, if necessary.
if ( WIFEXITED ( kid_status ))
{
int err = WEXITSTATUS ( kid_status );
if ( err != 0 )
{
if (( err == ENOENT ) || ( err == ENOEXEC ))
printf ( " \x1B [%d;%dm%s \x1B [0m: %s \n " , COLOR_LITE , COLOR_RED ,
file , strerror ( err ));
else
shell_error ( file , errno );
}
}
return 1 ;
}
int handle_command ( int argc , char ** argv , int amp , int ppln )
{
if ( argc == 0 )
return 1 ;
if (( amp != 0 ) && ( amp != 1 ))
{ // Exactly 0 or 1 ampersands are allowed by our shell.
shell_syntax_error ( "cannot have more than one & character" );
return 1 ;
}
if (( ppln != 0 ) && ( ppln != 1 ))
{ // Exactly 0 or 1 pipes are allowed by our shell.
shell_syntax_error ( "cannot have more than one | character" );
return 1 ;
}
if (( amp != 0 ) && ( amp + ppln != 1 ))
{ // You cannot combine background processes with pipes in our shell
shell_syntax_error ( "cannot combine background processes with pipelines" );
return 1 ;
}
int ret = - 1 ;
// Let's see if it's a builtin command that we understand, or if it's
// one that we need to launch.
if (( amp == 0 ) && ( ppln == 0 ))
ret = check_builtin ( argc , argv );
if ( ret == - 1 )
{ // Well, we need a bit of extra work. This might be a simple
// command that we need to fork and execute, or it might be
// a pipelined set of commands. Either way, handle it:
if ( ppln == 1 )
{ // OK, this is a pipeline. We want to parse out the two
// sets of commands that are in our input buffer. Remember
// that they are separated by a | character, so iterate
// and find that character, replacing it by a NULL.
char ** argv1 = argv ;
char ** argv2 = argv ;
while (( argv2 != NULL ) && ( strcmp ( * argv2 , "|" ) != 0 ))
argv2 ++ ;
if ( argv2 == NULL )
{
shell_syntax_error ( "unable to parse pipeline" );
return 1 ;
}
* argv2 ++ = NULL ;
if ( * argv2 == NULL )
{
shell_syntax_error ( "incomplete pipeline (the | character must not be at the end of the command)" );
return 1 ;
}
// Good, now, argv1 points to the command and arguments of the first
// part of the pipeline and argv2 points to the command and arguments
// of the second part of the pipeline. We continue by setting up the
// pipes we will be using:
int pipes [ 2 ];
if ( pipe ( pipes ) == - 1 )
{
shell_error ( "pipe" , errno );
return 1 ;
}
// Launch the first process in the pipeline, wait for it to finish
// and if it does so successfully, launch the second process.
if ( launch_process_redirect ( argv1 [ 0 ], argv1 , 1 , pipes [ 1 ]) != - 1 )
launch_process_redirect ( argv2 [ 0 ], argv2 , 0 , pipes [ 0 ]);
return 1 ;
}
if ( amp == 1 )
{
if ( strcmp ( argv [ argc - 1 ], shell_ampsnd_str ) != 0 )
{ // The ampersand that we use to put a job in the background
// must be at the end.
shell_syntax_error ( "the & character must be at the end of the command" );
return 1 ;
}
// Trim the ampersand off the command line we're about to execute
argc -- ;
argv [ argc ] = NULL ;
}
ret = launch_process ( argv [ 0 ], argv , amp );
}
return ret ;
}
int process_line ()
{ // We print a prompt, accept the user input, process it and
// return to our caller. We return '1' if we need to continue
// running, and 0 otherwise.
// First, make sure that our up/down handlers know what they
// need to do and from where they need to start.
shell_hist_updn = 0 ;
print_prompt ();
int linelen = 128 , lineidx = 0 , tmp ;
char * linebuf = ( char * ) malloc ( linelen ), key ;
while ( read ( 0 , & key , 1 ) == 1 )
{
if ( key == 0x1B )
{ // This is the beginning of a three-letter combo
// that we may be interested in... Let's see:
char * hist = NULL ;
if ( process_arrow_keys ( & hist ))
return 1 ;
if ( hist != NULL )
{ // We need to replace the current input buffer
// with whatever the contents in hist are.
// First, we need to clear out what's already there
// by backing up, replacing it with spaces and then
// backing up again:
for ( tmp = 0 ; tmp < lineidx ; tmp ++ )
printf ( " \b " );
for ( tmp = 0 ; tmp < lineidx ; tmp ++ )
printf ( " " );
for ( tmp = 0 ; tmp < lineidx ; tmp ++ )
printf ( " \b " );
int histlen = ( int ) strlen ( hist );
if ( histlen + 2 >= linelen )
{ // We need to allocate a new buffer
free ( linebuf );
linelen = histlen + 64 ;
linebuf = ( char * ) malloc ( linelen );
if ( linebuf == NULL )
shell_abort ( "malloc-linebuf-hist" );
}
// And now, copy the new command into our line buffer
lineidx = histlen ;
strcpy ( linebuf , hist );
printf ( "%s" , linebuf );
fflush ( stdout );
}
continue ;
}
if ( key == 0x7F )
{ // We have to handle a backspace character
if ( lineidx == 0 )
{ // We can't backspace if we're at the beginning of the
// line. So just sound the bell and do nothing.
printf ( " \a " );
fflush ( stdout );
continue ;
}
// Decrement our line index, so that on the next keystroke we
// effectively erase the last character from our input buffer.
lineidx -- ;
// And delete it from the screen. First back the cursor up one
// position, print a space, and then back the cursor up one
// position again.
printf ( " \b \b " );
fflush ( stdout );
continue ;
}
if ( isprint ( key ))
{ // Ahh, a printable character. First, we want to add it to our
// input buffer (enlarging it if necessary) and then we want
// to print it to the screen. Note, that we always leave one
// free byte at the end of the buffer, so that the end-of-line
// handler has a guaranteed spot to null-terminate the string.
if ( lineidx + 2 == linelen )
linelen += 128 ;
linebuf = ( char * ) realloc ( linebuf , linelen );
if ( linebuf == NULL )
shell_abort ( "realloc-linebuf" );
printf ( "%c" , key );
fflush ( stdout );
linebuf [ lineidx ++ ] = key ;
continue ;
}
if ( key == '\n' )
{ // Cool, the user pressed enter.
// First, NULL-terminate the input string.
linebuf [ lineidx ] = 0 ;
// We want to copy our current command into the history buffer
// so save it now, before we hack it up with our parser:
// First, if we were already storing a command there, release
// it, freeing the associated memory buffer.
if ( shell_hist_cmd [ shell_hist_idx ] != NULL )
free ( shell_hist_cmd [ shell_hist_idx ]);
// Now, store a duplicate of the new command.
shell_hist_cmd [ shell_hist_idx ] = strdup ( linebuf );
// And manage the list index so it's ready for the next
// time around:
shell_hist_idx = ( shell_hist_idx + 1 ) % SHELL_HIST_MAX ;
// We want to output a newline string, so that any output in
// response to the user's command starts on a newline.
printf ( " \n " );
// We want to tokenize the input string into an array.
int amp , ppln , i , ret = 1 ;
char ** argv = NULL ;
int argc = parse_command ( linebuf , & argv , & amp , & ppln );
if ( argc != 0 )
ret = handle_command ( argc , argv , amp , ppln );
if ( argv != NULL )
free ( argv );
// We return to our caller with an appropriate error code:
return ret ;
}
}
// The 'read' from the terminal couldn't complete.
shell_abort ( "term-read" );
// Never really called.
return 0 ;
}
void grim_reaper_info ( pid_t pid , const char * cmd , int status )
{ // Print a short information line about the process that
// just exited.
const char * stat = "Done" ;
int code = 0 ;
if ( WIFEXITED ( status ))
{
stat = "Exited" ;
code = WEXITSTATUS ( status );
}
else if ( WIFSIGNALED ( status ))
{
stat = "Killed" ;
code = WTERMSIG ( status );
}
printf ( "[ \x1B [%d;%dm%6d \x1B [0m ] \t %7s (%d)" ,
COLOR_LITE , COLOR_BLUE , pid , stat , code );
if ( cmd != NULL )
printf ( " \t %s" , cmd );
printf ( " \n " );
}
void grim_reaper ()
{ // Reaps any childer that were executing in the background and have
// finished, printing relevant status information:
pid_t ret ;
do
{
int kid_status = 0 ;
ret = waitpid ( - 1 , & kid_status , WNOHANG );
// If we get an ECHILD error, it means there's no child processes
// at all. Which is fine -- we can ignore that error and simply
// exit from the loop.
if (( ret == - 1 ) && ( errno == ECHILD ))
ret = 0 ;
if ( ret == - 1 )
{ // An error has occurred:
shell_error ( "wait-any-pid" , errno );
return ;
}
if ( ret != 0 )
{ // Ah, a child of ours was done. Let's see which:
int bgidx = 0 ;
while ( bgidx != SHELL_BGPROC_MAX )
{
if ( shell_bgproc [ bgidx ]. pid != ret )
{
bgidx ++ ;
continue ;
}
// Figure out the status as best as we can:
grim_reaper_info ( shell_bgproc [ bgidx ]. pid , shell_bgproc [ bgidx ]. cmd , kid_status );
// And free this slot from the table
free ( shell_bgproc [ bgidx ]. cmd );
shell_bgproc [ bgidx ]. cmd = NULL ;
shell_bgproc [ bgidx ]. pid = - 1 ;
break ;
}
if ( bgidx == SHELL_BGPROC_MAX ) // Ack, we couldn't find this child! Just print its PID
grim_reaper_info ( ret , NULL , kid_status );
}
} while ( ret != 0 );
}
int interactive_shell ()
{
// We need to get some information about the user we are running
// as: namely, the username and their home directory.
struct passwd * passwd = getpwuid ( geteuid ());
int i ;
if ( passwd == NULL )
shell_abort ( "getpwuid" );
// First, save the username:
if (( passwd -> pw_name == NULL ) || ( passwd -> pw_name [ 0 ] == 0 ))
shell_abort ( "getpwuid:nousername" );
shell_user = strdup ( passwd -> pw_name );
if ( shell_user == NULL )
shell_abort ( "strdup:pw_name" );
// Now for the home directory, if one is set
if (( passwd -> pw_dir != NULL ) && ( passwd -> pw_dir [ 0 ] != 0 ))
{
shell_home = strdup ( passwd -> pw_dir );
if ( shell_home == NULL )
shell_abort ( "strdup:pw_dir" );
}
// If we haven't gotten the directory yet, try to
// expand the HOME environment variable:
if (( shell_home == NULL ) || ( shell_home [ 0 ] == 0 ))
{
char * home = getenv ( "HOME" );
if ( home != NULL )
shell_home = strdup ( home );
}
if (( shell_home == NULL ) || ( shell_home [ 0 ] == 0 ))
shell_abort ( "get-homedir" );
// Now, get the current working directory from the system
// and store it. We use it to display our prompt.
shell_cwd = shell_getcwd ();
if ( shell_cwd == NULL )
shell_abort ( "get-cwd" );
// Set up our circular buffer:
for ( i = 0 ; i < SHELL_HIST_MAX ; i ++ )
shell_hist_cmd [ i ] = NULL ;
// And setup the background process tracking array:
for ( i = 0 ; i < SHELL_BGPROC_MAX ; i ++ )
{
shell_bgproc [ i ]. pid = - 1 ;
shell_bgproc [ i ]. cmd = NULL ;
}
// Print a banner.
puts ( " \x1B [2;32mWelcome to the CS370 shell prompt. \x1B [0m" );
puts ( " \t Written by Nikolaos D. Bougalis <old email> \n " );
puts ( " \x1B [2;32mYou can use all the normal UNIX commands that are" );
puts ( "normally available to you. \n\n\x1B [0m" );
// We loop, accepting input, for as long as process_line returns
// non-zero. Between processing, wait for any of our background
// children and reap them as soon as they're done.
while ( process_line () != 0 )
grim_reaper ();
return 0 ;
}
int main ( int argc , char ** argv )
{
// This is the first invocation of the shell so what
// we want to do is set some terminal attributes and
// then launch ourselves again to interact with the
// user. We do this so that if the shell crashes we
// still get a chance to reset the terminal modes.
struct termios termcfg_old , termcfg_new ;
if ( tcgetattr ( 0 , & termcfg_old ) == - 1 )
{
shell_error ( "tcgetattr" , errno );
return - 1 ;
}
// Make a copy of the current configuration, so that we
// modify that copy as necessary.
memcpy ( & termcfg_new , & termcfg_old , sizeof ( struct termios ));
termcfg_new . c_lflag &= ~ ( ICANON | ECHO );
termcfg_new . c_cc [ VMIN ] = 10 ;
termcfg_new . c_cc [ VTIME ] = 2 ;
if ( tcsetattr ( 0 , TCSANOW , & termcfg_new ) == - 1 )
{
shell_error ( "tcsetattr" , errno );
return - 1 ;
}
pid_t kid = fork ();
if ( kid == - 1 )
{
shell_error ( "fork-interactive" , errno );
return - 1 ;
}
// If kid is 0, this means that we are executing in the child process
// which will serve as the actual shell. We isolate all the shell code
// in a routine specifically designed for this purpose. When it returns
// it means the user chose to exit our shell, so we just exit, terminating
// the process and returning control to the parent, which is waiting.
if ( kid == 0 )
return interactive_shell ();
// The fork was a success, and we just have to wait for the interactive
// component of the shell to exit now:
int kid_status = 0 ;
if ( waitpid ( kid , & kid_status , 0 ) == - 1 )
shell_error ( "wait-interactive" , errno );
// Now, reset the terminal mode -- we need to do this whether
// the child launched correctly or not. Otherwise, we'll leave
// the user with a botched terminal.
if ( tcsetattr ( 0 , TCSANOW , & termcfg_old ) == - 1 )
{
shell_error ( "tcsetattr" , errno );
return - 1 ;
}
return ( kid != - 1 ) ? 0 : 1 ;
}