高造擎
高造擎

Reputation: 101

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.

Upvotes: 0

Views: 1014

Answers (1)

Craig Estey
Craig Estey

Reputation: 33601

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;
}

Upvotes: 1

Related Questions