Question: 1. Modify this project so that you can use 'ls' instead of '/bin/ls' * The command starts with a dot, such as ./shell * The
| 1. Modify this project so that you can use 'ls' instead of '/bin/ls' |
| * The command starts with a dot, such as ./shell |
| * The command is somewhere in the ENVPATH, such as l |
| Test: ./shell -test path |
| 2. Modify this project so that the command prompt includes a counter that |
| increments for each command executed (starting with 1). Your |
| program should use the following prompt format: |
| "Shell(pid=%1)%2> " %1=process pid %2=counter |
| Test: ./shell -test counter |
| 3. Modify this project so that '!NN' re-executes the n'th command entered. |
| no more than 9 values will be entered. If it goes over 9, loop back to 1 (use a circular queue structure) |
| Shell(...)1> ls |
| Shell(...)2> !1 # re-executes ls |
| Shell(...)3> !2 # re-executes ls |
| Test: ./shell -test rerun |
| 4. Modify the project so that it uses waitpid instead of wait. |
| The parent process must wait for its direct child to finish executing before showing the next shell prompt. |
| 5. Create a new builtin command 'sub' that forks the program to create |
| a new subshell. The parent shell should run the imtheparent() |
| function just as if we were running an external command (like 'ls'). |
| ./shell |
| Shell(.n1..)1> newsub |
| Shell(.n2..)1> exit # Exits sub shell |
| Shell(.n1..)1> exit # Exits back to 'real' shell |
| 6. Create a new global variable to prevent a subshell from invoking |
| a subshell invoking a subshell (i.e., more than 3 levels deep): |
| ./shell |
| Shell(.n1..)1> newsub |
| Shell(.n2..)1> newsub |
| Shell(.n3..)1> newsub # prints "Too deep!" to stderr |
// This is the shell.c
Test: ./shell -test sub
#include
#include
#include
#include
#include
#include
#include
#include "smp1_tests.h" /* Built-in test system */
/* DEFINE SECTION */
#define SHELL_BUFFER_SIZE 256 /* Size of the Shell input buffer */
#define SHELL_MAX_ARGS 8 /* Maximum number of arguments parsed */
#define SHELL_HISTORY_SIZE 10
/* VARIABLE SECTION */
enum { STATE_SPACE, STATE_NON_SPACE }; /* Parser states */
char shell_history[SHELL_HISTORY_SIZE][SHELL_BUFFER_SIZE];
int shell_history_next_index = 0;
int imthechild(const char *path_to_exec, char *const args[])
{
// TO-DO P5.1
return execvp(path_to_exec, args) ? -1 : 0;
}
void imtheparent(pid_t child_pid, int run_in_background)
{
int child_return_val, child_error_code;
/* fork returned a positive pid so we are the parent */
fprintf(stderr,
" Parent says 'child process has been forked with pid=%d' ",
child_pid);
if (run_in_background) {
fprintf(stderr,
" Parent says 'run_in_background=1 ... so we're not waiting for the child' ");
return;
}
// TO-DO P5.4
wait(&child_return_val);
/* Use the WEXITSTATUS to extract the status code from the return value */
child_error_code = WEXITSTATUS(child_return_val);
fprintf(stderr,
" Parent says 'wait() returned so the child with pid=%d is finished.' ",
child_pid);
if (child_error_code != 0) {
fprintf(stderr,
" Parent says 'Child process %d failed with code %d' ",
child_pid, child_error_code);
}
}
int get_shell_command(char *buffer, int size)
{
int i = 0;
char c;
while (i < size - 1 && (c = getchar()) != EOF && c != ' ') {
buffer[i++] = c;
}
buffer[i] = '\0';
return i;
}
void parse_command(char *command, char **exec_args, int *exec_bg)
{
int argc = 0;
*exec_bg = 0;
while (*command != '\0') {
/* Strip whitespace. */
while (isspace(*command)) {
++command;
}
if (*command == '\0') {
break;
}
/* Save the argument. */
if (argc < SHELL_MAX_ARGS) {
exec_args[argc++] = command;
}
while (*command != '\0' && !isspace(*command)) {
++command;
}
if (*command != '\0') {
*command++ = '\0';
}
}
exec_args[argc] = NULL;
/* Check for background execution request. */
if (argc > 0 && !strcmp(exec_args[argc - 1], "&")) {
*exec_bg = 1;
exec_args[--argc] = NULL;
}
}
void run_shell_command;
int main(int argc, char **argv)
{
pid_t shell_pid, pid_from_fork;
int n_read, i, exec_argc, parser_state, run_in_background;
char buffer[SHELL_BUFFER_SIZE];
char *exec_argv[SHELL_MAX_ARGS + 1];
// TO-DO new variables for P5.2, P5.3, P5.6
if (argc > 1 && !strcmp(argv[1], "-test"))
{
return run_smp1_tests(argc - 1, argv + 1);
}
shell_pid = getpid();
int command_counter = 1;
while (1)
{
/* The Shell runs in an infinite loop, processing input. */
if (exec_argc > 0)
{
command_counter++;
}
// TO-DO P5.2
fprintf(stdout, "Shell(pid=%d)%d> ", shell_pid, command_counter);
fflush(stdout);
if (fgets(buffer, SHELL_BUFFER_SIZE, stdin) == NULL)
return EXIT_SUCCESS;
n_read = strlen(buffer);
run_in_background = n_read > 2 && buffer[n_read - 2] == '&';
buffer[n_read - run_in_background - 1] = ' ';
// TO-DO P5.3
/* Parse the arguments: the first argument is the file or command *
* we want to run. */
parser_state = STATE_SPACE;
for (exec_argc = 0, i = 0;
(buffer[i] != ' ') && (exec_argc < SHELL_MAX_ARGS); i++)
{
if (!isspace(buffer[i]))
{
if (parser_state == STATE_SPACE)
exec_argv[exec_argc++] = &buffer[i];
parser_state = STATE_NON_SPACE;
}
else
{
buffer[i] = '\0';
parser_state = STATE_SPACE;
}
}
buffer[i] = '\0'; /* Terminate input, overwriting the '&' if it exists */
if (!exec_argc)
continue;
exec_argv[exec_argc] = NULL;
if (!strcmp(exec_argv[0], "exit"))
{
printf("Exiting process %d ", shell_pid);
return EXIT_SUCCESS; /* End Shell program */
} else if (!strcmp(exec_argv[0], "cd") && exec_argc > 1)
{
if (chdir(exec_argv[1]))
fprintf(stderr, "cd: failed to chdir %s ", exec_argv[1]);
}
else
{
pid_from_fork = fork();
if (pid_from_fork < 0)
{
fprintf(stderr, "fork failed ");
continue;
}
if (pid_from_fork == 0)
{
// TO-DO P5.6
return imthechild(exec_argv[0], &exec_argv[0]);
/* Exit from main. */
}
else
{
imtheparent(pid_from_fork, run_in_background);
}
}
}
return EXIT_SUCCESS;
}
/* end main() */
//This is the smp1._tests.c
#define _GNU_SOURCE
#include
#undef _GNU_SOURCE
#include
#include
#include
#include "testrunner.h"
#include "smp1_tests.h"
#define quit_if(cond) do {if (cond) exit(EXIT_FAILURE);} while(0)
/* Prepare input, reroute file descriptors, and run the program. */
void run_test(const char *input, int argc, char **argv)
{
FILE *in = fopen("smp1.in", "w");
fprintf(in, input);
fclose(in);
freopen("smp1.in", "r", stdin );
freopen("smp1.out", "w", stdout);
freopen("smp1.err", "w", stderr);
/* Run the program */
quit_if(main(argc, argv) != EXIT_SUCCESS);
fclose(stdout);
fclose(stderr);
}
/* P5.1: Test of executing commands in the path */
int test_path(int argc, char **argv)
{
char *args[] = { "./shell", NULL };
FILE *out, *err;
int pid_tmp;
/* Run the test */
run_test("ls /bin/ls exit ", 1, args);
/* Check output */
err = fopen("smp1.err", "r");
quit_if(fscanf(err, " Parent says 'child process has been forked with pid=%d' "
" Parent says 'wait() returned so the child with pid=%d is finished.' "
" Parent says 'child process has been forked with pid=%d' "
" Parent says 'wait() returned so the child with pid=%d is finished.' ",
&pid_tmp, &pid_tmp, &pid_tmp, &pid_tmp) != 4);
quit_if(!feof(err));
fclose(err);
return EXIT_SUCCESS;
}
/* P5.2: Test of command line counter */
int test_counter(int argc, char **argv)
{
char *args[] = { "./shell", NULL };
FILE *out, *err;
int pid_tmp;
/* Run the test */
run_test(" /bin/true exit ", 1, args);
/* Check output */
out = fopen("smp1.out", "r");
quit_if(fscanf(out, "Shell(pid=%d)1> Shell(pid=%d)1> Shell(pid=%d)2> Exiting process %d ", &pid_tmp, &pid_tmp, &pid_tmp, &pid_tmp) != 4);
quit_if(!feof(out));
fclose(out);
return EXIT_SUCCESS;
}
/* P5.3: Test of re-executing earlier commands */
int test_rerun(int argc, char **argv)
{
char *args[] = { "./shell", NULL };
FILE *out, *err;
int pid_tmp;
/* Run the test */
run_test("/bin/echo test !1 exit ", 1, args);
/* Check output */
out = fopen("smp1.out", "r");
quit_if(fscanf(out, "Shell(pid=%d)1> test Shell(pid=%d)2> test Shell(pid=%d)3> Exiting process %d ", &pid_tmp, &pid_tmp, &pid_tmp, &pid_tmp) != 4);
quit_if(!feof(out));
fclose(out);
return EXIT_SUCCESS;
}
/* P5.5: Test of depth-limited sub */
int test_sub(int argc, char **argv)
{
char *args[] = { "./shell", NULL };
FILE *out, *err;
int pids[4], warned_too_deep;
/* Run the test */
run_test("newsub newsub newsub exit exit exit ", 1, args);
/* Check output */
out = fopen("smp1.out", "r");
err = fopen("smp1.err", "r");
/* First, check that the subshells were invoked. */
fscanf(out, "Shell(pid=%d)1> Shell(pid=%d)1> Shell(pid=%d)1> Shell(pid=%d)2> ", &pids[0], &pids[1], &pids[2], &pids[3]);
quit_if(!((pids[0] != pids[1]) && (pids[1] != pids[2]) && (pids[0] != pids[2]) && (pids[2] == pids[3])));
/* Next, check for the "Too deep!" message: */
warned_too_deep = 0;
/* Use a while loop because multiple processes write to stderr concurrently. */
while (!warned_too_deep && !feof(err)) {
char too_deep[11];
fgets(too_deep, 11, err);
if (!strncmp(too_deep, "Too deep! ", 10))
warned_too_deep = 1;
}
quit_if(!warned_too_deep);
fclose(out);
fclose(err);
return EXIT_SUCCESS;
}
/*
* Main entry point for SMP1 test harness
*/
int run_smp1_tests(int argc, char **argv)
{
/* Tests can be invoked by matching their name or their suite name
or 'all' */
testentry_t tests[] = {
{ "newsub", "smp1", test_sub },
{ "rerun", "smp1", test_rerun },
{ "counter", "smp1", test_counter },
{ "path", "smp1", test_path } };
int result = run_testrunner(argc, argv, tests, sizeof(tests) / sizeof (testentry_t));
unlink("smp1.in");
unlink("smp1.out");
unlink("smp1.err");
return result;
}
Step by Step Solution
There are 3 Steps involved in it
Get step-by-step solutions from verified subject matter experts
