0

I'm currently making my own shell program. I have to keep a history of the last 10 commands the user inputs. Whenever the user enters a command that ISN'T a regular command (for example history, hi, fakecommand, etc.) it is placed in history. But whenever the user inputs a real command (example ls, ps, top, cat, etc.) it is not added to history.

I think it might have something to do with the execvp command, since I believe this command creates a fork and makes the child execute the command. But I'm not sure that it should do that since I put the command in history before I even execute execvp.

//A method which increments the histIndex
int incrementIndex(int index) {
    if (index != 9)
        return index+1;
    else
        return 0;
}

//A method which adds the current command to history
void updateHistory(char *history[], char command[], int histIndex) {

    history[histIndex] = realloc(history[histIndex], MAX_LENGTH);  //Allocating space
    strcpy(history[histIndex], command);    //Put the current command in history
}

int main(int argc, char *argv[]) {
//while true, do
while (1) {

    int pid = fork();       //fork, or start the first process
    if (pid != 0) {         //if pid is not 0, then this is the parent,
        wait(NULL);
    }

    else {                  //Otherwise, we have the child

        printf("%s> ", getenv("USER"));         //print out the user's name + >
        fgets(input, MAX, stdin);               //Get the users input
        strtok(input, "\n");                    //Take the entire line and set it to input

        updateHistory(history, input, histIndex);  //Updating history
        histIndex = incrementIndex(histIndex);

        if (strcmp(input, "exit")==0) {         //If the input is exit, then exit
            exit(0);
        }

        //Else if the current command is history, then print the last 10 commands
        else if (strcmp(input, "history")==0) {
            getHistory(history, histIndex);
        }

        //Otherwise, do
        else {
            numTokens = make_tokenlist(input, tokens);

            tokens[numTokens] = NULL;
            char cmd[MAX_LENGTH];
            strcpy(cmd, tokens[0]);
            execvp(cmd, tokens);
            printf("Error: %s not found.\n", cmd);
        }
    }
}
}
Logan
  • 1,047
  • 10
  • 33
  • the `updateHistory()` function results in a memory leak because it does not pass any existing memory allocation pointer in `history[]` to `free()` before making a new allocation. – user3629249 Feb 06 '16 at 18:23
  • please post the `incrementHistory()` function – user3629249 Feb 06 '16 at 18:25
  • after this line: `printf("Error: %s not found.\n", cmd)` insert the line: `exit(EXIT_FAILURE);` as you do not want the child to be acting like the parent. – user3629249 Feb 06 '16 at 18:29
  • @user I added the incrementIndex function. And I'm not sure what you mean by the memory leak. Are you saying I have to free the data before I realloc? – Logan Feb 06 '16 at 18:30
  • the updating of the history[] is being performed in the child process. The child process has a copy of the history[] array, so the parent will not see the update. – user3629249 Feb 06 '16 at 18:30
  • @user e0k actually answered that question below. I have fixed that incident. I'm just wondering about the memory leak now. – Logan Feb 06 '16 at 18:32
  • some of the logic errors can be corrected by not calling `fork()` until in the final `else` code block – user3629249 Feb 06 '16 at 18:32
  • the memory leak can be fixed by pre-allocating all the history[] pointers then NOT calling `malloc()` in the `updateHistory()` function. Of course, when processing a 'exit' command be sure to loop through the `history[]` array, passing each pointer to `free()` – user3629249 Feb 06 '16 at 18:36
  • @user so you're saying after I initialize the history array just use: for (int i=0; i<10; i++) { history[i] = malloc(MAX); } ? – Logan Feb 06 '16 at 19:05
  • that loop would be the initialization, Be sure to check (!=NULL) the returned value from each call to `malloc()` to assure the operation was successful – user3629249 Feb 06 '16 at 19:10
  • Consider using [`strdup()`](http://linux.die.net/man/3/strdup). It copies a string and allocates however much space is needed for it. You are not limited to some `MAX_LENGTH`, nor are you wasting space if you use less than that. It is also easy to use because it is only one call (instead of your `realloc()` and `strcpy()`). It `malloc`'s memory, so don't forget to `free` whatever it returns. `strdup` is not C standard library, but it is POSIX. See also [strcpy vs strdup](http://stackoverflow.com/questions/14020380/strcpy-vs-strdup). – e0k Feb 06 '16 at 20:08
  • Using `realloc` in `updateHistory()` shouldn't cause a memory leak. I am assuming you have initialized the `history[]` values to NULL. Calling [`realloc`](http://linux.die.net/man/3/realloc) on a NULL pointer will give you a new allocation (like `malloc`). So the first time you would be calling `realloc(NULL,...)` and getting a new allocation, and subsequent times resizing the same allocation. But you shouldn't have to resize the allocation to the same size over and over. If you want to use `strdup`, you'd have to `free` the old entry before storing the new one. Passing NULL to `free` is safe. – e0k Feb 06 '16 at 20:27
  • Before you edited it out, you were calling `free` on each `history[]` before `exit(0)`. You are terminating and the whole process will be cleaned up anyway, but it is good practice to always free your allocations. It will also prevent tools like [valgrind](https://en.wikipedia.org/wiki/Valgrind) from flagging it as a leak. (I would do it just for that.) – e0k Feb 06 '16 at 20:33

1 Answers1

2

Separate processes have their own separate memory space (unless you do something special like shared memory, etc.). Whatever updates to heap or stack structures you do in the child process (such as modifying the history), has no effect in the parent.

You are creating a child with fork(), then reading the user input in the child. The child updates its own copy of the history, which has no effect on whatever history the parent knows about. execvp() does not fork, it replaces the current process with the executed file. This replaces the entire child process, and you lose the history that you only updated in the child.

You also have children making children, which is probably not what you want, but explains why you think it is adding invalid commands to the history. (It is, but not correctly.) An illustration of the sequence of events:

    Parent
    ------
    fork()  ------>   Child
    wait()            -----
     ...              Read input
     ...              updateHistory()
     ...              exit if "exit"
     ...              print history if "history"
     ...              execvp()

                      (if execvp() succeeded, this child is consumed,
                      the executed file eventually terminates and the
                      parent stops waiting. If execvp() failed, we fall 
                      through back to the top of the while loop!)

                      fork()   --------->    Child's Child
                      wait()                 -------------
                      ...                    Read input
                      ...                    updateHistory()
                      ...                    exit if "exit"
                      ...                    print history if "history"
                      ...                    execvp()

The child's child has inherited the child's memory, so it knows about the updated history. This is why you think it is adding the failed commands to the history. It is, but it's actually much worse than that.

It seems that you should be reading input in the parent, updating history in the parent, then (given a valid command), fork off a child process to be consumed by execvp to run the command. Then let the parent wait for the child to finish. This way, the parent maintains the history. One primary purpose for forking a child in the first place is because execvp replaces the calling process. Since you want the parent to live on, you let it eat a child.

Try something like this (I'll leave it as abstract pseudocode):

    Parent
    ------
    Read input
    updateHistory()
    exit if "exit"
    print history if "history"
    if invalid, go back to [Read input]
    if valid:
        fork()  ------>   Child
        wait()            -----
        ...               execvp()
        ...     <-------  if successful, executable hopefully terminates
        ...     <-------  if failed, print error and exit
                          (either way, child ends)

    Parent goes back to [Read input]

Another thing worth mentioning, whenever you fork(), you should check for three possible return values: -1 (error in fork()), >0 (in parent process), and 0 (in child process).

e0k
  • 6,961
  • 2
  • 23
  • 30
  • Ok. So the reason a valid command isn't being added is because execvp replaces my current process with the memory of the child process before the history was updated? – Logan Feb 06 '16 at 05:25
  • `execvp` replaces **the entire child process**. You are only updating the child's memory when you `updateHistory()`. Then you call `execvp` and the child is consumed. The parent's history is unaffected because you never changed it. – e0k Feb 06 '16 at 05:29
  • That was a great explanation. I actually can't thank you enough for explaining this to me! – Logan Feb 06 '16 at 18:04