// CSE421 - Project 1 Part 1c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#define ASCII_TAB 0x09
#define ASCII_SPACE 0x20
//FUNCTION PROTOTYPES:
// Function to parse user input:
int parseInput(char* myString, char* &file, char** args, int maxArgs);
///////////////////////////////////////////////////////////////////////
int main()
{
int pid1; // PID of spawned child
int i; // temporary int
char inputBuffer[256]="\x00"; // buffer for user input
char* file; // pointer to a file name or path
char* args[64]; // pointer to an arguments array
while(true)
{
// Show prompt, get user input:
fputs("shell> ",stdout);
if(fgets(inputBuffer, 255, stdin) == NULL)
break;
// Strip the trailing newline character from the end of string:
inputBuffer[strlen(inputBuffer) - 1] = 0x00;
// Parse the user input, this will modifly the inputBuffer string as well as the file and args[] pointers
if(parseInput(inputBuffer, file, args,64) < 0)
{
fprintf(stderr,"Unable to parse command! Try again...\n");
continue;
}
// If user entered "exit" then break out of loop and exit.
if(strcmp(file,"exit")==0)
break;
// Spawn a child to attempt to run the command:
pid1=fork();
if(pid1 < 0)
{
perror("fork");
return -1;
}
if(pid1 != 0) //PARENT
{
waitpid(pid1,NULL,0);
}
else //CHILD
{
// The file and args pointers should have been set by parseInput (they point into the modifed
// user input string), so they can be fed directly into execvp:
i=execvp(file, args);
if(i < 0)
{
perror("execvp");
return -1;
}
exit(0);
}
}
return 0; //redundant & unneccessary?
}
//////////////////////////////////////////////////////////////////////////////
// parseInput function:
//
// PARAMETERS:
// myString - pointer to an existing input string (this string WILL get modified)
// file - address of an existing pointer. the pointer will be set to point to the first argument in myString,
// presumably a filename or path.
// args - address of an existing array of pointers. These pointers will be set to point to each individual
// argument in the input string.
// maxArgs - The number of pointers in the existing array of pointers passed into this function. Represents the
// maximum number of arguments that can be parsed.
//
// DESCRIPTION:
// Takes a null terminated string pointed to by myString, and modifies this string. First it removes any leading and trailing
// spaces/tabs from the string, then replaces any consecutive spaces and/or tabs with a single space. Then changes each
// single space to a null byte, effectively breaking the string into several smaller strings which represent the
// individual arguments of the original string. It then sets the file and args[] pointers to point to different locations inside
// the myString buffer in the following manner:
//
// file - will be set to point to the first argument
// args[0] - will point to the same string as file, but if file contains a '/' symbol, args[0] will point to the first
// character after the LAST '/' symbol. For example, if file == "/bin/ls",
// then args[0] will be just "ls"
// args[1]-args[?] - these point to each subsequent argument in the buffer
//
// RETURNS 0 for success, and <0 for failure
///////////////////////////////////////////////////////////////////////////////
// EXAMPLE:
// myString=" /bin/ls -l -a /home "
// The myString string would be changed to this (where # is a NULL byte):
// myString="/bin/ls#-l#-a#/home#"
// ^ ^ ^ ^ ^
//
// The ^ symbols above represent the pointed to locations of file, args[0], args[1], args[2], and args[3] respectively.
// And args[4] would be set to NULL.
//
///////////////////////////////////////////////////////////////////////////////
int parseInput(char* myString, char* &file, char** args, int maxArgs)
{
int i=0; // temp int
int j=0; //.temp int
int curArg=0; // current argument # being processed
file=NULL; // set the file pointer to be NULL (for good measure).
if(maxArgs<2) // args will need at least need 2 pointers, one for the command, the 2nd for the null term.
return -1;
args[0]=NULL; // set the first args[] pointer to be NULL, for good measure
// Remove any TRAILING spaces or tabs from myString:
i=strlen(myString);
while(i>0 && (myString[i-1]==ASCII_SPACE || myString[i-1]==ASCII_TAB))
{
myString[i-1]=0;
i--;
}
// Remove any LEADING spaces or tabs from myString:
while(myString[0] == ASCII_SPACE || myString[0] == ASCII_TAB)
myString++; // increase the pointer by 1 byte, so as to "lose" the 1st char of the string.
// if after removeing leading/trailing spaces the buffer is empty, then theres nothing to do:
if(strlen(myString) == 0)
return -1;
//
// i is the "read index", j is the "write index"
// the string is read, processed, and written back into the same location:
// It reads through myString 1 char at a time. If a series of spaces/tabs is encountered, they're replaced with
// a SINGLE SPACE.
i=j=0;
while(myString[i] != 0)
{
if(myString[i] == ASCII_SPACE || myString[i] == ASCII_TAB) // if the char currently being READ is a space,
{
if(myString[j-1] == ASCII_SPACE) // check to see if the last char WRITTEN was also a space,
{
i++; // if so, then continue reading the next char
continue;
}
else // otherwise, write a single space:
{
myString[j++]=ASCII_SPACE;
i++;
continue;
}
}
myString[j++]=myString[i++]; // if the char being read is not a space, then just
// write it.
}
myString[j]=0; // add the null termination to the new myString string
// position i so that myString[i-1] is the LAST CHAR of the first argument
i=0;
while(myString[i] != 0 && myString[i] != ASCII_SPACE)
i++;
// position i so that IF the first argument is a path name such as "/bin/ls" then myString[i] points to just "ls".
while(i>0)
{
if(myString[i-1] == '/')
{
break;
}
i--;
}
args[curArg++] = &(myString[i]); //set args[0] to point to just the file name, ie "ls" not "/bin/ls'
// position i again so that myString[i] is the char AFTER the first argument (which will be either a space or a null byte.)
while(myString[i]!=0 && myString[i] != ASCII_SPACE)
i++;
// Change each space in myString to a null byte and set the args[] pointers so that they point to each individual
// argument string.
while(myString[i]!=0 && curArg < (maxArgs-1))
{
if(myString[i]== ASCII_SPACE) // a space can only come BETWEEN 2 arguments, so if one is encountered,
// the NEXT char must be the first char of a new argument:
{
args[curArg++]=&(myString[i+1]); // set the next args[] pointer
myString[i]=0; // null terminate the previous arg string
}
i++;
}
args[curArg]=NULL; // NULL-terminate the args[] array.
// set the file name pointer to first argument which should be the entire file name & maybe path, ie "/bin/ls"
file=myString;
return 0;
}