Created
November 13, 2017 04:14
-
-
Save SibTiger/b7518981e5349d02f39ec3e7fd3f64ed to your computer and use it in GitHub Desktop.
Operating Systems: Simulation of a Shell Environment [with Pipes]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ============================================================================= | |
// ----------------------------------------------------------------------------- | |
// ============================================================================= | |
// Programmer: Nicholas Gautier | |
// Class: CS3513; Operating Systems | |
// Assignment #: 1 1/2 (optional) | |
// Due Date: 28.September.2017 | |
// Instructor: Dr. Zhao | |
// Description: This program allows the user to interact with a basic shell | |
// environment and executes external commands at the user's | |
// request. This shell environment is very basic and offers | |
// very limited functionality. | |
// NOTES: This program was originally developed under the Windows | |
// environment, however due to limitations - this was | |
// expeditiously converted to work with *nix environment. | |
// The reason for this change-over was how CreateProcess() and | |
// fork() works. fork() copies the program and furthers the | |
// execution as if nothing really changed (visually), | |
// CreateProcess() however starts a brand new process without | |
// copying the main instance source and furthering the execution. | |
// Hacks can be made to emulate this behavior, but this may not | |
// be suitable for class material. Thus, we will have to shift | |
// to work with Linux and expeditiously get this program to work | |
// given that it took several days already.... | |
// | |
// VERSION: 1.1 | |
// Standard execution has now been formed into its own function, | |
// thus it is no longer in main(). | |
// Trimmed spaces from user input from the beginning and end of the | |
// input. | |
// Added support for pipes! Mario would be pleased! | |
// | |
// Credits: | |
// Dr. Zhao for resolving how the PATH data is stored internally within the | |
// program. | |
// Kyle Ressel for helping to resolve how PATH data is stored by the | |
// further revisements of improvements, also helping to get the | |
// execl() statement to work dynamically. | |
// Return Codes: | |
// 0 = Successful | |
// 1 = %PATH% Environment System Variable not obtained | |
// ============================================================================= | |
// ----------------------------------------------------------------------------- | |
// ============================================================================= | |
#pragma region Online Resources - HELP ME | |
// printf special formatting: | |
// https://www.codingunit.com/printf-format-specifiers-format-conversions-and-formatted-output | |
// Passing and returning char's: | |
// https://www.quora.com/How-can-I-pass-a-string-as-an-argument-to-a-function-and-return-a-string-in-C | |
// strtok example: | |
// http://www.cplusplus.com/forum/general/13058/ | |
// strtok_s [Microsoft's thread-safe version]: | |
// https://msdn.microsoft.com/en-us/library/ftsafwz3.aspx | |
// Time features | |
// https://www.tutorialspoint.com/c_standard_library/time_h.htm | |
// fgets [user input] | |
// https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm | |
// _stricmp() [Windows] | |
// https://stackoverflow.com/questions/17899993/compare-strings-after-converting-to-lower-case-in-c | |
// https://msdn.microsoft.com/en-us/library/k59z8dwe(v=vs.80).aspx | |
// Page 118 - OS book | |
#pragma endregion | |
#pragma region Libraries | |
#include <stdio.h> | |
#include <stdlib.h> // Holds malloc (if we ever need it), system(), | |
#include <stddef.h> // Holds are NULLPTR | |
#include <string.h> // for functions: strtok(), strtok_s() - MICROSOFT's 'THREAD SAFE' version, strcmp() | |
#include <time.h> // Used for the tm struct functionality; for prompt | |
#include <ctype.h> // used for tolower() | |
#include <unistd.h> // Used for fork() and pid_t | |
#include <stdbool.h> // Because I was spoiled in C[++ | #]; give me bool datatypes! | |
#pragma endregion | |
#pragma region Macros | |
#define _NAME_ "Sea Shell" // Program name | |
#define _VERSION_ "1.1" // Program version | |
#define _INPUT_MAX_LENGTH_ 255 // Entire input limit; used used for ROW limit in the 2D Array's | |
#pragma endregion | |
#pragma region Function Prototypes | |
char *FetchEnvironmentVariable(char *[]); // Retrieve the System Environment variables | |
void BootUpScreen(); // Display bootup message | |
void WelcomeScreen(); // Display the Welcome Screen | |
void ExitingScreen(); // Display the terminating message | |
void PromptMSG(); // Display the Prompt message before STDIN is provided. | |
char *PromptSTDIN(); // Allow the end-user to type a command into the shell | |
void ExecuteCommandPipe(char *, int, char [][_INPUT_MAX_LENGTH_]);// Provides functionality to process commands that contains pipes. | |
void ExecuteCommandStandard(char *, int, char [][_INPUT_MAX_LENGTH_]);// Provides functionality to process commands in standard form. | |
void RemoveSpaces(char*); // This will remove spaces from the given string. | |
#pragma endregion | |
// Main - Entry Point | |
// ------- | |
// This function is the starting or entry-point of the program. | |
// Help for Main Arguments: https://stackoverflow.com/a/4176337 | |
int main(int argc, char **argv) | |
{ | |
// Declarations and Initializations | |
// --------------------------------- | |
char *pathRaw = FetchEnvironmentVariable("PATH"); // Fetch %PATH% (or $PATH) and store it directly to the pointer of chars | |
char *pathToken = NULL; // Used for parsing through the PATH system environment variable | |
char storedToken[50][_INPUT_MAX_LENGTH_]; // Stores the entire tokens in an array for easy access. | |
// This will allow the program to access each directory easily when invoking | |
char tokenDelim[] = ":;"; // Specifies the delimiters for the tokens | |
// Delim: : = Linux's $PATH, ; = Windows's %PATH% or $PATH | |
// This will cover both fields as an 'or'. Thus : OR ; but never both | |
int parsingCounter = 0; // This will allow the program to pass through iterations and record storing. | |
// Record storing will be for caching the value held within this variable to | |
// another variable, such as 'parsingCounter_Result'. | |
int parsingCounter_Result = 0; // This variable can help to determine the maximum indexes used in the | |
// variable 'parsingCounter'. | |
char *userSTDIN; // This pointer of char's variable will be used for capturing user input. | |
bool detectionPipe = false; // This variable is merely a flag of wither or not a pipe was detected in the | |
// in the user's input. | |
// TRUE = Pipe was detected and the program will treat it as such | |
// FALSE = No pipe was detected; normal execution will proceed. | |
// ---------- | |
// Provide the user with a bootup message | |
BootUpScreen(); | |
// Check to make sure that pathRaw contains data we need; else - terminate this program abruptly. | |
if (!strcmp(pathRaw, "ERROR")) | |
{ | |
printf("<!> OPERATION HALTED <!>\n"); | |
printf("===============================\n\n"); | |
printf("UNABLE TO SUCCESSFULLY OBTAIN SYSTEM ENVIRONMENT %PATH%!\nPlease complain to the developer of this program to make better software.\n\n"); | |
return 1; // Failure to obtain %PATH% - KILL PROGRAM | |
} // if: %PATH% not retrieved successfully | |
else | |
{ | |
pathToken = strtok(pathRaw, tokenDelim); // Immediately capture the first token and have strtok() cache the initial string. | |
// NOTE: This is NOT thread safe and stores data in a static pointer. | |
while(pathToken != NULL) | |
{ | |
strcpy(storedToken[parsingCounter], pathToken); // Duplicate the token to an array for access for later | |
// As required for this assignment, provide output in the following way: | |
// Index - of the array we are going to store into. | |
// Path - Store data within based on the index of the array | |
// Token - Token that has been generated from the PATH system environment. | |
printf("Path Index: %d\n\t\tPath Directory: %s\n\t\tGenerated Token: %s\n", parsingCounter, storedToken[parsingCounter], pathToken); | |
parsingCounter++; // Update the parsingCounter to the next index. | |
pathToken = strtok(NULL, tokenDelim); // Shift to the next token | |
} // Scan through the %PATH% and generate tokens | |
printf("\n"); // Visual purposes only | |
// Save the final index for later | |
parsingCounter_Result = parsingCounter; | |
} // else | |
// Provide the user with the basic information about this shell | |
WelcomeScreen(); | |
// Never-Ending loop | |
do | |
{ | |
//Provide the prompt message | |
PromptMSG(); | |
// Get user-feedback | |
userSTDIN = PromptSTDIN(); | |
// As requested for this assignment, provide what the user entered into the program | |
printf("\nRequested Command: %s\n\n", userSTDIN); | |
// Terminate the program by request? | |
if ((strcasecmp(userSTDIN, "exit") == 0) || | |
(strcasecmp(userSTDIN, "quit") == 0)) | |
break; // Leave the loop and start the termination process | |
else | |
{ | |
// Check for pipes | |
// ------------------------------------- | |
// https://stackoverflow.com/a/7040526 | |
// I had trouble trying to understand how to scan | |
// a pointer of Char's. This apparently does the | |
// trick. In C++, I could have merely treated | |
// a 'string' datatype as an array of Char's and scan | |
// the array with that behavior, though here - it is | |
// different. | |
for (parsingCounter = 0; userSTDIN[parsingCounter]; parsingCounter++) | |
{ | |
if (userSTDIN[parsingCounter] == '|') | |
{ | |
printf("Pipe was detected!\n"); // Display on the terminal buffer | |
// that the pipe was detected | |
detectionPipe = true; // Update the pipe flag | |
break; // Get out of the loop; minimize wasted CPU cycles | |
} // if: Found pipe char | |
} //for: Detect pipe | |
if (detectionPipe) | |
ExecuteCommandPipe(userSTDIN, parsingCounter_Result, storedToken); | |
else | |
ExecuteCommandStandard(userSTDIN, parsingCounter_Result, storedToken); | |
// If the pipe flag was previously set, reset it. | |
if (detectionPipe) | |
detectionPipe = !detectionPipe; | |
} // if: Run Execute | |
} while (1); | |
// Terminating process | |
ExitingScreen(); | |
// Terminate the software | |
return 0; | |
} // main() | |
// Fetch Environment Variable | |
// This function is designed to retrieve a variable from the host system | |
// and return the data as requested. | |
// ------- | |
// Parameters: | |
// envVar[] - Char | |
// This string will state what environment variable is being requested. | |
// ------- | |
// Output: | |
// String (char[]) | |
// Returns the results from the system environment variable as a string (or char[]). | |
// If failure, the return will be 'ERROR'. Which in this case, the program is unable to work. | |
// ------- | |
char *FetchEnvironmentVariable(char *envVar[]) | |
{ | |
// Declaration and Initializations | |
// ---------------- | |
char *buffer = NULL; // Capture the data into the buffer-pointer | |
size_t sizeNum = 0; // Retains the number of elements | |
// ---------------- | |
buffer = getenv(envVar); // Capture the system environment variable data | |
if (buffer != NULL) // Check to see if the data is valid (failed or passed) | |
return buffer; | |
else | |
return "ERROR"; | |
} // FetchPathEnvironment() | |
// Remove Spaces | |
// ---------------------------- | |
// This function is designed to take the input provided within the arguments | |
// and then remove any spaces present within the string. | |
// ------ | |
// Parameters: | |
// string :: char* | |
// String in which spaces will be removed. | |
void RemoveSpaces(char* string) | |
{ | |
// If incase the string is NULL'ed, then merely leave this function. | |
// There is merely nothing that can be done. | |
if (string == NULL) | |
return; | |
// Remove the heading white spaces | |
// --------------------------------------------- | |
int index = 0; | |
int i; | |
while (string[index] == ' ') | |
index++; | |
// Shift all trailing characters to the left | |
i = 0; | |
while(string[i + index] != '\0') | |
{ | |
string[i] = string[i + index]; | |
i++; | |
} // while | |
string[i] = '\0'; // Terminate the string as required | |
// Remove the trailing white spaces | |
// --------------------------------------------- | |
i = 0; | |
index = -1; | |
while (string[i] != '\0') | |
{ | |
if (string[i] != ' ') | |
index = i; | |
i++; | |
} // while | |
string[index + 1] = '\0'; | |
} // RemoveSpaces() | |
// Execute Command [STANDARD] | |
// ---------------------------- | |
// This function will evaluate the commands giving by the user and will process them accordingly. | |
// ------- | |
// Parameters: | |
// userSTDIN | |
// End-user provides the requested commands | |
// maxPathList | |
// The maximum column length of the path list | |
// Path[COLUMNS][ROWS], for example. | |
// sysPath[][] | |
// Filtered path; ready to be parsed. | |
// ------- | |
void ExecuteCommandStandard(char *userSTDIN, int maxPathList, char sysPath[][_INPUT_MAX_LENGTH_]) | |
{ | |
// Declarations and Initializations | |
// --------------------------------- | |
char path[_INPUT_MAX_LENGTH_]; // This variable is dedicated to the execl() for capturing the path | |
// and the binary. | |
int parsingCounter_Result = 0; // This variable can help to determine the maximum indexes used in the | |
// variable 'parsingCounter'. | |
char *execCMD; // This variable will allow the end-user to execute the specific command. | |
char *execArgs; // This allows the user to pass arguments to the array of char's | |
// in which allows the user to issue arguments to perform special | |
// operations that could not be normally performed without. | |
// the execl() or other means (or actions). | |
int parsingCounter = 0; // Setup the parser; used to scan each iteration of the char array | |
// --------------------------------- | |
pid_t processID = fork(); // Create a new child process | |
// Check to see if the fork() operation succeeded or if it failed | |
if (processID < 0) | |
{ // Were we able to fork a new process? Check child state | |
fprintf(stderr, "Unable To Generate Child; Fork Failure!\n"); | |
} // if: Fork() status | |
else if (processID == 0) | |
{ // Child Process: | |
// Fetch the initial command that the user requested for | |
execCMD = strtok(userSTDIN, " "); | |
// Try to retrieve parameters issued by the end-user - - if any was provided | |
execArgs = strtok(NULL, ""); | |
// Remove all spaces within the commands and arguments. | |
RemoveSpaces(execCMD); | |
RemoveSpaces(execArgs); | |
// DEBUG STATE | |
printf("execCMD = %s\nexecArgs = %s\n\n", execCMD, execArgs); | |
// ----- | |
do | |
{ | |
strcpy(path, sysPath[parsingCounter]); // Capture path into a cache variable | |
strcat(path, "/"); // Add the UNIX filesystem '/' directory char. | |
strcat(path, userSTDIN); // Add the user's requested command to the cache variable | |
// ---- | |
printf("%s\n", path); // Display the path before execution | |
// ---- | |
// Execute the command as requested | |
execl (path, execCMD, execArgs, (char *)NULL); | |
parsingCounter++; // Update to the next index | |
} while (parsingCounter <= maxPathList); // Loop until we reach the max index limit | |
printf("Invalid External or Internal command!\n"); | |
exit(0); | |
} // if: Child Process | |
else // Parent process; wait for child (fork()) to finish process. | |
wait(NULL); | |
} // ExecuteCommandStandard() | |
// Execute Command [PIPE] | |
// ---------------------------- | |
// This function is designed to handle pipes and process them accordingly. Though, the functionality may be limited. | |
// NOTE: This function is VERY limited and can not be used in combination of commands. | |
// This function supports: CMD[1] | CMD[2] | |
// but: CMD[1] | CMD[2] | CMD[3] | CMD[N] | CMD[N+1] | |
// In addition, the first command MUST ALWAYS have an argument. This is due to a limitation with strtok(). | |
// More time and motivation is required to enforce a tighter checking to resolve this limitation. | |
// ------- | |
// Parameters: | |
// userSTDIN | |
// End-user provides the requested commands | |
// maxPathList | |
// The maximum column length of the path list | |
// Path[COLUMNS][ROWS], for example. | |
// sysPath[][] | |
// Filtered path; ready to be parsed. | |
// ------- | |
void ExecuteCommandPipe(char *userSTDIN, int maxPathList, char sysPath[][_INPUT_MAX_LENGTH_]) | |
{ | |
// Declarations and Initializations | |
// --------------------------------- | |
char path[_INPUT_MAX_LENGTH_]; // This variable is dedicated to the execl() for capturing the path | |
// and the binary. | |
int parsingCounter_Result = 0; // This variable can help to determine the maximum indexes used in the | |
// variable 'parsingCounter'. | |
// FIRST CMD | |
char *execCMD1; // This variable will allow the end-user to execute the first specific command. | |
char *execArgs1; // This allows the user to pass arguments to the array of char's | |
// in which allows the user to issue arguments to perform special | |
// operations that could not be normally performed without. | |
// the execl() or other means (or actions). | |
// SECOND CMD | |
char *execCMD2; // This variable will allow the end-user to execute the first specific command. | |
char *execArgs2; // This allows the user to pass arguments to the array of char's | |
// in which allows the user to issue arguments to perform special | |
// operations that could not be normally performed without. | |
// the execl() or other means (or actions). | |
int parsingCounter = 0; // Setup the parser; used to scan each iteration of the char array | |
pid_t processID; // Stores the child process ID | |
// --------------------------------- | |
// Lets go ahead and parse the command now; thus they will be ready for use when needed | |
// Capture the first command before the pipe | |
execCMD1 = strtok(userSTDIN, "|"); | |
execCMD2 = strtok(NULL, ""); | |
// ---- | |
execCMD1 = strtok(execCMD1, " "); | |
execArgs1 = strtok(NULL, ""); | |
// ---- | |
execCMD2 = strtok(execCMD2, " "); | |
execArgs2 = strtok(NULL, ""); | |
// Remove all spaces within the commands and arguments. | |
RemoveSpaces(execCMD1); | |
RemoveSpaces(execArgs1); | |
RemoveSpaces(execCMD2); | |
RemoveSpaces(execArgs2); | |
// ---- | |
// DEBUG STATE | |
printf("execCMD1 = %s\nexecArgs1 = %s\n\n", execCMD1, execArgs1); | |
printf("execCMD2 = %s\nexecArgs2 = %s\n\n", execCMD2, execArgs2); | |
// ----- | |
int pipeUniDirection[2]; // Store two ends of the first pipe. | |
int childFork1; // Child fork ID | |
int childFork2; // Child fork ID | |
int status; // Status ID | |
pipe(pipeUniDirection); | |
// Check to make sure that the pipe()'s are ready | |
if (pipeUniDirection == -1) | |
{ | |
fprintf(stderr, "Failure to establish pipe()!\n"); | |
return; // Leave from this function abruptly. | |
} // if: Pipe creation failure | |
if ((childFork1 = fork()) == 0) | |
{ | |
close(1); // Close the STDOUT from the first command | |
dup(pipeUniDirection[1]); // Create an alias [clarification needed] | |
close (pipeUniDirection[0]); // Close the Pipe | |
do | |
{ | |
strcpy(path, sysPath[parsingCounter]); // Capture path into a cache variable | |
strcat(path, "/"); // Add the UNIX filesystem '/' directory char. | |
strcat(path, execCMD1); // Add the user's requested command to the cache variable | |
// ---- | |
printf("%s\n", path); // Display the path before execution | |
// ---- | |
// Execute the command as requested | |
execl (path, execCMD1, execArgs1, (char *)NULL); | |
parsingCounter++; // Update to the next index | |
} while (parsingCounter <= maxPathList); | |
_exit(0); | |
}// if: Child ONE | |
if ((childFork2 = fork()) == 0) | |
{ | |
close(0); // Close the STDIN [user interaction] | |
dup(pipeUniDirection[0]); // Create an alias | |
close(pipeUniDirection[1]); // Close the Pipe | |
do | |
{ | |
strcpy(path, sysPath[parsingCounter]); // Capture path into a cache variable | |
strcat(path, "/"); // Add the UNIX filesystem '/' directory char. | |
strcat(path, execCMD2); // Add the user's requested command to the cache variable | |
// ---- | |
printf("%s\n", path); // Display the path before execution | |
// ---- | |
// Execute the command as requested | |
execl (path, execCMD2, execArgs2, (char *)NULL); | |
parsingCounter++; // Update to the next index | |
} while (parsingCounter <= maxPathList); | |
_exit(0); | |
} // if: Child TWO | |
// Revert pipes | |
close(pipeUniDirection[0]); // Close the read function of the pipe | |
close(pipeUniDirection[1]); // Close the write function of the pipe | |
do | |
{ | |
// Wait for the two child processes to terminate | |
processID = wait(&status); | |
if (processID == childFork1) | |
childFork1 = 0; | |
if (processID == childFork2) | |
childFork2 = 0; | |
} while(childFork1 != childFork2 && processID != -1); | |
} // ExecuteCommandPipe() | |
// Welcome Screen | |
// ------- | |
// This function will provide basic information to the user about the program. Adjacent to the Command Shell (and PowerShell) | |
void WelcomeScreen() | |
{ | |
printf("%s [version: %s]\nOn the C shore\n\n", _NAME_, _VERSION_); | |
} // WelcomeScreen() | |
// Boot Up Screen | |
// ------- | |
// This function will merely display the bootup message on the terminal screen. | |
void BootUpScreen() | |
{ | |
printf("\nStarting up %s. . .\n\n", _NAME_); | |
} // BootUpScreen() | |
// Exiting Screen | |
// ------- | |
// This function will display a message in regards to the shell terminating | |
void ExitingScreen() | |
{ | |
printf("\n\n%s is closing. . .\n", _NAME_); | |
} // ExitingScreen() | |
// Prompt Message | |
// ------- | |
// With this function, this can provide the user basic information that would matter most. | |
void PromptMSG() | |
{ | |
// Declarations and Initializations | |
struct tm * timeDateObj; // Create an instance of the 'tm' object | |
char timeDateBuffer[100]; // Declare a buffer for the time\date formatting | |
char *programWorkingDirectory; // Create a new variable for holding the program's WD | |
// Gather information as needed | |
// =========================== | |
// Program's Working Directory | |
programWorkingDirectory = getcwd(NULL, 0); // Cache the program's working directory. | |
if (programWorkingDirectory == NULL) // Did 'getcwd()' failed? | |
programWorkingDirectory = "<ERROR_GET_WD_FAILED>"; // Provide an error message, but DON'T kill the program. | |
// =========================== | |
// Date and Time | |
// I wasted over an hour on this, if something breaks - just nuke this feature. | |
// https://stackoverflow.com/questions/23024862/localtime-s-in-c-using-microsoft-visual-studio-2013 | |
// https://stackoverflow.com/questions/13658756/example-of-tm-use | |
// http://www.cplusplus.com/reference/ctime/strftime/ | |
// https://www.tutorialspoint.com/c_standard_library/time_h.htm <-- USING TM DIRECTLY VIA OBJECT | |
time_t rawTime; // We will need this var. for the next step | |
time(&rawTime); // Capture current date in seconds as since 1970's | |
timeDateObj = localtime(&rawTime); // Populate the time\date data in 'tm' | |
strftime(timeDateBuffer, 100, "[%a.%e.%b.%Y %H.%M:%S]", timeDateObj); // Perform special formatting to time and date scheme. | |
// =========================== | |
// Display the Prompt Message: Date and Time | |
printf("%s ", timeDateBuffer); | |
// Display the Prompt Message: WD | |
printf("[%s]\n", programWorkingDirectory); | |
} // PromptMSG() | |
// Prompt STDIN [keyboard] | |
// ------- | |
// This function allows the end-user to type a command into the shell for evaluation and execution. | |
// ------- | |
// Output | |
// char* | |
// Command requested by the end user | |
char *PromptSTDIN() | |
{ | |
char *interactionSTDIN = (char *) malloc(sizeof(char) * _INPUT_MAX_LENGTH_); // Used for storing user input | |
printf(">>>>> "); // This should help indicate that we are waiting for user interaction | |
fgets(interactionSTDIN, _INPUT_MAX_LENGTH_, stdin); // Capture user input | |
interactionSTDIN[strlen(interactionSTDIN) -1] = '\0'; | |
// Report back the requested command | |
return interactionSTDIN; | |
} // PromptSTDIN() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment