Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

This post involves 7 text files quoted in here. At the end there are the questions to answer by changing FILE 5: shell.c. Questions 11

This post involves 7 text files quoted in here. At the end there are the questions to answer by changing FILE 5: shell.c. Questions 11 o 16 are the most important questions.

****************************

FILE 1: assignment2_tests.c

****************************

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/

#define _GNU_SOURCE

#include

#undef _GNU_SOURCE

#include

#include

#include

#include "testrunner.h"

#include "assignment2_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)

{

/* Prepare input */

FILE *in = fopen("assignment2.in", "w");

fprintf(in, input);

fclose(in);

/* Reroute standard file descriptors */

freopen("assignment2.in", "r", stdin );

freopen("assignment2.out", "w", stdout);

freopen("assignment2.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("assignment2.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("assignment2.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("assignment2.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("sub sub sub exit exit exit ", 1, args);

/* Check output */

out = fopen("assignment2.out", "r");

err = fopen("assignment2.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 ASSIGNMENT2 test harness

*/

int run_assignment2_tests(int argc, char **argv)

{

/* Tests can be invoked by matching their name or their suite name

or 'all' */

testentry_t tests[] = {

{ "sub", "assignment2", test_sub },

{ "rerun", "assignment2", test_rerun },

{ "counter", "assignment2", test_counter },

{ "path", "assignment2", test_path } };

int result = run_testrunner(argc, argv, tests, sizeof(tests) / sizeof (testentry_t));

unlink("assignment2.in");

unlink("assignment2.out");

unlink("assignment2.err");

return result;

}

***************************

FILE 2: assignment2_tests.h

***************************

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/

int run_assignment2_tests(int argc, char **argv);

int main(int argc, char **argv);

***************************

FILE 3: b.sh

***************************

#!/bin/bash

for i in `seq 10 -1 1`; do

echo -n "$i($$).."

sleep 1

done

echo "Blastoff $$ Complete"

***************************

FILE 4: Makefile

***************************

CC = gcc

CCOPTS = -c -g

LINKOPTS = -g

all: shell

shell: shell.o assignment2_tests.o testrunner.o

$(CC) $(LINKOPTS) -o $@ $^

shell.o: shell.c assignment2_tests.h

$(CC) $(CCOPTS) -o $@ shell.c

testrunner.o: testrunner.c testrunner.h

$(CC) $(CCOPTS) -o $@ $<

assignment2_tests.o: assignment2_tests.c assignment2_tests.h testrunner.h

$(CC) $(CCOPTS) -o $@ $<

test: shell

./shell -test -f0 all

clean:

rm -rf *.o shell shell.exe *~

***************************

FILE 5: shell.c

***************************

/* ASSIGNMENT2: Simple Shell */

/* LIBRARY SECTION */

#include /* Character types */

#include /* Standard buffered input/output */

#include /* Standard library functions */

#include /* String operations */

#include /* Data types */

#include /* Declarations for waiting */

#include /* Standard symbolic constants and types */

#include "assignment2_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 */

/* VARIABLE SECTION */

enum { STATE_SPACE, STATE_NON_SPACE }; /* Parser states */

int imthechild(const char *path_to_exec, char *const args[])

{

return execv(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;

}

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) {

/* Error: Child process failed. Most likely a failed exec */

fprintf(stderr,

" Parent says 'Child process %d failed with code %d' ",

child_pid, child_error_code);

}

}

/* MAIN PROCEDURE SECTION */

int main(int argc, char **argv)

{

pid_t shell_pid, pid_from_fork;

int n_read, i, exec_argc, parser_state, run_in_background;

/* buffer: The Shell's input buffer. */

char buffer[SHELL_BUFFER_SIZE];

/* exec_argv: Arguments passed to exec call including NULL terminator. */

char *exec_argv[SHELL_MAX_ARGS + 1];

/* Entrypoint for the testrunner program */

if (argc > 1 && !strcmp(argv[1], "-test")) {

return run_assignment2_tests(argc - 1, argv + 1);

}

/* Allow the Shell prompt to display the pid of this process */

shell_pid = getpid();

while (1) {

/* The Shell runs in an infinite loop, processing input. */

fprintf(stdout, "Shell(pid=%d)> ", shell_pid);

fflush(stdout);

/* Read a line of input. */

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] = ' ';

/* 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;

}

}

/* run_in_background is 1 if the input line's last character *

* is an ampersand (indicating background execution). */

buffer[i] = '\0'; /* Terminate input, overwriting the '&' if it exists */

/* If no command was given (empty line) the Shell just prints the prompt again */

if (!exec_argc)

continue;

/* Terminate the list of exec parameters with NULL */

exec_argv[exec_argc] = NULL;

/* If Shell runs 'exit' it exits the program. */

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) {

/* Running 'cd' changes the Shell's working directory. */

/* Alternative: try chdir inside a forked child: if(fork() == 0) { */

if (chdir(exec_argv[1]))

/* Error: change directory failed */

fprintf(stderr, "cd: failed to chdir %s ", exec_argv[1]);

/* End alternative: exit(EXIT_SUCCESS);} */

} else {

/* Execute Commands */

/* Try replacing 'fork()' with '0'. What happens? */

pid_from_fork = fork();

if (pid_from_fork < 0) {

/* Error: fork() failed. Unlikely, but possible (e.g. OS *

* kernel runs out of memory or process descriptors). */

fprintf(stderr, "fork failed ");

continue;

}

if (pid_from_fork == 0) {

return imthechild(exec_argv[0], &exec_argv[0]);

/* Exit from main. */

} else {

imtheparent(pid_from_fork, run_in_background);

/* Parent will continue around the loop. */

}

} /* end if */

} /* end while loop */

return EXIT_SUCCESS;

} /* end main() */

***************************

FILE 6: testrunner.c

***************************

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/

/*

A simple testrunner framework

Original Author: L. Angrave

*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "testrunner.h"

/* Constants */

#define false (0)

#define true (1)

#define test_killed (2)

/* defaults */

static int default_timeout_seconds=5;

static int timeout_seconds;

void set_testrunner_default_timeout(int s) {

assert(s>0);

default_timeout_seconds=s;

}

void set_testrunner_timeout(int s) {

assert(s>0);

timeout_seconds=s;

}

/* --- Helper macros and functions --- */

#define DIE(mesg) {fprintf(stderr," %s(%d):%s ",__fname__,__LINE__,mesg); exit(1);}

static int eql( char*s1, char*s2) {return s1&&s2&&!strcmp(s1,s2);}

/* Callback function for qsort on strings */

static int mystrcmp( const void *p1, const void *p2) {

return eql( ( char*)p1, ( char*)p2);

}

/* Stats of all tests run so far */

typedef struct

{

int ran, passed, failed;

} stats_t;

/* -- Signal handlers -- */

static pid_t child_pid;

static int sent_child_timeout_kill_signal;

static void kill_child_signal_handler(intsigno) {

if(!child_pid) return;

char m[]="-Timeout(Killing test process)-";

write(0,m,sizeof(m)-1);

kill(child_pid,SIGKILL);

sent_child_timeout_kill_signal=1;

}

/* Internal function to run a test as a forked child. The child process is terminated if it runs for more than a few seconds */

static int invoke_test_with_timelimit(testentry_t* test, int redirect_stdouterr,int argc, char **argv)

{

char fname[255];

int wait_status;

pid_t wait_val;

struct sigaction action;

assert(!child_pid);

assert(test && test->test_function && test->name);

set_testrunner_timeout(default_timeout_seconds);

errno=0;

child_pid = fork ();

if (child_pid == -1) {

fprintf(stderr,"-fork failed so running test inline-");

return test->test_function (argc, argv);

}

if (child_pid == 0)

{

if(redirect_stdouterr) {

snprintf(fname,(int)sizeof(fname),"stdout-%s.txt",test->name);

fname[sizeof(fname)-1]=0;

freopen(fname, "w", stdout);

memcpy(fname+3,"err",3);

freopen(fname, "w", stderr);

}

exit(test->test_function(argc,argv));

}else {

wait_status=-1;

sigemptyset(&action.sa_mask);

action.sa_handler=kill_child_signal_handler;

sigaction(SIGALRM,&action,NULL);

sent_child_timeout_kill_signal=0;

alarm(timeout_seconds);

wait_val = waitpid (child_pid, &wait_status, 0);

int child_exited_normally= WIFEXITED (wait_status);

int child_exit_value=WEXITSTATUS (wait_status);

int child_term_by_signal=WIFSIGNALED(wait_status);

int child_term_signal=WTERMSIG(wait_status);

if(child_term_by_signal) {

fprintf(stderr,"testrunner:Test terminated by signal %d ",child_term_signal);

fprintf(stderr,"testrunner:waitpid returned %d (child_pid=%d,wait_status=%d)",wait_val,child_pid,wait_status);

}

if(child_pid != wait_val)

fprintf(stderr,"testrunner: strange... wait_val != child_pid ");

int passed= (child_pid == wait_val) && (child_exit_value==0) && (child_exited_normally!=0);

alarm(0);

kill(child_pid,SIGKILL);

child_pid=0;

return sent_child_timeout_kill_signal ? test_killed : passed ? 0 : 1;

}

}

/*

* run a test and update the stats. The main guts of this functionality is provided by invoke_test_with_timelimit

* This outer wrapper updates thes output and statistics before and after running the test.

*/

static int

run_one_test (stats_t * stats, testentry_t * test, int redirect_stdouterr,int argc, char **argv)

{

int test_result;

assert (stats && test->name && argc > 0 && argv && *argv);

stats->ran++;

stats->failed++;

printf ("%2d.%-20s:", stats->ran, test->name);

fflush(stdout);

test_result=invoke_test_with_timelimit(test,redirect_stdouterr,argc,argv);

if (test_result == 0)

{

stats->failed--;

stats->passed++;

}

printf(":%s ", (test_result == 0 ? "pass" : test_result ==

2 ? "TIMEOUT * " : "FAIL *"));

return test_result!=0;

}

/* Help functionality to print out sorted list of test names and suite names */

static void print_targets(testentry_t tests[], int count) {

char**array;

char *previous;

int i;

array=(char**)calloc(sizeof(char*),count);

/* Sort the test names and print unique entries*/

for(i=0;i

qsort(array,count,sizeof(array[0]),mystrcmp);

printf(" Valid tests : all");

for(i=0,previous="";i

/* Sort the suite names and print unique entries*/

for(i=0;i

qsort(array, count,sizeof(array[0]),mystrcmp);

printf(" Valid suites:");

for(i=0,previous="";i

printf(" ");

}

/*

* Main entry point for test harness

*/

int

run_testrunner(int argc, char **argv,testentry_t tests[],int test_count)

{

char *test_name, *target;

int i;

stats_t stats;

int target_matched,max_errors_before_quit,redirect_stdouterr;

memset (&stats, 0, sizeof (stats));

max_errors_before_quit=1;

redirect_stdouterr=0;

assert (tests != NULL);

assert(test_count>0);

assert (argc > 0 && argv && *argv);

while(true) {

target = argc > 1 ? argv[1] : "";

assert (target);

if(*target!='-') break;

argc--;argv++;

if(target[1]=='f' && target[2])

max_errors_before_quit=atoi(target+1);

else if(target[1]=='r')

redirect_stdouterr=1;

}

target_matched = false;

for (i=0;i

test_name = tests[i].name;

assert(test_name);

assert(tests[i].suite);

assert(tests[i].test_function);

if (eql(target,test_name)||eql(target,"all") || eql (target,tests[i].suite) ) {

if(!target_matched) printf("Running tests... ");

target_matched = true;

run_one_test (&stats, &tests[i],redirect_stdouterr, argc - 1,argv + 1);

}

}

if (!target_matched)

{

fprintf (stderr, "Test '%s' not found", (strlen(target)>0?target : "(empty)"));

print_targets(tests,test_count);

}

else {

printf (" Test Results:%d tests,%d passed,%d failed. ", stats.ran,

stats.passed, stats.failed);

}

return stats.passed == stats.ran && target_matched ? 0 : 1;

}

***************************

FILE 7: testrunner.h

***************************

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/

typedef int (*test_fp) (int, char **);

typedef struct

{

char *name;

char *suite;

test_fp test_function;

} testentry_t;

int run_testrunner(int argc, char **argv, testentry_t *entries,int entry_count);

void set_testrunner_default_timeout(int s);

void set_testrunner_timeout(int s);

*************************** ******* QUESTIONS ********* ***************************

General questions: ------------------

1)- Why is it necessary to implement a change directory 'cd' command in the shell? Could it be implemented by an external program instead?

2)- Explain how our sample shell implements the change directory command.

3)- What would happen if this program did not use the fork function, but just used execv directly? (Tryit!) Try temporarily changing the code 'pid_from_fork = fork();' to 'pid_from_fork = 0;'

4)- Explain what the return value of fork() means and how this program uses it.

5)- What would happen if fork() were called prior to chdir(),and chdir() invoked within the forked child process? (Try it!) Try temporarily changing the code for 'cd' to use fork: if (fork() == 0) { if (chdir(exec_argv[1])) /* Error: change directory failed */ fprintf(stderr, "cd: failed to chdir %s ", \ exec_argv[1]); exit(EXIT_SUCCESS); }

6)- Can you run multiple versions of ./b.sh in the background? What happens to their output?

7)- Can you execute a second instance of our shell from within our shell program(use './shell')? Which shell receives your input?

8)- What happens if you type CTRL-C while the count down script ./b.sh is running? What if ./b.sh is running in the background?

9)- Can a shell kill itself? Can a shell within a shell kill the parent shell? ./shell ./shell /bin/kill -s KILL NNN (Where NNN is the parent's PID.)

10)- What happens to background processes when you exit from the shell? Do they continue to run? Can you see them with the 'ps' command? ./shell ./b.sh& exit ps

shell.c changes: ----------------

Please make the following modifications to the given file shell.c. We have included some built-in test cases, which are described along with the feature requests below. In addition to running the tests as listed individually, you can run "make test" to attempt all tests on your modified code.

11)- Modify this assignment so that you canuse 'ls' insteadof '/bin/ls' (i.e. the shell searches the path for the command to execute.) Test: ./shell -test path

12)- Modify this assignment 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

(You will need to change this into a correct printf format). Do not increment the counter if no command is supplied to execute. Test: ./shell -test counter

13)- Modify this assignment so that '!NN're-executes the n'th command entered. You can assume that NN will only be tested with values 1 through 9, no more than 9 values will be entered. Shell(...)1> ls Shell(...)2> !1 # re-executes ls Shell(...)3> !2 # re-executes ls Shell(...)4> !4 # prints "Not valid" to stderr

Test: ./shell -test rerun

14)- Modify the assignment so that it uses waitpid instead of wait.

15)- 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(pid=n1)1> sub Shell(pid=n2)1> exit # Exits sub shell Shell(pid=n1)1> exit # Exits back to 'real' shell

16)- Create a new global variable to prevent a subshell from invoking a subshell in voking a subshell (i.e., more than 3 levels deep): Shell(pid=n1)1> sub Shell(pid=n2)1> sub Shell(pid=n3)1> sub # prints "Too deep!" to stderr Test: ./shell -test sub

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

Mysql Examples Explanations Explain Examples

Authors: Harry Baker ,Ray Yao

1st Edition

B0CQK9RN2J, 979-8872176237

More Books

Students also viewed these Databases questions

Question

1-4 How will MIS help my career?

Answered: 1 week ago