1

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)
B_Ran
  • 39
  • 1
  • 7
  • Is your `rawInput` null-terminated? The segfault is not introduced by `fgets` but by something else – Eugene Sh. May 03 '19 at 17:36
  • I don't see a problem with that `fgets()` call. The problem is probably somewhere else. Once you do something that causes undefined behavior, it can cause misbehavior anywhere else in the program. – Barmar May 03 '19 at 17:36
  • @EugeneSh. `fgets()` doesn't require the buffer to be null-terminated, or even initialized. – Barmar May 03 '19 at 17:37
  • @barmar but `strdup` does – Eugene Sh. May 03 '19 at 17:37
  • @EugeneSh. `fgets()` always adds a null terminator. – Barmar May 03 '19 at 17:37
  • @Barmar Ah, that's true – Eugene Sh. May 03 '19 at 17:38
  • You should free `inputDup` after you're done counting the tokens. You have a memory leak there. – Barmar May 03 '19 at 17:40
  • @Barmar I see where I'm leaking memory, thank you for the advice. I've been writing this code on MacOS and went to test on Ubuntu where I found the cross platform issue I've written the question about. On Ubuntu, I can confirm that my code goes no farther than the fgets() call. – B_Ran May 03 '19 at 17:48
  • @B_Ran out of the errors how do you will know the number of entries in your vector out of the function ? see my answer – bruno May 03 '19 at 17:51

1 Answers1

3

doing

 char** parse(void)
 {
   char* tokenArray[argCount];
   ...
   return tokenArray;
 }

you return the address of a local array, when you will read inside the behavior will be undefined

Do not place tokenArray in the stack, allocate it, so replace

 char* tokenArray[argCount];

by

char** tokenArray = malloc(argCount * sizeof(char *));

How do you will know the number of entries out of the function ?

Can be a good idea to allocate one entry more and place a NULL at the end, or to have argCount as an output variable


Note also your inputDup = strdup(rawInput); creates a memory leak, free it

bruno
  • 32,421
  • 7
  • 25
  • 37
  • Hey @bruno I see what you're getting at. This is definitely an issue I need to fix, if I'm following you correctly I will lose that information once I return from the function. Thank you. Currently on Ubuntu, I cannot get past the fgets() call. I appreciate and will heed your advice. – B_Ran May 03 '19 at 17:55
  • @B_Ran "_I cannot get past the fgets() call_" the *first* time you use _parse_ or you called it several times ? – bruno May 03 '19 at 17:58
  • the very first time I call parse(), I am unable to proceed past the fgets call. I've included an edit that shows sample output from both systems. All the best – B_Ran May 03 '19 at 17:59
  • @B_Ran what are you doing before to call _parse_ ? Probably you already 'destructed' the memory. Use _valgrind_ under ubuntu to execute your program. and BTW you said you have a warning when compiling, which one ? I encourage you to compile with the options `-pedantic -Wall -Werror` with _gcc_ – bruno May 03 '19 at 18:02
  • I've edited my post to include the entire source code. The program is very incomplete. The warning given to me when I compile is the same that you have; my local variable is being returned at the end of the function parse(). – B_Ran May 03 '19 at 18:20
  • @B_Ran with your code I have a problem because of the returned local array, if I replace `char* tokenArray[argCount];` by `char** tokenArray = malloc(argCount * sizeof(char *));` there is no problem (out of the memory leak) – bruno May 03 '19 at 18:37
  • 1
    I must apologize for being kind of stubborn. I understood what you were telling me before, but believed that because the error I was getting happened before your suggested change, there must be some other problem. I adopted your suggestion and now it works on both systems. I'm still unsure as to exactly why it fixed what it did, but I appreciate your help and multiple suggestions. All the best. – B_Ran May 03 '19 at 18:47