Skip to content

Instantly share code, notes, and snippets.

@SibTiger
Created November 13, 2017 04:14
Show Gist options
  • Save SibTiger/b7518981e5349d02f39ec3e7fd3f64ed to your computer and use it in GitHub Desktop.
Save SibTiger/b7518981e5349d02f39ec3e7fd3f64ed to your computer and use it in GitHub Desktop.
Operating Systems: Simulation of a Shell Environment [with Pipes]
// =============================================================================
// -----------------------------------------------------------------------------
// =============================================================================
// 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