Insight into something.
////////////////////////////////////////////////////////////////////////////////
/**
* @file myshell.c
* @brief A C program to implement an interactive shell in which a user
* executes commands.
*
* @title My Shell
* @author Nicholas Guthrie
* @web http//nickguthrie.com
* @created February 26, 2013
*
* Compile with: gcc -Wall -g -o ./myshell myshell.c
* Flags: -DVERBOSE for verbose child/parent status outputs
* -DCMD to print the command struct after it has been parsed
* -DPRINTCD to print the current directory after every command
* -DTOKEN to print the token parsing
*
*/
////////////////////////////////////////////////////////////////////////////////
/**----------------------------------------------------------------- :Libraries:
// _ _ _ _
// | | (_) |__ _ _ __ _ _ _(_)___ ___
// | |__| | '_ \ '_/ _` | '_| / -_|_-<
// |____|_|_.__/_| \__,_|_| |_\___/__/
//
//----------------------------------------------------------------------------*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> //for umask
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/**-------------------------------------------------------------------- :Global:
// ___ _ _ _
// / __| |___| |__ __ _| |
// | (_ | / _ \ '_ \/ _` | |
// \___|_\___/_.__/\__,_|_|
//
//----------------------------------------------------------------------------*/
#define BUFFER_LENGTH 100 /* maximum buffer length */
#define MAX_ARGS 64 /* maximum number of arguments */
#define MAX_ARG_LEN 100 /* maximum length of a single argument */
#define WHITESPACE " ,\t\n" /* whitespace in commandline args */
#define MAX_HISTORY 999 /* maximum commands to be stored in history */
#define PATH_VAR "MYPATH" /* the environment path variable */
/**------------------------------------------------------------------- :Structs:
// ___ _ _
// / __| |_ _ _ _ _ __| |_ ___
// \__ \ _| '_| || / _| _(_-<
// |___/\__|_| \_,_\__|\__/__/
//
//----------------------------------------------------------------------------*/
struct command_t {
char *name; /* redundently stored in argv[0] */
int background;
int argc;
char *argv[MAX_ARGS];
int hasOutput, hasInput, specialOutput;
char *input;
char *output;
};
/**---------------------------------------------------------------- :Prototypes:
// ___ _ _
// | _ \_ _ ___| |_ ___| |_ _ _ _ __ ___ ___
// | _/ '_/ _ \ _/ _ \ _| || | '_ \/ -_|_-<
// |_| |_| \___/\__\___/\__|\_, | .__/\___/__/
// |__/|_|
//
//----------------------------------------------------------------------------*/
int parsePath( char *dirs[] );
int getPath (char *dirs[], int dir_size, char * command, char * path_out);
int parseCommand(char *cLine, struct command_t *cmd);
void printCommand(struct command_t *cmd);
int storeHistory (char * cLine, char history[MAX_HISTORY][BUFFER_LENGTH],
int history_size);
void printHistory (char history[MAX_HISTORY][BUFFER_LENGTH], int history_size);
int execute(char * fullpath, struct command_t * cmd,
char history[MAX_HISTORY][BUFFER_LENGTH], int history_size);
void printCD();
/**---------------------------------------------------------------------- :Main:
// __ __ _
// | \/ |__ _(_)_ _
// | |\/| / _` | | ' \
// |_| |_\__,_|_|_||_|
//
//----------------------------------------------------------------------------*/
int main( int argc, char *argv[] )
{
//////////////////////////////
// Variables
//////////////////////////////
char buffer[BUFFER_LENGTH];
char *dirs[10];
char fullpath[512];
char history[MAX_HISTORY][BUFFER_LENGTH];
int history_size = 0;
//////////////////////////////
// Shell Loop
//////////////////////////////
printf("welcome to myshell\n");
int dir_size = parsePath(dirs);
while(1)
{
#ifdef PRINTCD
printCD();
#endif
struct command_t cmd;
printf("---| ");
// Get Command from User
fgets(buffer, BUFFER_LENGTH, stdin);
// Store History
if ( strcmp(buffer, "\n" ))
history_size = storeHistory(buffer, history, history_size);
//////////////////////////////
// Process Input
//////////////////////////////
parseCommand(buffer, &cmd);
#ifdef CMD
printCommand(&cmd);
#endif
// Exit the Shell
if ( !strcmp(cmd.argv[0], "exit" ))
{
int pid;
//wait for all processes to finish
while ((pid = waitpid(-1, NULL, 0)))
{
if (errno == ECHILD)
{
break;
}
}
printf("bye\n");
exit(0);
}
// Bang Search
else if ( cmd.argv[0][0] == '!' && strlen(cmd.argv[0]) > 1 )
{
//printf("Searching for %s\n", cmd.argv[0]);
int k = history_size -1;
int found = 0;
while(k >= 0)
{
int max = strlen(cmd.argv[0]) - 1;
int j = 0;
int matches = 0;
for(j=0; j<max; j++)
{
//printf("\t%i comparing %c to %c\n", k, cmd.argv[0][j+1], history[k][j]);
if( cmd.argv[0][j+1] == history[k][j] )
matches++;
else
break;
}
if ( matches == max )
{
found = 1;
strcpy(buffer, history[k]);
parseCommand(buffer, &cmd);
//Otherwise Execute the Command if Possible
if( getPath(dirs, dir_size, cmd.argv[0], fullpath) )
execute(fullpath, &cmd, history, history_size);
break;
}
k--;
}
// Unable to find in History
if(!found)
{
printf("ERROR: no command with '");
int i;
for(i=1; i<strlen(cmd.argv[0]); i++)
printf("%c",cmd.argv[0][i]);
printf("' prefix in history\n");
}
}
//Execute the Command
else if ( getPath(dirs, dir_size, cmd.argv[0], fullpath) && strcmp(buffer, "" ) )
{
execute(fullpath, &cmd, history, history_size);
}
}
}
/**----------------------------------------------------------------- :Functions:
// ___ _ _
// | __| _ _ _ __| |_(_)___ _ _ ___
// | _| || | ' \/ _| _| / _ \ ' \(_-<
// |_| \_,_|_||_\__|\__|_\___/_||_/__/
//
//
//----------------------------------------------------------------------------*/
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Reads the PATH variable for this enviroment, then builds an array,
* dirs[], of the directories in PATH
* @param[out] dirs[] An array of the directories in PATH.
* @return The number of directories stored in dirs.
*/
////////////////////////////////////////////////////////////////////////////////
int parsePath( char *dirs[] )
{
char *pathEnvVar;
char *thePath;
int i;
for(i =0; i<MAX_ARGS; i++)
dirs[i] = NULL;
pathEnvVar = (char *) getenv( PATH_VAR );
if(pathEnvVar == NULL)
{
pathEnvVar = "/bin";
}
thePath = (char *) malloc(strlen(pathEnvVar) + 1);
strcpy(thePath, pathEnvVar);
printf("($MYPATH is %s)\n", thePath);
/* Loop to parse thePath.
Look for a ':' delimiter between each path name. */
int pos = 0;
char * pch;
#ifdef TOKEN
printf ("Splitting \"%s\" into tokens:\n",thePath);
#endif
pch = strtok (thePath,":");
while (pch != NULL)
{
#ifdef TOKEN
printf ("%s\n",pch);
#endif
// Parse ~ to $HOME Conversions
if( pch[0] == '~' )
{
pch++;
char fullpath[512];
char * HOME = (char *) getenv( "HOME" );
strcpy (fullpath, HOME);
strcat (fullpath, pch);
strcpy(pch, fullpath);
}
dirs[pos] = pch;
pch = strtok (NULL, ":");
pos++;
}
return pos;
}
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Searches for the path of the file in dirs.
* @param[in] dirs[]
* @param[in] dir_size
* @param[in] command
* @param[out] path_out
* @return 1 if the path was found, 0 otherwise.
*/
////////////////////////////////////////////////////////////////////////////////
int getPath (char *dirs[], int dir_size, char * command, char * path_out)
{
char f_name[512];
int i;
if( !strcmp(command, "history") || !strcmp(command, "cd") )
return 1;
// Test Local Directory
if(command[0]);// == '.')
{
//strcat(f_name, command);
if( access( command, F_OK) != -1 )
{
strcpy(path_out, command);
return 1;
}
}
// Test Directories in dirs[]
for(i=0; i<dir_size; i++)
{
//Concate and Copy Strings
strcpy(f_name, dirs[i]);
strcat(f_name, "/");
strcat(f_name, command);
// Found File
if( access( f_name, F_OK ) != -1 )
{
//printf("Found %s\n", f_name);
strcpy(path_out, f_name);
return 1;
}
}
printf("ERROR: command '%s' not found\n", command);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Parses a commandline string into the command structure.
* @param[in] cLine The commandline command to process.
* @param[out] cmd The command structure to store the result in.
* @return 1 on success.
*/
////////////////////////////////////////////////////////////////////////////////
int parseCommand(char *cLine, struct command_t *cmd)
{
int argc;
char **clPtr;
// Initialization
clPtr = &cLine; //cLine is the command line
argc = 0;
cmd->argc = 0;
cmd->input = NULL; cmd->output = NULL;
cmd->hasInput = 0; cmd->hasOutput = 0; cmd->specialOutput = 0;
cmd->argv[argc] = (char *) malloc(MAX_ARG_LEN);
while((cmd->argv[argc] = strsep(clPtr, WHITESPACE)) != NULL)
{
// Check for ~ to $HOME Conversions
if( cmd->argv[argc][0] == '~' )
{
cmd->argv[argc]++;
char fullpath[512];
char * HOME = (char *) getenv( "HOME" );
strcpy (fullpath, HOME);
strcat (fullpath, cmd->argv[argc]);
strcpy(cmd->argv[argc], fullpath);
}
//Check for Redirection.
if(!strcmp(cmd->argv[argc], "==>"))
{
cmd->hasOutput = 1;
cmd->output = (char *) malloc(MAX_ARG_LEN);
cmd->output = strsep(clPtr, WHITESPACE);
argc--;
}
else if(!strcmp(cmd->argv[argc], "<=="))
{
cmd->hasInput = 1;
cmd->input = (char *) malloc(MAX_ARG_LEN);
cmd->input = strsep(clPtr, WHITESPACE);
argc--;
}
else if(!strcmp(cmd->argv[argc], "-->"))
{
cmd->specialOutput = 1;
cmd->output = (char *) malloc(MAX_ARG_LEN);
cmd->output = strsep(clPtr, WHITESPACE);
argc--;
}
cmd->argv[++argc] = (char *) malloc(MAX_ARG_LEN);
}
//Set the command name and argc
cmd->argc += argc-1;
cmd->name = (char *) malloc(sizeof(cmd->argv[0]));
strcpy(cmd->name, cmd->argv[0]);
cmd->argv[cmd->argc] = NULL;
if(!strcmp(cmd->argv[cmd->argc - 1], "&"))
{
cmd->background = 1;
cmd->argv[cmd->argc - 1] = NULL;
cmd->argc = cmd->argc - 1;
}
else
cmd->background = 0;
return 1;
}
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Prints all members of the command line structure
* @param[in] cmd The commandline structure to print.
* @return void
*/
////////////////////////////////////////////////////////////////////////////////
void printCommand(struct command_t *cmd)
{
printf("------Printing Command------------------\n");
printf("cmd Name = %s\nnum Args = %i\n", cmd->name, cmd->argc);
int i;
for(i=0; i<cmd->argc; i++)
printf("\t%i. %s\n", i, cmd->argv[i]);
printf("Output: %s\n", cmd->output);
printf("Input: %s\n", cmd->input);
printf("isBackground: %i\n", cmd->background);
printf("----------------------------------------\n");
}
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Stores the command in history.
* @param[in] cLine The full command to store in histoyr.
* @param[in] history To store the history commands in.
* @param[in] history_size The current size of the history.
* @return The new size of the history.
*/
////////////////////////////////////////////////////////////////////////////////
int storeHistory (char * cLine, char history[MAX_HISTORY][BUFFER_LENGTH],
int history_size)
{
if( history_size < MAX_HISTORY )
strcpy(history[history_size++], cLine);
else
{
int i;
for(i=1; i < MAX_HISTORY; i++)
strcpy(history[i-1], history[i]);
strcpy(history[MAX_HISTORY-1], cLine);
}
return history_size;
}
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Prints stored history.
* @param[in] history The history to print.
* @param[in] history_size The num of commands to print from history
* @return void
*/
////////////////////////////////////////////////////////////////////////////////
void printHistory (char history[MAX_HISTORY][BUFFER_LENGTH], int history_size)
{
int i;
for(i=0; i<history_size; i++)
printf("%03i %s", i, history[i]);
}
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Executes a file with arguments.
* @param[in] fullpath the fullpath including filename of the file to execute.
* @param[in] cmd The command arguments.
* @return EXIT_FAILURE if unsuccessful.
*/
////////////////////////////////////////////////////////////////////////////////
int execute(char * fullpath, struct command_t * cmd,
char history[MAX_HISTORY][BUFFER_LENGTH], int history_size)
{
// Print History
if ( !strcmp(cmd->argv[0], "history") )
{
printHistory(history, history_size);
return EXIT_SUCCESS;
}
// CD
else if ( !strcmp(cmd->argv[0], "cd" ) )
{
chdir(cmd->argv[1]);
return EXIT_SUCCESS;
}
// General Execute Case
else
{
pid_t pid = fork();
// Error Handling Condition
if( pid < 0 )
{
printf("Unable to fork()\n");
}
// In the Child Process
else if ( pid == 0 )
{
#ifdef VERBOSE
printf( "CHILD: happy birthday to me!\n" );
#endif
// File Redirection
int bak, bak_in, new_in, fd;
// ==> Output
if(cmd->hasOutput)
{
fflush(stdout);
bak = dup(1);
fd = open(cmd->output, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0)
{
fprintf(stderr, "open error: %d [%s]\n", errno, strerror(errno));
exit(1);
}
dup2(fd, 1);
close(fd);
}
// --> Output
if(cmd->specialOutput)
{
fflush(stdout);
bak = dup(1);
fd = open(cmd->output, O_WRONLY | O_APPEND, 0600);
if (fd < 0)
{
fprintf(stderr, "open error: %d [%s]\n", errno, strerror(errno));
exit(1);
}
dup2(fd, 1);
close(fd);
}
// <== Input
if(cmd->hasInput)
{
fflush(stdin);
bak_in = dup(0);
new_in = open(cmd->input, O_RDONLY);
dup2(new_in, 0);
close(new_in);
}
// Execute the Command
execv(fullpath, cmd->argv);
// Close File Redirection
// @note - not sure this code is ever run
if(cmd->hasOutput)
{
fflush(stdout);
dup2(bak, 1);
close(bak);
}
// --------------------
if(cmd->hasInput)
{
fflush(stdin);
dup2(bak_in, 0);
close(bak_in);
}
/* if the execv() sys call works, code should never get here */
perror( "execv() failed" );
return EXIT_FAILURE;
}
// In the Parent Process
else
{
#ifdef VERBOSE
printf( "PARENT: my child's pid is %d\n", pid );
#endif
/* wait for the child to terminate */
int status;
if( cmd->background )
{
waitpid( -1, &status, WNOHANG ); /* NOBLOCKING */
printf( "[process running in background with pid %i]\n", pid );
}
else
waitpid( -1, &status, 0 ); /* BLOCKING */
#ifdef VERBOSE
printf( "PARENT: child %d terminated...", child_pid );
if ( WIFSIGNALED( status ) ) /* core dump or kill or kill -9 */
{
printf( "abnormally\n" );
}
else if ( WIFEXITED( status ) ) /* child called return or exit() */
{
int rc = WEXITSTATUS( status );
printf( "successfully with exit status %d\n", rc );
}
#endif
}
}
return EXIT_FAILURE;
}
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Prints the current directory.
* @return void
*/
////////////////////////////////////////////////////////////////////////////////
void printCD()
{
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) != NULL)
printf("\nCurrent working dir: %s/\n", cwd);
else
perror("getcwd() error");
}