Subversion Repositories Code-Repo

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
141 Kevin 1
/*
2
 * esh - the 'pluggable' shell.
3
 *
4
 * Developed by Godmar Back for CS 3214 Fall 2009
5
 * Virginia Tech.
6
 */
7
#include <stdio.h>
8
#include <readline/readline.h>
9
#include <unistd.h>
10
#include <sys/wait.h>
11
#include <assert.h>
12
#include <sys/types.h>
13
#include <sys/stat.h>
14
#include <fcntl.h>
15
 
16
#include "esh.h"
17
#include "esh-sys-utils.h"
18
 
19
// Function declarations
20
static void usage(char *progname);
21
static char * build_prompt_from_plugins(void);
22
 
23
static struct list *get_job_list(void);
24
static struct esh_pipeline *job_from_jid(int jid);
25
static struct esh_pipeline *job_from_pgrp(pid_t pgrp);
26
static struct esh_command *cmd_from_pid(pid_t pid);
27
 
28
static void print_jobs(void);
29
static void purge_jobs(void);
30
static void wait_for_job(struct esh_pipeline *pipeline,bool exists_in_job_list);
31
static void send_signal_to_job(int jid, int sig);
32
static void bring_to_foreground(int jid);
33
static void give_terminal_to(pid_t pgrp, struct termios *pg_tty_state);
34
 
35
static void sigchld_handler(int sig, siginfo_t *info, void *_ctxt);
36
 
37
static int job_next_id = 1;
38
static struct termios *sys_tty;
39
static pid_t shell_pgrp;
40
 
41
// List of all jobs running in the current shell
42
static struct esh_command_line *job_list;
43
 
44
/* The shell object plugins use.
45
 * Some methods are set to defaults. */
46
struct esh_shell shell = {
47
    .get_jobs = get_job_list,
48
    .get_job_from_jid = job_from_jid,
49
    .get_job_from_pgrp = job_from_pgrp,
50
    .get_cmd_from_pid = cmd_from_pid,
51
    .build_prompt = build_prompt_from_plugins,
52
    .readline = readline,       /* GNU readline(3) */ 
53
    .parse_command_line = esh_parse_command_line /* Default parser */
54
};
55
 
56
int main(int ac, char *av[]) {
57
    int opt;
58
    list_init(&esh_plugin_list);
59
    int i = 0;
60
 
61
    // Create an empty list of pipelines (jobs)
62
    job_list = esh_command_line_create_empty();
63
    // Obtain an empty (sane) terminal
64
    sys_tty = esh_sys_tty_init();
65
    // Store the process group of the shell
66
    shell_pgrp = getpgrp();
67
 
68
    /* Process command-line arguments. See getopt(3) */
69
    while ((opt = getopt(ac, av, "hp:")) > 0) {
70
        switch (opt) {
71
        case 'h':
72
            usage(av[0]);
73
            break;
74
 
75
        case 'p':
76
            esh_plugin_load_from_directory(optarg);
77
            break;
78
        }
79
    }
80
 
81
    esh_plugin_initialize(&shell);
82
 
83
    /* Read/eval loop. */
84
    for (;;) {
85
        // Ensure that the shell has access to input/output before prompting
86
        give_terminal_to(shell_pgrp, sys_tty);
87
 
88
        /* Do not output a prompt unless shell's stdin is a terminal */
89
        char * prompt = isatty(0) ? shell.build_prompt() : NULL;
90
        char * cmdline = shell.readline(prompt);
91
        free (prompt);
92
 
93
        if (cmdline == NULL)  /* User typed EOF */
94
            break;
95
 
96
        struct esh_command_line * cline = shell.parse_command_line(cmdline);
97
        free (cmdline);
98
        if (cline == NULL)                  /* Error in command line */
99
            continue;
100
 
101
        if (list_empty(&cline->pipes)) {    /* User hit enter */
102
            esh_command_line_free(cline);
103
            continue;
104
        }
105
 
106
        esh_signal_sethandler(SIGCHLD, sigchld_handler);
107
        esh_signal_block(SIGCHLD);
108
 
109
        // Pop each pipeline, parse it, then execute the commands
110
        while (!list_empty(&cline->pipes)) {
111
            struct list_elem *l = list_pop_front(&cline->pipes);
112
            struct esh_pipeline *pipeline = list_entry(l, struct esh_pipeline, elem);
113
 
114
            // Initialize some variables in the pipeline
115
            pipeline->jid = 0;
116
            pipeline->pgrp = 0;
117
            memset(pipeline->cmd_string, 0, 256);
118
 
119
            // Initialize array of pipes
120
            int p_pipe_ind = 0;
121
            int p_pipe_max = list_size(&pipeline->commands) - 1;
122
            int **pipes = (int**) malloc(p_pipe_max*sizeof(int*));
123
            if (pipes == NULL) esh_sys_fatal_error("malloc: ");
124
            for (i = 0; i < p_pipe_max; i++) {
125
                pipes[i] = (int *) malloc(2);
126
                if (pipes[i] == NULL) esh_sys_fatal_error("malloc: ");
127
                if (pipe(pipes[i]) == -1) esh_sys_fatal_error("pipe: ");
128
            }
129
 
130
            // Concat the command string containing the parsed commands
131
            struct list_elem *c = list_begin(&pipeline->commands);
132
            for (; c != list_end(&pipeline->commands); c = list_next(c)) {
133
                struct esh_command *cmd = list_entry(c, struct esh_command, elem);
134
                char **p = cmd->argv;
135
                while (*p) {
136
                    strcat(pipeline->cmd_string, *p++);
137
                    if (*p)
138
                        strcat(pipeline->cmd_string, " ");
139
                }
140
                if (list_next(c) != list_tail(&pipeline->commands))
141
                    strcat(pipeline->cmd_string, " | ");    
142
            }
143
            // printf("Command string: %s\n", pipeline->cmd_string);
144
 
145
            // Get the first command
146
            c = list_begin(&pipeline->commands);
147
            struct esh_command *cmd = list_entry(c, struct esh_command, elem);
148
            char **p = cmd->argv;
149
 
150
            // Check if the first command is a built in command
151
            if (strcmp(*p, "jobs") == 0) {
152
                print_jobs();
153
                purge_jobs();
154
                continue;
155
            } else if (strcmp(*p, "fg") == 0) {
156
                bring_to_foreground(atoi(cmd->argv[1]));
157
                continue;
158
            } else if (strcmp(*p, "bg") == 0) {
159
                send_signal_to_job(atoi(cmd->argv[1]), SIGCONT);
160
                continue;
161
            } else if (strcmp(*p, "kill") == 0) {
162
                send_signal_to_job(atoi(cmd->argv[1]), SIGKILL);
163
                continue;
164
            } else if (strcmp(*p, "stop") == 0) {
165
                send_signal_to_job(atoi(cmd->argv[1]), SIGSTOP);
166
                continue;
167
            } else if (strcmp(*p, "exit") == 0) {
168
                exit(0);
169
            }
170
 
171
            // Otherwise parse and execute each command
172
            for (; c != list_end(&pipeline->commands); c = list_next(c)) {
173
                cmd = list_entry(c, struct esh_command, elem);
174
                p = cmd->argv;
175
 
176
                // If it is not a built in command, fork the process and let it run
177
                if ((cmd->pid = fork()) == 0) {
178
                    // Set the group process ID to the same value for each pipeline
179
                    setpgid(0,pipeline->pgrp);
180
                    esh_signal_unblock(SIGCHLD);
181
 
182
                    if (p_pipe_max) {
183
                        // Set up the pipelines if there are more than one command
184
                        if (p_pipe_ind == 0) {
185
                            // First command
186
                            if (dup2(pipes[p_pipe_ind][1], 1) < 0) 
187
                                esh_sys_fatal_error("dup2: ");
188
                            // Set stdin of the first process to the input file if it is specified
189
                            if (pipeline->iored_input) {
190
                                if (dup2(open(pipeline->iored_input, O_RDONLY), 0) < 0) 
191
                                    esh_sys_fatal_error("dup2: ");
192
                            }
193
                        } else if (p_pipe_ind == p_pipe_max) {
194
                            // Last command
195
                            if (dup2(pipes[p_pipe_ind-1][0], 0) < 0) 
196
                                esh_sys_fatal_error("dup2: ");
197
                            // Set stdout of the final process to the output file if it is specified
198
                            if (pipeline->iored_output) {
199
                                // Check if we are creating a new file or appending to an existing file
200
                                if (pipeline->append_to_output) {
201
                                    if (dup2(open(pipeline->iored_output, O_WRONLY|O_APPEND), 1) < 0) 
202
                                        esh_sys_fatal_error("dup2: ");
203
                                } else {
204
                                    if (dup2(creat(pipeline->iored_output, 0600), 1) < 0) 
205
                                        esh_sys_fatal_error("dup2: ");
206
                                }
207
                            }
208
                        } else {
209
                            // All other commands in between
210
                            if (dup2(pipes[p_pipe_ind-1][0], 0) < 0) 
211
                                esh_sys_fatal_error("dup2: ");
212
                            if (dup2(pipes[p_pipe_ind][1], 1) < 0) 
213
                                esh_sys_fatal_error("dup2: ");
214
                        }
215
                        // Close all  pipes
216
                        for (i = 0; i < p_pipe_max; i++) {
217
                            if (close(pipes[i][0]) == -1)
218
                                esh_sys_fatal_error("close: ");
219
                            if (close(pipes[i][1]) == -1)
220
                                esh_sys_fatal_error("close: ");
221
                        }
222
                    } else {
223
                        // If there is only one command, set stdin to input file if it is specified
224
                        if (pipeline->iored_input) {
225
                            if (dup2(open(pipeline->iored_input, O_RDONLY), 0) < 0) 
226
                                esh_sys_fatal_error("dup2: ");
227
                        }
228
                        // Set stdout of the process to the output file if it is specified
229
                        if (pipeline->iored_output) {
230
                            // Check if we are creating a new file or appending to an existing file
231
                            if (pipeline->append_to_output) {
232
                                if (dup2(open(pipeline->iored_output, O_WRONLY|O_APPEND), 1) < 0) 
233
                                    esh_sys_fatal_error("dup2: ");
234
                            } else {
235
                                if (dup2(creat(pipeline->iored_output, 0600), 1) < 0) 
236
                                    esh_sys_fatal_error("dup2: ");
237
                            }
238
                        }
239
                    }
240
 
241
                    if (execvp(cmd->argv[0],&(cmd->argv[0])) < 0)
242
                        printf("%s: Command not found.\n",cmd->argv[0]);
243
                    return 1;
244
                } 
245
                if (cmd->pid == -1)
246
                    esh_sys_fatal_error("fork: ");
247
 
248
                // Make sure the terminal is in its own process group
249
                if (setpgid(0,0) == -1) 
250
                    esh_sys_error("setpgid: ");
251
 
252
                // Store the process group ID
253
                if (pipeline->pgrp == 0)
254
                    pipeline->pgrp = cmd->pid;
255
 
256
                // Increment the pipe counter
257
                if (p_pipe_max) {
258
                    p_pipe_ind++;
259
                }
260
            }
261
 
262
            // Close all  pipes
263
            for (i = 0; i < p_pipe_max; i++) {
264
                if (close(pipes[i][0]) == -1)
265
                    esh_sys_fatal_error("close: ");
266
                if (close(pipes[i][1]) == -1)
267
                    esh_sys_fatal_error("close: ");
268
            }
269
 
270
            if (!pipeline->bg_job) {
271
                // If the process is going to be executed in the background, give it the terminal
272
                pipeline->status = FOREGROUND;
273
                esh_sys_tty_save(sys_tty);
274
                give_terminal_to(pipeline->pgrp, NULL);
275
                wait_for_job(pipeline, false);
276
            } else {
277
                // Otherwise save the job in the job list and move on
278
                pipeline->status = BACKGROUND;
279
                // Assign the pipeline a job ID
280
                pipeline->jid = job_next_id;
281
                job_next_id++;
282
                list_push_back(shell.get_jobs(),l);
283
                printf("[%d] %d\n", pipeline->jid, pipeline->pgrp);
284
            }
285
        }
286
 
287
        esh_signal_unblock(SIGCHLD);
288
        esh_command_line_free(cline);
289
    }
290
    return 0;
291
}
292
 
293
static void usage(char *progname) {
294
    printf("Usage: %s -h\n"
295
        " -h            print this help\n"
296
        " -p  plugindir directory from which to load plug-ins\n",
297
        progname);
298
 
299
    exit(EXIT_SUCCESS);
300
}
301
 
302
/* Build a prompt by assembling fragments from loaded plugins that 
303
 * implement 'make_prompt.'
304
 *
305
 * This function demonstrates how to iterate over all loaded plugins.
306
 */
307
static char * build_prompt_from_plugins(void) {
308
    char *prompt = NULL;
309
    struct list_elem * e = list_begin(&esh_plugin_list);
310
 
311
    for (; e != list_end(&esh_plugin_list); e = list_next(e)) {
312
        struct esh_plugin *plugin = list_entry(e, struct esh_plugin, elem);
313
 
314
        if (plugin->make_prompt == NULL)
315
            continue;
316
 
317
        /* append prompt fragment created by plug-in */
318
        char * p = plugin->make_prompt();
319
        if (prompt == NULL) {
320
            prompt = p;
321
        } else {
322
            prompt = realloc(prompt, strlen(prompt) + strlen(p) + 1);
323
            strcat(prompt, p);
324
            free(p);
325
        }
326
    }
327
 
328
    /* default prompt */
329
    if (prompt == NULL)
330
        prompt = strdup("esh> ");
331
 
332
    return prompt;
333
}
334
 
335
/* Shell function to return the list of jobs running in the shell */
336
static struct list *get_job_list(void) {
337
    return &job_list->pipes;
338
}
339
 
340
/* Shell function to return a job given a job ID */
341
static struct esh_pipeline *job_from_jid(int jid) {
342
    struct list_elem * p = list_begin (get_job_list()); 
343
    for (; p != list_end (get_job_list()); p = list_next (p)) {
344
        struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem);
345
        if (pipe->jid == jid)
346
            return pipe;
347
    }
348
    return NULL;
349
}
350
 
351
/* Shell function to return a job given a process group */
352
static struct esh_pipeline *job_from_pgrp(pid_t pgrp) {
353
    struct list_elem * p = list_begin (get_job_list()); 
354
    for (; p != list_end (get_job_list()); p = list_next (p)) {
355
        struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem);
356
        if (pipe->pgrp == pgrp)
357
            return pipe;
358
    }
359
    return NULL;
360
}
361
 
362
/* Shell function to return a command given a PID */
363
static struct esh_command *cmd_from_pid(pid_t pid) {
364
    struct list_elem * p = list_begin (get_job_list()); 
365
    for (; p != list_end (get_job_list()); p = list_next (p)) {
366
        struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem);
367
        struct list_elem *c = list_begin(&pipe->commands);
368
        for (; c != list_end(&pipe->commands); c = list_next(c)) {
369
            struct esh_command *cmd = list_entry(c, struct esh_command, elem);
370
            if (cmd->pid == pid)
371
                return cmd;
372
        }
373
    }
374
    return NULL;
375
}
376
 
377
/* Prints out the current list of jobs in the shell and their statuses */
378
static void print_jobs(void) {
379
    struct list_elem * p = list_begin (shell.get_jobs()); 
380
    for (; p != list_end (shell.get_jobs()); p = list_next (p)) {
381
        struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem);
382
 
383
        // For each job, print out [JobID] Status (Command String)
384
        printf("[%d] ", pipe->jid);
385
        if (pipe->status == FOREGROUND || pipe->status == BACKGROUND)
386
            printf("Running  ");
387
        else if (pipe->status == COMPLETED)
388
            printf("Done  ");
389
        else if (pipe->status == TERMINATED)
390
            printf("Terminated  ");
391
        else if (pipe->status == STOPPED)
392
            printf("Stopped  ");
393
        else
394
            printf("Unknown State  ");
395
 
396
        printf("(%s)\n", pipe->cmd_string);
397
    }
398
}
399
 
400
/* Removes completed or terminated jobs from the job list */
401
static void purge_jobs(void) {
402
    struct list_elem * p = list_begin (shell.get_jobs()); 
403
    for (; p != list_end (shell.get_jobs()); p = list_next (p)) {
404
        struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem);
405
        if (pipe->status == COMPLETED || pipe->status == TERMINATED) {
406
            list_remove(p);
407
        }
408
    }
409
    // Also reset the job ID if the job list is empty
410
    if (list_empty(shell.get_jobs())) {
411
        job_next_id = 1;
412
    }
413
}
414
 
415
/* Waits for the specified job to finish */
416
static void wait_for_job(struct esh_pipeline *pipeline, bool exists_in_job_list) {
417
    assert(esh_signal_is_blocked(SIGCHLD));
418
 
419
    // Loop till all commands in the pipeline is done executing
420
    while (pipeline->status == FOREGROUND && !list_empty(&pipeline->commands)) {
421
        int status;
422
 
423
        // Wait till a child process exits
424
        pid_t child = waitpid(-1, &status, WUNTRACED);
425
        if (child != -1) {
426
            // If child has exited or terminated, remove the child from the pipeline
427
            if (WIFEXITED(status) || WIFSIGNALED(status)) {
428
                struct list_elem * e = list_begin (&pipeline->commands); 
429
                for (; e != list_end (&pipeline->commands); e = list_next (e)) {
430
                    struct esh_command *cmd = list_entry(e, struct esh_command, elem);
431
                    if (child == cmd->pid)
432
                        list_remove(e);
433
                }
434
            }
435
            // If child was stopped (INTPSTP), put it in the job list and print out status
436
            if (WIFSTOPPED(status)) {
437
                pipeline->status = STOPPED;
438
                if (pipeline->jid == 0) {
439
                    pipeline->jid = job_next_id;
440
                    job_next_id++;
441
                }
442
                if (!exists_in_job_list)
443
                    list_push_back(shell.get_jobs(), &pipeline->elem);
444
                printf("\n[%d]  Stopped  (%s)\n", pipeline->jid, pipeline->cmd_string);
445
                return;
446
            }
447
        }
448
    }
449
    pipeline->status = COMPLETED;
450
}
451
 
452
/* Sends the specified signal to the specified job ID */
453
static void send_signal_to_job(int jid, int sig) {
454
    struct esh_pipeline *pipe = job_from_jid(jid);
455
    if (killpg(pipe->pgrp, sig) == -1)
456
        esh_sys_fatal_error("killpg: ");
457
}
458
 
459
/* Brings a job to the foreground */
460
static void bring_to_foreground(int jid) {
461
    struct esh_pipeline *pipe = job_from_jid(jid);
462
    printf("%s\r\n", pipe->cmd_string);
463
    pipe->status = FOREGROUND;
464
 
465
    // Give the foreground process the terminal
466
    give_terminal_to(pipe->pgrp, NULL);
467
    // Resume the process
468
    send_signal_to_job(jid, SIGCONT);
469
    // Wait for process to finish
470
    wait_for_job(pipe, true);
471
}
472
 
473
/* Gives the terminal to the specified process group */
474
static void give_terminal_to(pid_t pgrp, struct termios *pg_tty_state) {
475
    esh_signal_block(SIGTTOU);
476
    // printf("Terminal to process group %d\n", pgrp);
477
    int rc = tcsetpgrp(esh_sys_tty_getfd(), pgrp);
478
    if (rc == -1)
479
        esh_sys_fatal_error("tcsetpgrp: ");
480
 
481
    if (pg_tty_state)
482
        esh_sys_tty_restore(pg_tty_state);
483
    esh_signal_unblock(SIGTTOU);
484
}
485
 
486
/* Handler for SIGCHLD */
487
static void sigchld_handler(int sig, siginfo_t *info, void *_ctxt) {
488
    pid_t child;
489
    int status;
490
 
491
    assert(sig == SIGCHLD);
492
 
493
    while ((child = waitpid(-1, &status, WUNTRACED|WNOHANG|WCONTINUED)) > 0) {
494
        // printf("Received signal %d from process %d\n",sig, child);
495
        struct esh_command *cmd = shell.get_cmd_from_pid(child);
496
        // Check the status of the child and update status accordingly
497
        if (WIFEXITED(status)) {
498
            cmd->pipeline->status = COMPLETED;
499
        } else if (WIFSIGNALED(status)) {
500
            cmd->pipeline->status = TERMINATED;
501
        } else if (WIFCONTINUED(status)) {
502
            cmd->pipeline->status = BACKGROUND;
503
        } else if (WIFSTOPPED(status)) {
504
            cmd->pipeline->status = STOPPED;
505
        }
506
    }
507
}