Q:

C - Adding CD to Shell

I'm following through Operating System Concepts 9th Edition by Silberschatz, Galvin and Gagne. I have gotten to the first project on Chapter 3 where they're asking us to create a UNIX Shell and History Feature. I have created some of both, the history and most shell commands work I believe (pwd, date, cal, etc.) - I'm now trying to add cd to the list and I'm getting a Segmentation fault (core dumped) when I use cd in my shell. I feel that this isn't too difficult to implement, you just take pwd and swap it out with wherever you're going. Here is my code: 
//Enter command 'history' for history feature and CTRL - c to exit the 'osh>' shell 
/*Header files */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>

#define MAX_LINE 80 /* The maximum length of a command */
#define BUFFER_SIZE 50
#define buffer "\n\Shell Command History:\n"

//declarations
char history[10][BUFFER_SIZE]; //history array to store history commands
int count = 0;
char *gdir, *dir, *to;

//function to display the history of commands
void displayHistory() {

    printf("Shell command history:\n");

    int i;
    int j = 0;
    int histCount = count;

    //loop for iterating through commands
    for (i = 0; i<10;i++) {
         //command index
        printf("%d. ", histCount);
        while (history[i][j] != '\n' && history[i][j] != '\0') {    
            //printing command
             printf("%c", history[i][j]);
             j++;
         }
        printf("\n");
         j = 0;
         histCount--;
         if (histCount == 0)
           break;
     }
     printf("\n");
    } 

    //Fuction to get the command from shell, tokenize it and set the args parameter

    int formatCommand(char inputBuffer[], char *args[],int *flag) {
    int length; // # of chars in command line
    int i; // loop index for inputBuffer
    int start; // index of beginning of next command
    int ct = 0; // index of where to place the next parameter into args[]
    int hist;
    //read user input on command line and checking whether the command is !! or !n
          length = read(STDIN_FILENO, inputBuffer, MAX_LINE);    

     start = -1;
     if (length == 0)
         exit(0); //end of command
     if (length < 0) {
         printf("Command not read\n");
         exit(-1); //terminate
     }

     //examine each character
     for (i=0;i<length;i++) {
         switch (inputBuffer[i]) {
             case ' ':
            case '\t' : // to seperate arguments
             if(start != -1) {
                args[ct] = &inputBuffer[start]; 
                 ct++;
             }
             inputBuffer[i] = '\0'; // add a null char at the end
             start = -1;
            break;

             case '\n': //final char 
            if (start != -1) {
                 args[ct] = &inputBuffer[start];
                ct++;
            }
             inputBuffer[i] = '\0';
             args[ct] = NULL; // no more args
             break;

             default : 
             if (start == -1)
                 start = i;
             if (inputBuffer[i] == '&') {
                 *flag = 1; //this flag is the differentiate whether the child process is invoked in background
                 inputBuffer[i] = '\0';
             }
         }
     }

     args[ct] = NULL; //if the input line was > 80

     if(strcmp(args[0],"history")==0) {    
         if(count>0) {
             displayHistory();
        } else {
            printf("\nNo Commands in the history\n");
        }
        return -1;
     } else if (args[0][0]-'!' ==0) {    
        int x = args[0][1]- '0'; 
        int z = args[0][2]- '0'; 

        if(x>count) { // second letter check 
            printf("\nNo Such Command in the history\n");
            strcpy(inputBuffer,"Wrong command");
        } else if (z!=-48) { // third letter check
            printf("\nNo Such Command in the history. Enter <=!9 (buffer size is 10 along with current command)\n");
            strcpy(inputBuffer,"Wrong command");
        } else {
            if(x==-15) {     
                strcpy(inputBuffer,history[0]); // this will be your 10 th(last) command
        } else if(x==0) { //Checking for '!0'
            printf("Enter proper command");
            strcpy(inputBuffer,"Wrong command");
        } else if(x>=1) { //Checking for '!n', n >=1
            strcpy(inputBuffer,history[count-x]);
        }    
    }
    }

    for (i = 9;i>0; i--) //Moving the history elements one step higher
        strcpy(history[i], history[i-1]);

    strcpy(history[0],inputBuffer); //Updating the history array with input buffer
    count++;
    if(count>10) { 
        count=10;
    }
}

int main(void) {
     char inputBuffer[MAX_LINE]; /* buffer to hold the input command */
     int flag; // equals 1 if a command is followed by "&"
     char *args[MAX_LINE/2 + 1];/* max arguments */
     int should_run =1;

     pid_t pid,tpid;
     int i;

     while (should_run) { //infinite loop for shell prompt
         flag = 0; //flag =0 by default
         printf("osh>");
         fflush(stdout);

         if(-1!=formatCommand(inputBuffer,args,&flag)) { // get next command 
            pid = fork();

            if (!strcmp(args[0], "cd")) {
                gdir = getcwd(inputBuffer, sizeof(inputBuffer));
                dir = strcat(gdir, "/");
                to = strcat(dir, args[1]);

                chdir(to);
                continue;
            }

             if (pid < 0) { // if pid is less than 0, forking fails
                 printf("Fork failed.\n");
                 exit (1);
             } else if (pid == 0) { //if pid == 0
                 //command not executed
                 if (execvp(args[0], args) == -1) {
                     printf("Error executing command\n");
                 }
            } else {
             // if flag == 0, the parent will wait,
             // otherwise returns to the formatCommand() function.
                 i++;
                 if (flag == 0) {
                     i++;
                     wait(NULL);
                 }
             }
         }
     }
}

I'm also stuck up on how to add batch processing so that I can run a script with my shell. I have the file: script.sh that has the code:
pwd
cal
date

Ideally when I type into my shell ./script.sh it would (or I guess I want it to) run the script file but currently just gets Error since I haven't implemented that. I'm stuck up on these 2 things if anyone could assist me with them I'd appreciate it!

A:

This problem still occurs if you leave out all the history management. It also happens if you use a static cd command instead of reading it from the user. If you keep removing all the things that are not actually necessary, you might come up with this tiny example that still shows the same issue:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
  char str[50];
  // Pretend to read input
  strcpy(str, "cd tmp");
  // Pretend to split string
  str[2]='\0';

  // Pretend to set up arguments
  char* args[2];
  args[0] = &str[0];
  args[1] = &str[3];

   // Your code for chdir:
  char* gdir = getcwd(str, sizeof(str));
  char* dir = strcat(gdir, "/");

  // Why does this segfault?
  char* to = strcat(dir, args[1]);

  chdir(to);
  perror("Result");
}

The problem with this is that args[1] and to are actually the same piece of memory. When you append a character to one, you also append a character to the other. This means that copying one character at a time means you'll never finish, and instead it segfaults.
Instead, just pass the relative directory directly to chdir. All syscalls accept relative paths:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
  char str[50];
  // Pretend to read input
  strcpy(str, "cd tmp");
  // Pretend to split string
  str[2]='\0';

  // Pretend to set up arguments
  char* args[2];
  args[0] = &str[0];
  args[1] = &str[3];

  // Doesn't segfault
  chdir(args[1]);
  // Prints "Result: Success"
  perror("Result");
}

If you fix this issue, you'll find that it stops segfaulting and that perror claims it was successful.
However, applying this to your program will appear to work but not to change the directory and report success, but you're still in the same directory.
If you narrow it down again, you'll find that it only happens if you fork() first: child processes can't change the directory of their parents. Don't fork if you intend to do so. 
I added both fixes in your full shell dump below, and here's a session showing how it works:
osh>pwd
/
osh>cd tmp
osh>pwd
/tmp

Here's the complete source with fixes attached:
//Enter command 'history' for history feature and CTRL - c to exit the 'osh>' shell
/*Header files */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>

#define MAX_LINE 80 /* The maximum length of a command */
#define BUFFER_SIZE 50
#define buffer "\n\Shell Command History:\n"

//declarations
char history[10][BUFFER_SIZE]; //history array to store history commands
int count = 0;
char *gdir, *dir, *to;

//function to display the history of commands
void displayHistory() {

    printf("Shell command history:\n");

    int i;
    int j = 0;
    int histCount = count;

    //loop for iterating through commands
    for (i = 0; i<10;i++) {
         //command index
        printf("%d. ", histCount);
        while (history[i][j] != '\n' && history[i][j] != '\0') {
            //printing command
             printf("%c", history[i][j]);
             j++;
         }
        printf("\n");
         j = 0;
         histCount--;
         if (histCount == 0)
           break;
     }
     printf("\n");
    }

    //Fuction to get the command from shell, tokenize it and set the args parameter

    int formatCommand(char inputBuffer[], char *args[],int *flag) {
    int length; // # of chars in command line
    int i; // loop index for inputBuffer
    int start; // index of beginning of next command
    int ct = 0; // index of where to place the next parameter into args[]
    int hist;
    //read user input on command line and checking whether the command is !! or !n
          length = read(STDIN_FILENO, inputBuffer, MAX_LINE);

     start = -1;
     if (length == 0)
         exit(0); //end of command
     if (length < 0) {
         printf("Command not read\n");
         exit(-1); //terminate
     }
     //examine each character
     for (i=0;i<length;i++) {
         switch (inputBuffer[i]) {
             case ' ':
            case '\t' : // to seperate arguments
             if(start != -1) {
                args[ct] = &inputBuffer[start];
                 ct++;
             }
             inputBuffer[i] = '\0'; // add a null char at the end
             start = -1;
            break;

             case '\n': //final char
            if (start != -1) {
                 args[ct] = &inputBuffer[start];
                ct++;
            }
             inputBuffer[i] = '\0';
             args[ct] = NULL; // no more args
             break;

             default :
             if (start == -1)
                 start = i;
             if (inputBuffer[i] == '&') {
                 *flag = 1; //this flag is the differentiate whether the child process is invoked in background
                 inputBuffer[i] = '\0';
             }
         }
     }

     args[ct] = NULL; //if the input line was > 80

     if(strcmp(args[0],"history")==0) {
         if(count>0) {
             displayHistory();
        } else {
            printf("\nNo Commands in the history\n");
        }
        return -1;
     } else if (args[0][0]-'!' ==0) {
        int x = args[0][1]- '0';
        int z = args[0][2]- '0';

        if(x>count) { // second letter check
            printf("\nNo Such Command in the history\n");
            strcpy(inputBuffer,"Wrong command");
        } else if (z!=-48) { // third letter check
            printf("\nNo Such Command in the history. Enter <=!9 (buffer size is 10 along with current command)\n");
            strcpy(inputBuffer,"Wrong command");
        } else {
            if(x==-15) {
                strcpy(inputBuffer,history[0]); // this will be your 10 th(last) command
        } else if(x==0) { //Checking for '!0'
            printf("Enter proper command");
            strcpy(inputBuffer,"Wrong command");
        } else if(x>=1) { //Checking for '!n', n >=1
            strcpy(inputBuffer,history[count-x]);
        }
    }

    }

    for (i = 9;i>0; i--) //Moving the history elements one step higher
        strcpy(history[i], history[i-1]);

    strcpy(history[0],inputBuffer); //Updating the history array with input buffer
    count++;
    if(count>10) {
        count=10;
    }
}

int main(void) {
     char inputBuffer[MAX_LINE]; /* buffer to hold the input command */
     int flag; // equals 1 if a command is followed by "&"
     char *args[MAX_LINE/2 + 1];/* max arguments */
     int should_run =1;

     pid_t pid,tpid;
     int i;

     while (should_run) { //infinite loop for shell prompt
         flag = 0; //flag =0 by default
         printf("osh>");
         fflush(stdout);

         if(-1!=formatCommand(inputBuffer,args,&flag)) { // get next command

            // Don't fork first
            if (!strcmp(args[0], "cd")) {
                // Don't fetch the current dir
                chdir(args[1]);
                continue;
            }

            pid = fork();

             if (pid < 0) { // if pid is less than 0, forking fails
                 printf("Fork failed.\n");
                 exit (1);
             } else if (pid == 0) { //if pid == 0
                 //command not executed
                 if (execvp(args[0], args) == -1) {
                     printf("Error executing command\n");
                 }
            } else {
             // if flag == 0, the parent will wait,
             // otherwise returns to the formatCommand() function.
                 i++;
                 if (flag == 0) {
                     i++;
                     wait(NULL);
                 }
             }
         }
     }
}

