Search code examples
clinuxprocessfork

Making a process run in the background by using fork


I am trying to make a simple shell. When I execute a command in the child and wait for it, the code works fine. However, when I want to execute it in the background, the program just hangs there, and the prompt symbol did not show up. What else code should I add to make my child process do its work, while the parent continues on receiving another user command?

int
lsh_launch(char **args)
{

    pid = fork();

    if (pid == 0) {
        // child process
        if (execvp(args[0], args) == -1) {
            perror("lsh");
        }
        exit(0);
    }
    else if (pid < 0) {
        perror("lsh");
    }
    else {
        // parent process
        if (!background) {
            wait(NULL);                 // works fine
        }
        else {
            printf("[PID]: %d\n", pid);
            // after printing the pid, the prompt symbol does not show again
        }
    }

    return 1;
}

My caller function: After lsh_execute, the code will go back to main, and go back to the top of the while loop, and print another command prompt.

int lsh_execute(char **args)
{
  int i;
  if (args[0] == NULL) { 
    // An empty command was entered, return
    return 1;
  }

  //if the command is in my predefined function list, execute
  for (i = 0; i < lsh_num_builtins(); i++) {
    if (strcmp(args[0], builtin_str[i]) == 0) {
      return (*builtin_func[i])(args);
    }
  }
  //if not, go to launch and fork a child and execute
  return lsh_launch(args);
}

Code Link: myshell The problem is when I type "ls &", the program do output a list of file names in the current directory, but it hangs there.


Solution

  • There were a few issues:

    1. After the printf for the prompt, we have to fflush(stdout) to force out the text because there is no newline.
    2. No reaping of background children, so they become zombies.
    3. This would interfere with wait(NULL) for the next foreground command. (e.g.) sleep 5 & ; pwd would mess up the wait for 2nd command.
    4. To fix: the program should periodically call waitpid(-1,NULL,WNOHANG) to reap the zombie background children.
    5. Also, with background jobs, a previous one might complete before the next foreground job. So, we want to foreground to change from wait to waitpid
    6. Doing signal(SIGCHLD,SIG_IGN) prevents wait* from seeing/reaping a background pid.
    7. This would interfere with subsequent wait(NULL) for foreground commands.
    8. The code for the replay command that attaches to the linked list was wrong. The compiler flagged this when compiled with warnings (e.g. -Wall).
    9. Compiler warnings about "unused" or "set but not used" variables.
    10. We should fflush both stdout and stderr prior to a fork.

    Some improvements:

    1. Added "job control" for background jobs and jobs and wait commands.
    2. Added debug printing with dbgprt.

    In the code below, I've used cpp conditionals to denote old vs. new code:

    #if 0
    // old code
    #else
    // new code
    #endif
    
    #if 1
    // new code
    #endif
    

    Note: this can be cleaned up by running the file through unifdef -k


    Here is the refactored code. It is annotated with the bugs and fixes:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <dirent.h>
    
    #define BUFSIZE 1024
    #define TOK_BUFSIZE 64
    #define TOK_DELIM " \t\r\n\a"
    
    char *currentdir;
    int tokencount;
    typedef struct node {
        char str[64];
        struct node *next;
    } node;
    
    node *top = NULL;
    node *cur = NULL;
    
    #if 1
    #ifdef DEBUG
    #define dbgprt(_fmt...) \
        do { \
            int sverr = errno; \
            printf(_fmt); \
            errno = sverr; \
        } while (0)
    #else
    #define dbgprt(_fmt...) \
        do { } while (0)
    #endif
    #endif
    
    // NOTE/FIX: implement job control
    #if 1
    struct jobctl {
        int job_jobno;                      // job number
        pid_t job_pid;                      // child pid
        int job_status;                     // child status
        struct jobctl *job_next;            // list link
    };
    struct jobctl *joblist;                 // list of background jobs
    int jobcount;                           // highest job number
    
    // jobreap -- reap all detached/background jobs (children)
    void
    jobreap(void)
    {
    
        // wait for all background jobs
        while (joblist != NULL) {
            int status;
    
            // reap any child (non-blocking)
            pid_t pid = waitpid(-1,&status,WNOHANG);
    
            // more children are pending [but not yet finished]
            if (pid == 0)
                break;
    
            dbgprt("jobreap: WAITPID pid=%d status=%8.8X\n",pid,status);
    
            if (pid < 0) {
                printf("jobreap: error -- %s\n",strerror(errno));
                break;
            }
    
            // find it in our list of detached/background jobs
            struct jobctl *cur;
            struct jobctl *prev = NULL;
            for (cur = joblist;  cur != NULL;  cur = cur->job_next) {
                if (cur->job_pid == pid)
                    break;
                prev = cur;
            }
    
            // reaping child of one of our children (i.e. grandchild) that is _not_
            // in our list
            if (cur == NULL) {
                printf("[grandchild]: pid %d, status %8.8X\n", pid, status);
                continue;
            }
    
            printf("[background]: pid %d, %8.8X, job %d\n",
                pid, status, cur->job_jobno);
    
            // we don't do anything with this but ...
            cur->job_status = status;
    
            // remove our child from list
            if (prev != NULL)
                prev->job_next = cur->job_next;
            else
                joblist = cur->job_next;
    
            // release storage
            free(cur);
    
            // reset job count if we've reaped _all_ of our direct children
            if (joblist == NULL)
                jobcount = 0;
        }
    }
    
    int
    lsh_jobs(char **args)
    {
    
        for (struct jobctl *cur = joblist;  cur != NULL;  cur = cur->job_next)
            printf("[%d] pid %d\n",cur->job_jobno,cur->job_pid);
    
        return 1;
    }
    
    int
    lsh_wait(char **args)
    {
    
        while (1) {
            jobreap();
            if (joblist == NULL)
                break;
        }
    
    #if 0
        for (struct jobctl *cur = joblist;  cur != NULL;  cur = cur->job_next)
            printf("[%d] pid %d\n",cur->job_jobno,cur->job_pid);
    #endif
    
        return 1;
    }
    
    #endif
    
    char *
    read_line(void)
    {
        int bufsize = BUFSIZ;
        int position = 0;
        char *buffer = (char *) malloc(sizeof(char) * bufsize);
        int c;
    
        if (!buffer) {
            fprintf(stderr, "lsh: allocation error\n");
            exit(EXIT_FAILURE);
        }
    
        while (1) {
            c = getchar();
            if (c == EOF || c == '\n') {
                buffer[position] = '\0';
                return buffer;
            }
            else {
                buffer[position] = c;
            }
            position++;
    
            if (position >= bufsize) {
                bufsize += BUFSIZ;
                buffer = realloc(buffer, bufsize);
                if (!buffer) {
                    fprintf(stderr, "lsh: allocation error\n");
                    exit(EXIT_FAILURE);
                }
            }
        }
    
    }
    
    char **
    split_line(char *line)
    {
        int bufsize = BUFSIZ;
        int position = 0;
        char **tokens = (char **) malloc(sizeof(char *) * bufsize);
        char *token;
    
        if (!tokens) {
            fprintf(stderr, "lsh: allocation error\n");
            exit(EXIT_FAILURE);
        }
    
        token = strtok(line, TOK_DELIM);
        while (token != NULL) {
            tokens[position] = token;
            position++;
    
            if (position >= bufsize) {
                bufsize += TOK_BUFSIZE;
                tokens = realloc(tokens, bufsize * sizeof(char *));
                if (!tokens) {
                    fprintf(stderr, "lsh: allocation error\n");
                    exit(EXIT_FAILURE);
                }
            }
            token = strtok(NULL, TOK_DELIM);
        }
        tokens[position] = NULL;
        tokencount = position;
        return tokens;
    
    }
    
    void
    catHeadTail(char *filename, int headtail, char *line)
    {
    
        char *ptr;
        long num;
    
        num = strtol(line, &ptr, 10);
        num *= -1;
        char cwd[256];
    
        if (getcwd(cwd, sizeof(cwd)) == NULL) {
            perror("getcwd() error");
        }
        else {
            strcat(cwd, "/");
            strcat(cwd, filename);
        }
    
        char lines[1000][256];
        int linecount = 0;
        FILE *input = fopen(cwd, "r");
    
        if (!input) {
            perror("lsh: file open error");
        }
    
        while (fgets(lines[linecount], 256, input) != NULL) {
            linecount++;
        }
        fclose(input);
    
        if (headtail == 1) {
            for (int i = 0; i < num; i++) {
                printf("%s", lines[i]);
            }
        }
        else {
            for (int i = linecount - num; i <= linecount; i++) {
                printf("%s", lines[i]);
            }
        }
    
    }
    
    int
    lsh_launch(char **args)
    {
        pid_t pid;
    // NOTE/BUG: unused
    #if 0
        pid_t wpid;
        int state;
    #endif
        int background = 0;
    
        if (!strcmp(args[tokencount - 1], "&")) {
            background = 1;
            args[tokencount - 1] = '\0';
        }
    
        dbgprt("lsh_launch: PARENT pid=%d pgrp=%d\n",getpid(),getpgrp());
    
    #if 1
        fflush(stdout);
        fflush(stderr);
    #endif
        pid = fork();
    
        if (pid == 0) {
            // child process
            dbgprt("lsh_launch: BEF pgid=%d\n",getpgrp());
            setpgid(0, 0);
            dbgprt("lsh_launch: AFT pgid=%d\n",getpgrp());
    
            if (tokencount >= 4 && !strcmp(args[2], "|"))   // pipleline
            {
                if (!strcmp(args[0], "cat") && !strcmp(args[2], "|")) {
                    if (!strcmp(args[3], "head"))
                        catHeadTail(args[1], 1, args[4]);   // head
                    else
                        catHeadTail(args[1], 0, args[4]);   // tail
                }
            }
            else if (tokencount >= 3 && !strcmp(args[1], "<")) {
                char *temp[3];
    
                temp[0] = args[0];
                temp[1] = args[2];
                temp[2] = NULL;
                if (execvp(temp[0], temp) == -1) {
                    perror("lsh");
                }
            }
            else if (tokencount >= 4 && !strcmp(args[2], ">")) {
                char lines[1000][256];
                int linecount = 0;
                FILE *input = fopen(args[1], "r");
                FILE *output = fopen(args[3], "w");
    
                if (!input || !output) {
                    perror("lsh: file open error");
                }
    
                while (fgets(lines[linecount], 256, input) != NULL) {
                    linecount++;
                }
    
                for (int i = 0; i < linecount; i++) {
                    fputs(lines[i], output);
                }
            }
            else if (execvp(args[0], args) == -1) {
                perror("lsh");
            }
            exit(0);
        }
        else if (pid < 0) {
            perror("lsh");
        }
        else {
            // parent process
            if (! background) {
                dbgprt("lsh_launch: WAIT/BEF pid=%d\n",pid);
                int status;
    // NOTE/BUG: with detached jobs, a detached job might complete _before_ this
    // one -- so wait for pid specifically
    #if 0
                pid = wait(&status);
    #else
                pid = waitpid(pid,&status,0);
    #endif
                dbgprt("lsh_launch: WAIT/AFT pid=%d status=%8.8X\n",pid,status);
            }
            else {
    // NOTE/BUG: doing this prevents wait from working correctly on the child
    #if 0
                signal(SIGCHLD, SIG_IGN);
    #endif
    // NOTE/FIX: implement "job control" for background job
    #if 1
                // get new job control
                struct jobctl *job = calloc(1,sizeof(*job));
    
                job->job_pid = pid;
                job->job_jobno = ++jobcount;
    
                printf("[PID]: %d job %d\n", pid, job->job_jobno);
    
                // add it to list
                job->job_next = joblist;
                joblist = job;
    #endif
            }
        }
        return 1;
    }
    
    int lsh_cd(char **args);
    int lsh_help(char **args);
    int lsh_exit(char **args);
    int lsh_echo(char **args);
    int lsh_record(char **args);
    int lsh_replay(char **args);
    int lsh_mypid(char **args);
    
    /*
      List of builtin commands, followed by their corresponding functions.
     */
    char *builtin_str[] = {
        "cd",
        "help",
        "exit",
        "echo",
        "record",
        "replay",
        "mypid",
    #if 1
        "jobs",
        "wait",
    #endif
    };
    
    /* all the buildin func*/
    int (*builtin_func[]) (char **) = {
        lsh_cd,
        lsh_help, lsh_exit, lsh_echo, lsh_record, lsh_replay, lsh_mypid,
    #if 1
        lsh_jobs,
        lsh_wait,
    #endif
    };
    
    int
    lsh_num_builtins()
    {
        return sizeof(builtin_str) / sizeof(char *);
    }
    
    int
    lsh_cd(char **args)
    {
        if (args[1] == NULL) {
            fprintf(stderr, "lsh: expected argument to \"cd\"\n");
        }
        else {
            if (chdir(args[1]) != 0) {
                perror("lsh");
            }
        }
        return 1;
    }
    
    int
    lsh_help(char **args)
    {
        int i;
    
        printf("Stephen Brennan's LSH\n");
        printf("Type program names and arguments, and hit enter.\n");
        printf("The following are built in:\n");
    
        for (i = 0; i < lsh_num_builtins(); i++) {
            printf("  %s\n", builtin_str[i]);
        }
    
        printf("Use the man command for information on other programs.\n");
        return 1;
    }
    
    int
    lsh_exit(char **args)
    {
        return 0;
    }
    
    int
    lsh_echo(char **args)
    {
        if (!strcmp(args[1], "-n")) {
            for (int i = 2; i < tokencount; i++) {
                printf("%s ", args[i]);
            }
        }
        else {
            for (int i = 1; i < tokencount; i++) {
                printf("%s ", args[i]);
            }
            printf("\n");
        }
        return 1;
    }
    
    int
    lsh_record(char **args)
    {
        int i = 1;
        node *go = top;
    
        while (go != NULL) {
            printf("%d: %s\n", i, go->str);
            go = go->next;
            i++;
        }
        return 1;
    }
    
    int
    lsh_replay(char **args)
    {
        node *go;
    
        go = top;
    
        for (int i = 0; i < atoi(args[1]) - 1; i++) {
            go = go->next;
        }
        node *add = (node *) malloc(sizeof(node));
    
        add->next = NULL;
        strcpy(add->str, go->str);
        cur->next = add;
        cur = cur->next;
    
        char **command = split_line(go->str);
    
        pid_t wpid;
        int state;
    
    #if 1
        fflush(stdout);
        fflush(stderr);
    #endif
        pid_t pd = fork();
    
        if (pd == 0) {
            if (execvp(command[0], command) == -1) {
                perror("lsh");
            }
            exit(EXIT_FAILURE);
        }
        else if (pd < 0) {
            perror("lsh");
        }
        else {
            do {
                wpid = waitpid(pd, &state, WUNTRACED);
                dbgprt("lsh_replay: pd=%d wpid=%d state=%8.8X\n",
                    pd,wpid,state);
    // NOTE/FIX: wpid is set but not used -- this is a dummy test
    #if 1
                if (wpid != pd)
                    continue;
    #endif
            } while (!WIFEXITED(state) && !WIFSIGNALED(state));
        }
    
        return 1;
    }
    
    int
    lsh_mypid(char **args)
    {
        int pid = getpid();
    
        if (!strcmp(args[1], "-i")) {
            printf("%d", pid);
        }
        else if (!strcmp(args[1], "-p")) {
    #if 0
            int c;
    #endif
            FILE *file;
            char path[30] = "/proc/";
            char content[512];
            char *token;
    
            strcat(path, args[2]);
            strcat(path, "/stat");
            file = fopen(path, "r");
            if (file) {
                int i = 1;
    
                fgets(content, 512, file);
                token = strtok(content, TOK_DELIM);
                while (i < 4) {
                    token = strtok(NULL, TOK_DELIM);
                    i++;
                }
                fclose(file);
            }
            else {
                printf("not existed");
            }
    
            printf("%d", atoi(token));
        }
        else if (!strcmp(args[1], "-c")) {
    #if 0
            int c;
    #endif
            FILE *file;
            char path[30] = "/proc/";
    #if 0
            char pids[10];
    #endif
            char content[512];
            char *token;
    
            strcat(path, args[2]);
            strcat(path, "/task/");
            strcat(path, args[2]);
            strcat(path, "/children");
            file = fopen(path, "r");
            if (file) {
                fgets(content, 512, file);
                token = strtok(content, TOK_DELIM);
                printf("%s", token);
                while (token != NULL) {
                    token = strtok(NULL, TOK_DELIM);
                    printf("%s", token);
                }
                fclose(file);
            }
            else {
                fprintf(stderr, "can not open the file\n");
            }
            return 1;
    
        }
        return 1;
    }
    
    int
    lsh_execute(char **args)
    {
        int i;
    
        if (args[0] == NULL) {
            // An empty command was entered.
            return 1;
        }
    
        for (i = 0; i < lsh_num_builtins(); i++) {
            if (strcmp(args[0], builtin_str[i]) == 0) {
                return (*builtin_func[i]) (args);
            }
        }
    
        return lsh_launch(args);
    }
    
    void
    shell_loop()
    {
        int counter = 0;
        char *line;
        char **args;
        char *allline = malloc(sizeof(char) * 64);
        int state = 1;
    
        while (state == 1) {
    #if 1
            jobreap();
    #endif
    
            printf(">>>$ ");
    // NOTE/FIX: must flush the output because the printf does _not_ end in newline
    #if 1
            fflush(stdout);
    #endif
            line = read_line();
    #if 1
            jobreap();
    #endif
            strcpy(allline, line);
            args = split_line(line);
            if (args[0] == NULL) {          // space or \t
                continue;
            }
            else if (strcmp(args[0], "replay")) // not replay
            {
                if (counter == 0) {
                    top = (node *) malloc(sizeof(node));
                    strcpy(top->str, allline);
                    top->next = NULL;
    
                    cur = top;
                }
                else {
                    node *m = (node *) malloc(sizeof(node));
    
                    strcpy(m->str, allline);
                    m->next = NULL;
    
                    cur->next = m;
                    cur = m;
                }
                if (counter >= 16) {
                    node *del;
    
                    del = top;
                    top = top->next;
                    free(del);
                }
            }
            else if (!strcmp(args[0], "replay"))    // replay
            {
                node *go = top;
    
                for (int i = 0; i < atoi(args[1]) - 1; i++) {
                    go = go->next;
                }
                node *add = (node *) malloc(sizeof(node));
    
                strcpy(add->str, go->str);
                add->next = NULL;
                cur->next = add;
    
                if (counter >= 16) {
                    node *del = (node *) malloc(sizeof(node));
    // NOTE/BUG: this is flagged by the compiler (i.e.) it sees that the value
    // is discarded
    #if 0
                    del = top;
                    top = top->next;
    #else
                    del->next = top;
                    top = del;
    #endif
                    // free(del);
                }
            }
            state = lsh_execute(args);
    
            free(line);
            free(args);
            // free(allline); don't really know why it can not be freed
            counter++;
    
        }
    }
    
    int
    main()
    {
    
        shell_loop();
    
        return EXIT_SUCCESS;
    
    }
    

    UPDATE:

    Note: Redacted for SO space limits ...

    1. Why is fflush necessary with "\n"?

    Your prompt does not: printf(">>>$ ");

    2.-7. Could you please guide me to some link?

    1. Download source code for bash and/or other shells.

    2. Similar SO questions and some do background processes.

    3. Years ago, I looked at how bash did this. I modeled changes to job control [loosely] on that.

    1. Is that because the remaining string in the buffer that has not been printed will be copy to the child?

    Default for stdout for TTY is line buffered. For a file, it's full buffering (e.g. 4096 chars).

    Then not doing fflush before a fork can cause both parent and child to flush stale data in parent's buffer.

    I've updated the code:

    1. Did unifdef -k and removed comments
    2. Changed foreground to use job control vs. explicit waitpid
    3. Fixed record/replay.
    4. shell_loop was not freeing args
    5. Changed builtins to use a struct.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <dirent.h>
    
    #define TOK_DELIM " \t\r\n\a"
    
    #define FREE(_ptr) \
        do { \
            dbgprt("FREE: " #_ptr "=%p (line %d)\n",_ptr,__LINE__); \
            free(_ptr); \
            _ptr = NULL; \
        } while (0)
    
    char *currentdir;
    int tokencount;
    
    typedef struct node {
        char *str;
        struct node *next;
    } node;
    
    // replay list
    node *top = NULL;
    node *cur = NULL;
    int counter;
    
    #ifdef DEBUG
    #define dbgprt(_fmt...) \
        do { \
            int sverr = errno; \
            printf(_fmt); \
            errno = sverr; \
        } while (0)
    #else
    #define dbgprt(_fmt...) \
        do { } while (0)
    #endif
    
    #define ALLOCP(_ptr)            malloc(sizeof(*(_ptr)))
    
    struct builtin {
        const char *cmd_str;
        const char *cmd_help;
        int (*cmd_func)(char **);
    };
    
    #define CMDX(_cmd,_fnc,_help) \
        { .cmd_str = #_cmd, .cmd_func = _fnc, .cmd_help = _help}
    #define CMD(_cmd,_help) \
        CMDX(_cmd,lsh_ ## _cmd,_help)
    
    // job control
    struct jobctl {
        int job_jobno;                      // job number
        pid_t job_pid;                      // child pid
        int job_status;                     // child status
        struct jobctl *job_next;            // list link
    };
    struct jobctl *joblist;                 // list of background jobs
    int jobcount;                           // highest job number
    pid_t fgpid;                            // foreground job
    
    // jobreap -- reap all detached/background jobs (children)
    void
    jobreap(void)
    {
    
        // wait for all background jobs
        while (joblist != NULL) {
            int status;
    
            // should we wait for foreground command?
            int nowait = (fgpid == 0) ? WNOHANG : 0;
    
            // reap any child
            pid_t pid = waitpid(-1,&status,nowait);
    
            // more children are pending [but not yet finished]
            // NOTE: should _not_ occur with wait for foreground
            if (pid == 0) {
                if (! nowait)
                    continue;
                break;
            }
    
            dbgprt("jobreap: WAITPID pid=%d status=%8.8X\n",pid,status);
    
            // NOTE: this probably should not occur
            if (pid < 0) {
                printf("jobreap: error -- %s\n",strerror(errno));
                if (! nowait)
                    continue;
                break;
            }
    
            // are we reaping the foreground job?
            int fgflg = (pid == fgpid);
    
            // find it in our list of detached/background jobs
            struct jobctl *cur;
            struct jobctl *prev = NULL;
            for (cur = joblist;  cur != NULL;  cur = cur->job_next) {
                if (cur->job_pid == pid)
                    break;
                prev = cur;
            }
    
            // reaping child of one of our children (i.e. grandchild) that is _not_
            // in our list
            if (cur == NULL) {
                if (nowait)
                    printf("[grandchild]: pid %d, status %8.8X\n", pid, status);
                continue;
            }
    
            if (! fgflg)
                printf("[background]: pid %d, %8.8X, job %d\n",
                    pid, status, cur->job_jobno);
    
            // we don't do anything with this but ...
            cur->job_status = status;
    
            // remove our child from list
            if (prev != NULL)
                prev->job_next = cur->job_next;
            else
                joblist = cur->job_next;
    
            // release storage
            free(cur);
    
            // reset job count if we've reaped _all_ of our direct children
            if (joblist == NULL)
                jobcount = 0;
    
            // if we just reaped the foreground job, we no longer need to hard wait
            // for it
            if (fgflg)
                fgpid = 0;
        }
    }
    
    // jobadd -- get new job control
    void
    jobadd(pid_t pid,int fgflg)
    {
        struct jobctl *job = calloc(1,sizeof(*job));
    
        job->job_pid = pid;
        job->job_jobno = ++jobcount;
    
        if (! fgflg)
            printf("[PID]: %d job %d\n", pid, job->job_jobno);
    
        // add it to list
        job->job_next = joblist;
        joblist = job;
    
        // tell jobreap to wait for this
        if (fgflg)
            fgpid = pid;
    }
    
    int
    lsh_jobs(char **args)
    {
    
        for (struct jobctl *cur = joblist;  cur != NULL;  cur = cur->job_next)
            printf("[%d] pid %d\n",cur->job_jobno,cur->job_pid);
    
        return 1;
    }
    
    int
    lsh_wait(char **args)
    {
    
        while (1) {
            jobreap();
            if (joblist == NULL)
                break;
        }
    
        return 1;
    }
    
    char *
    read_line(void)
    {
        int bufsize = 0;
        int position = 0;
        char *buffer = NULL;
        int c;
    
        while (1) {
            c = getchar();
    
            if (position >= bufsize) {
                bufsize += 100;
    
                buffer = realloc(buffer, bufsize + 1);
    
                if (!buffer) {
                    fprintf(stderr, "read_line: allocation error\n");
                    exit(EXIT_FAILURE);
                }
            }
    
            if (c == EOF || c == '\n')
                break;
    
            buffer[position] = c;
            position++;
        }
    
        buffer[position] = '\0';
        buffer = realloc(buffer, position + 1);
    
        return buffer;
    }
    
    char **
    split_line(char *line)
    {
        int bufsize = 0;
        char **tokens = NULL;
        int position = 0;
        char *token;
    
        token = strtok(line, TOK_DELIM);
    
        while (token != NULL) {
            if (position >= bufsize) {
                bufsize += 64;
                tokens = realloc(tokens, (bufsize + 1) * sizeof(char *));
                if (!tokens) {
                    fprintf(stderr, "lsh: allocation error\n");
                    exit(EXIT_FAILURE);
                }
            }
    
            tokens[position] = token;
            position++;
    
            token = strtok(NULL, TOK_DELIM);
        }
    
        tokens[position] = NULL;
    
        // trim to actual size used
        tokens = realloc(tokens, (position + 1) * sizeof(char *));
    
        tokencount = position;
    
        return tokens;
    
    }
    
    void
    catHeadTail(char *filename, int headtail, char *line)
    {
        char *ptr;
        long num;
    
        num = strtol(line, &ptr, 10);
        num *= -1;
        char cwd[256];
    
        if (getcwd(cwd, sizeof(cwd)) == NULL) {
            perror("getcwd() error");
        }
        else {
            strcat(cwd, "/");
            strcat(cwd, filename);
        }
    
        char lines[1000][256];
        int linecount = 0;
        FILE *input = fopen(cwd, "r");
    
        if (!input) {
            perror("lsh: file open error");
        }
    
        while (fgets(lines[linecount], 256, input) != NULL) {
            linecount++;
        }
        fclose(input);
    
        if (headtail == 1) {
            for (int i = 0; i < num; i++) {
                printf("%s", lines[i]);
            }
        }
        else {
            for (int i = linecount - num; i <= linecount; i++) {
                printf("%s", lines[i]);
            }
        }
    }
    
    int
    lsh_launch(char **args)
    {
        pid_t pid;
        int background = 0;
    
        if (!strcmp(args[tokencount - 1], "&")) {
            background = 1;
            args[tokencount - 1] = '\0';
        }
    
        dbgprt("lsh_launch: PARENT pid=%d pgrp=%d\n",getpid(),getpgrp());
    
        fflush(stdout);
        fflush(stderr);
        pid = fork();
    
        if (pid == 0) {
            // child process
            setpgid(0, 0);
    
            if (tokencount >= 4 && !strcmp(args[2], "|"))   // pipleline
            {
                if (!strcmp(args[0], "cat") && !strcmp(args[2], "|")) {
                    if (!strcmp(args[3], "head"))
                        catHeadTail(args[1], 1, args[4]);   // head
                    else
                        catHeadTail(args[1], 0, args[4]);   // tail
                }
            }
            else if (tokencount >= 3 && !strcmp(args[1], "<")) {
                char *temp[3];
    
                temp[0] = args[0];
                temp[1] = args[2];
                temp[2] = NULL;
                if (execvp(temp[0], temp) == -1) {
                    perror("lsh");
                }
            }
            else if (tokencount >= 4 && !strcmp(args[2], ">")) {
                char lines[1000][256];
                int linecount = 0;
                FILE *input = fopen(args[1], "r");
                FILE *output = fopen(args[3], "w");
    
                if (!input || !output) {
                    perror("lsh: file open error");
                }
    
                while (fgets(lines[linecount], 256, input) != NULL) {
                    linecount++;
                }
    
                for (int i = 0; i < linecount; i++) {
                    fputs(lines[i], output);
                }
            }
            else if (execvp(args[0], args) == -1) {
                perror("lsh");
            }
            exit(0);
        }
        else if (pid < 0) {
            perror("lsh");
        }
        else {
            // parent process
            jobadd(pid,! background);
        }
    
        return 1;
    }
    
    int lsh_help(char **args);
    
    int
    lsh_cd(char **args)
    {
        if (args[1] == NULL) {
            fprintf(stderr, "lsh: expected argument to \"cd\"\n");
        }
        else {
            if (chdir(args[1]) != 0) {
                perror("lsh");
            }
        }
        return 1;
    }
    
    int
    lsh_exit(char **args)
    {
        return 0;
    }
    
    int
    lsh_echo(char **args)
    {
        if (!strcmp(args[1], "-n")) {
            for (int i = 2; i < tokencount; i++) {
                printf("%s ", args[i]);
            }
        }
        else {
            for (int i = 1; i < tokencount; i++) {
                printf("%s ", args[i]);
            }
            printf("\n");
        }
        return 1;
    }
    
    int
    lsh_record(char **args)
    {
        int i = 1;
        node *go = top;
    
        while (go != NULL) {
            printf("%d: %s\n", i, go->str);
            go = go->next;
            i++;
        }
    
        return 1;
    }
    
    int
    lsh_replay(char **args)
    {
        node *go;
    
        go = top;
    
        for (int i = 0; i < atoi(args[1]) - 1; i++) {
            go = go->next;
        }
        node *add = ALLOCP(add);
    
        add->next = NULL;
        strcpy(add->str, go->str);
        cur->next = add;
        cur = cur->next;
    
        char **command = split_line(go->str);
    
        pid_t wpid;
        int state;
    
        fflush(stdout);
        fflush(stderr);
        pid_t pd = fork();
    
        if (pd == 0) {
            if (execvp(command[0], command) == -1) {
                perror("lsh");
            }
            exit(EXIT_FAILURE);
        }
        else if (pd < 0) {
            perror("lsh");
        }
        else {
            do {
                wpid = waitpid(pd, &state, WUNTRACED);
                dbgprt("lsh_replay: pd=%d wpid=%d state=%8.8X\n",
                    pd,wpid,state);
                if (wpid != pd)
                    continue;
            } while (!WIFEXITED(state) && !WIFSIGNALED(state));
        }
    
        return 1;
    }
    
    int
    lsh_mypid(char **args)
    {
        int pid = getpid();
    
        if (!strcmp(args[1], "-i")) {
            printf("%d", pid);
        }
        else if (!strcmp(args[1], "-p")) {
            FILE *file;
            char path[30] = "/proc/";
            char content[512];
            char *token;
    
            strcat(path, args[2]);
            strcat(path, "/stat");
            file = fopen(path, "r");
            if (file) {
                int i = 1;
    
                fgets(content, 512, file);
                token = strtok(content, TOK_DELIM);
                while (i < 4) {
                    token = strtok(NULL, TOK_DELIM);
                    i++;
                }
                fclose(file);
            }
            else {
                printf("not existed");
            }
    
            printf("%d", atoi(token));
        }
        else if (!strcmp(args[1], "-c")) {
            FILE *file;
            char path[30] = "/proc/";
            char content[512];
            char *token;
    
            strcat(path, args[2]);
            strcat(path, "/task/");
            strcat(path, args[2]);
            strcat(path, "/children");
            file = fopen(path, "r");
            if (file) {
                fgets(content, 512, file);
                token = strtok(content, TOK_DELIM);
                printf("%s", token);
                while (token != NULL) {
                    token = strtok(NULL, TOK_DELIM);
                    printf("%s", token);
                }
                fclose(file);
            }
            else {
                fprintf(stderr, "can not open the file\n");
            }
            return 1;
    
        }
        return 1;
    }
    
    /*
      List of builtin commands, followed by their corresponding functions.
     */
    struct builtin builtin_list[] = {
        CMD(cd,"change current directory"),
        CMD(help,"internal command help"),
        CMD(exit,"exit shell"),
        CMD(echo,"echo arguments"),
        CMD(record,"show command history"),
        CMDX(replay,NULL,"replay Nth command in history"),
        CMD(mypid,"print shell pid"),
        CMD(jobs,"list background jobs"),
        CMD(wait,"wait for background jobs"),
    };
    
    #define FORALLCMD \
        struct builtin *cmd = builtin_list; \
        cmd < &builtin_list[sizeof(builtin_list) / sizeof(builtin_list[0])]; \
        ++cmd
    
    int
    lsh_help(char **args)
    {
    
        printf("Stephen Brennan's LSH\n");
        printf("Type program names and arguments, and hit enter.\n");
        printf("The following are built in:\n");
    
        for (FORALLCMD)
            printf("  %s -- %s\n", cmd->cmd_str, cmd->cmd_help);
    
        printf("Use the man command for information on other programs.\n");
    
        return 1;
    }
    
    int
    lsh_execute(char **args)
    {
        int builtin = 0;
        int ret = 1;
    
        // empty command ...
        if (args[0] == NULL)
            return ret;
    
        for (FORALLCMD) {
            if (strcmp(args[0], cmd->cmd_str) == 0) {
                builtin = 1;
                if (cmd->cmd_func != NULL)
                    ret = cmd->cmd_func(args);
                break;
            }
        }
    
        if (! builtin)
            ret = lsh_launch(args);
    
        return ret;
    }
    
    // replaytrim -- limit history
    void
    replaytrim(void)
    {
    
        for (;  counter > 16;  --counter) {
            // unlink the oldest command
            node *tmp = top;
            top = tmp->next;
    
            // adjust the tail
            if (tmp == cur)
                cur = top;
    
            // free its data
            free(tmp->str);
            free(tmp);
        }
    }
    
    // replayadd -- add command to the replay buffer
    void
    replayadd(char *line)
    {
    
        node *m = ALLOCP(m);
    
        m->str = line;
        m->next = NULL;
    
        if (cur != NULL)
            cur->next = m;
        else
            top = m;
    
        cur = m;
    
        ++counter;
    
        replaytrim();
    }
    
    void
    shell_loop(void)
    {
        char *line = NULL;
        char **args = NULL;
    // NOTE/BUG: don't use fixed/hardwired buffers
    #if 0
        char *allline = malloc(64);
    #else
        char *repline = NULL;
    #endif
        int state = 1;
    
        while (state == 1) {
            jobreap();
    
            printf(">>>$ ");
            fflush(stdout);
    
            FREE(line);
            line = read_line();
    
            jobreap();
    
            repline = strdup(line);
    
            FREE(args);
            args = split_line(line);
    
            // space or \t
            if (args[0] == NULL) {
                FREE(repline);
                continue;
            }
    
            // not replay
            if (strcmp(args[0], "replay") != 0) {
                replayadd(repline);
                state = lsh_execute(args);
                continue;
            }
    
            // replay command
            else {
                // get index into replay list (origin 1)
                int argidx;
                if (args[1] != NULL)
                    argidx = atoi(args[1]);
                else
                    argidx = counter + 1;
    
                // convert to origin 0
                argidx -= 1;
    
                node *go = top;
                node *prev = NULL;
    
                // find match in replay list
                for (int i = 0;  go != NULL; i++, go = go->next) {
                    if (i == argidx)
                        break;
                    prev = go;
                }
    
                FREE(args);
                FREE(repline);
    
                if (go == NULL) {
                    printf("replay command not found in history -- %d\n",argidx);
                    continue;
                }
    
                // move replay entry to tail of the list
                do {
                    // already at the end of list
                    if (go == cur)
                        break;
    
                    // unlink entry from middle of list
                    if (prev != NULL)
                        prev->next = go->next;
                    // unlink entry from front of list
                    else
                        top = go->next;
    
                    // make it the newest entry
                    cur->next = go;
                    go->next = NULL;
                    cur = go;
                } while (0);
    
                repline = strdup(go->str);
                args = split_line(repline);
    
                state = lsh_execute(args);
            }
        }
    }
    
    int
    main()
    {
    
    #if 1
        setlinebuf(stdout);
    #endif
    
        shell_loop();
    
        return EXIT_SUCCESS;
    }