I'm writing a simple unix shell program and am having some trouble with a segmentation fault caused by an fgets() call. With the call, I'm attempting to get user input from the command line and save that for further use. Should be simple. On MacOS the code compiles with one warning but executes fine. When testing on my Ubuntu virtual machine, I'm getting a seg fault and I've narrowed it down currently to a specific fgets() call.
I'm not too familiar with DDD, but I was able to use that and some some simple print statements to determine its the fgets() call giving me trouble. I've checked that the pointer I'm assigning the call to has been properly allocated. Although my two machines are running different versions of gcc, I'm confused why I'm getting the segfault on only one system as opposed to both.
Below is my source code, and the specific function I'm having issues with is the parse() function. This is not complete or finished code, but where I'm at with it is hoping to continually prompt a user for input, receive input from the command line, save this input and split it into tokens to pass to execute() for further use. I have been advised and understand I currently have memory leaks and scope errors. I'm still unsure why on Ubuntu, I get the segmentation fault the very first time I call parse() and make it to the fgets() call inside the function.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PROMPT "MyShell> "
#define MAX_SIZE 256
#define EXIT_CMD "exit"
/**
@brief Takes a pointer as an argument and checks whether or not it is NULL
(hasn't been properly allocated in memory). If the pointer is NULL,
behavior is undefined, so an error message is displayed to the user
and the program is terminated.
*/
void validateMemoryAllocation(char* pointer)
{
if (pointer == NULL)
{
printf("%s", "Fatal Error: Failed to allocate memory to save command input. Exiting...\n");
exit(0);
}
}
/**
@brief Fork a child to execute the command using execvp. The parent should wait for the child to terminate
@param args Null terminated list of arguments (including program).
@return returns 1, to continue execution and 0 to terminate the MyShell prompt.
*/
int execute(char **args)
{
if (strcmp(args[0], "exit") == 0) // Check for exit command.
{
printf("Exit received. Terminating MyShell...\n");
return 1; // Return to main with exit value to terminate the program.
} else // Not exit command, proceed attempting to execute.
{
}
return 0; // Return 0 and continue MyShell.
}
/**
@brief gets the input from the prompt and splits it into tokens. Prepares the arguments for execvp
@return returns char** args to be used by execvp
*/
char** parse(void)
{
char *rawInput, *inputDup, *token;
int validCheck, argCount, i, newLineLocation;
/* Save the entire line of user input. */
rawInput = malloc(sizeof(char) * MAX_SIZE);
validateMemoryAllocation(rawInput);
fgets(rawInput, MAX_SIZE, stdin);
inputDup = strdup(rawInput); /* Duplicate the string for modification. */
/* First loop: Count number of total arguments in user input. */
argCount = 0;
while( (token = strsep(&inputDup, " ")) != NULL)
{
argCount++;
}
/* Create array to hold individual command arguments. */
char* tokenArray[argCount];
/* Second loop: save tokens as arugments in tokenArray. */
for (i = 0; i < argCount; i++)
{
token = strsep(&rawInput, " ");
tokenArray[i] = token;
}
/**
Before returning the arguments, trim the dangling new line
character at the end of the last argument.
*/
tokenArray[argCount - 1] = strtok(tokenArray[argCount - 1], "\n");
return tokenArray;
}
/**
@brief Main function should run infinitely until terminated manually using CTRL+C or typing in the exit command
It should call the parse() and execute() functions
@param argc Argument count.
@param argv Argument vector.
@return status code
*/
int main(int argc, char **argv)
{
int loopFlag = 0;
char** input;
/* Loop to continue prompting for user input. Exits with proper command or fatal failure. */
while (loopFlag == 0)
{
printf("%s", PROMPT); // Display the prompt to the user.
input = parse(); // Get input.
loopFlag = execute(input); // Execute input.
}
return EXIT_SUCCESS;
}
I'm expecting that the user input be saved to the string pointer rawInput, and this is the case on MacOS, but not on Ubuntu.
EDIT: If it is helpful, here is some sample output from both systems being used. I understand I have some memory leaks that I need to patch.
MacOS
D-10-16-18-145:a1 user$ ./myshell
MyShell> hello
MyShell> darkness
MyShell> my
MyShell> old
MyShell> friend
MyShell> exit
Exit received. Terminating MyShell...
D-10-16-18-145:a1 user$
Ubuntu
MyShell> hello
Segmentation fault (core dumped)