Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

LONG PROBLEM IN C Write a program in C that simulates the tsh shell in Unix. The program should have the following features: * The

LONG PROBLEM IN C

Write a program in C that simulates the tsh shell in Unix. The program should have the following features:

* The prompt should be the string \"tsh>\"

*The command line typed by the user should consist of a name and zero or more arguments, all separated by one or more spaces. If name is a built-in command, then tsh should handle it immediately and wait for the next command line. Otherwise, tsh should assume that name is the path of an executable file, which it loads and runs in the context of an initial child process (In this context, the term job refers to this initial child process).

*tsh need not support pipes (|) or I/O redirection ().

*Typing ctrl-c (ctrl-z) should cause a SIGINT (SIGTSTP) signal to be sent to the current foreground job, as well as any descendents of that job (e.g., any child processes that it forked). If there is no foreground job, then the signal should have no effect.

*If the command line ends with an ampersand &, then tsh should run the job in the background. Otherwise, it should run the job in the foreground.

*Each job can be identified by either a process ID (PID) or a job ID (JID), which is a positive integer assigned by tsh. JIDs should be denoted on the command line by the prefix '%'. For example, \"%5\" denotes JID 5, and \"5\" denotes PID 5. (We have provided you with all of the routines you need for manipulating the job list.)

*tsh should support the following built-in commands:

quit: terminates the shell.

jobs: lists all background jobs

bg : restarts by sending it a SIGCONT signal, and then runs it in the background. The argument can be either a PID or a JID.

fg : restarts by sending it a SIGCONT signal, and then runs it in the foreground. The argument can be either a PID or a JID.

* tsh should reap all of its zombie children. If any job terminates because it receives a signal that it didn't catch, then tsh should recognize this event and print a message with the job's PID and a description of the offending signal.

Follow the initial skeleton program below

/*

* tsh - A tiny shell program with job control

*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

/* Misc manifest constants */

#define MAXLINE 1024 /* max line size */

#define MAXARGS 128 /* max args on a command line */

#define MAXJOBS 16 /* max jobs at any point in time */

#define MAXJID 1 /* max job ID */

/* Job states */

#define UNDEF 0 /* undefined */

#define FG 1 /* running in foreground */

#define BG 2 /* running in background */

#define ST 3 /* stopped */

/*

* Jobs states: FG (foreground), BG (background), ST (stopped)

* Job state transitions and enabling actions:

* FG -> ST : ctrl-z

* ST -> FG : fg command

* ST -> BG : bg command

* BG -> FG : fg command

* At most 1 job can be in the FG state.

*/

/* Global variables */

extern char **environ; /* defined in libc */

char prompt[] = \"tsh> \"; /* command line prompt (DO NOT CHANGE) */

int verbose = 0; /* if true, print additional output */

int nextjid = 1; /* next job ID to allocate */

char sbuf[MAXLINE]; /* for composing sprintf messages */

struct job_t { /* The job struct */

pid_t pid; /* job PID */

int jid; /* job ID [1, 2, ...] */

int state; /* UNDEF, BG, FG, or ST */

char cmdline[MAXLINE]; /* command line */

};

struct job_t jobs[MAXJOBS]; /* The job list */

/* End global variables */

/* Function prototypes */

/* Here are the functions that you will implement */

void eval(char *cmdline);

int builtin_cmd(char **argv);

void do_bgfg(char **argv);

void waitfg(pid_t pid);

void sigchld_handler(int sig);

void sigtstp_handler(int sig);

void sigint_handler(int sig);

/* Here are helper routines that we've provided for you */

int parseline(const char *cmdline, char **argv);

void sigquit_handler(int sig);

void clearjob(struct job_t *job);

void initjobs(struct job_t *jobs);

int maxjid(struct job_t *jobs);

int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);

int deletejob(struct job_t *jobs, pid_t pid);

pid_t fgpid(struct job_t *jobs);

struct job_t *getjobpid(struct job_t *jobs, pid_t pid);

struct job_t *getjobjid(struct job_t *jobs, int jid);

int pid2jid(pid_t pid);

void listjobs(struct job_t *jobs);

void usage(void);

void unix_error(char *msg);

void app_error(char *msg);

typedef void handler_t(int);

handler_t *Signal(int signum, handler_t *handler);

/*

* main - The shell's main routine

*/

int main(int argc, char **argv)

{

char c;

char cmdline[MAXLINE];

int emit_prompt = 1; /* emit prompt (default) */

/* Redirect stderr to stdout (so that driver will get all output

* on the pipe connected to stdout) */

dup2(1, 2);

/* Parse the command line */

while ((c = getopt(argc, argv, \"hvp\")) != EOF) {

switch (c) {

case 'h': /* print help message */

usage();

break;

case 'v': /* emit additional diagnostic info */

verbose = 1;

break;

case 'p': /* don't print a prompt */

emit_prompt = 0; /* handy for automatic testing */

break;

default:

usage();

}

}

/* Install the signal handlers */

/* These are the ones you will need to implement */

Signal(SIGINT, sigint_handler); /* ctrl-c */

Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */

Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */

/* This one provides a clean way to kill the shell */

Signal(SIGQUIT, sigquit_handler);

/* Initialize the job list */

initjobs(jobs);

/* Execute the shell's read/eval loop */

while (1) {

/* Read command line */

if (emit_prompt) {

printf(\"%s\", prompt);

fflush(stdout);

}

if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))

app_error(\"fgets error\");

if (feof(stdin)) { /* End of file (ctrl-d) */

fflush(stdout);

exit(0);

}

/* Evaluate the command line */

eval(cmdline);

fflush(stdout);

fflush(stdout);

}

exit(0); /* control never reaches here */

}

/*

* eval - Evaluate the command line that the user has just typed in

*

* If the user has requested a built-in command (quit, jobs, bg or fg)

* then execute it immediately. Otherwise, fork a child process and

* run the job in the context of the child. If the job is running in

* the foreground, wait for it to terminate and then return. Note:

* each child process must have a unique process group ID so that our

* background children don't receive SIGINT (SIGTSTP) from the kernel

* when we type ctrl-c (ctrl-z) at the keyboard.

*/

void eval(char *cmdline)

{

/* the following code demonstrates how to use parseline --- you'll

* want to replace most of it (at least the print statements). */

int i, bg;

char *argv[MAXARGS];

bg = parseline(cmdline, argv);

if (bg) {

printf(\"background job requested \");

}

for (i=0; argv[i] != NULL; i++) {

printf(\"argv[%d]=%s%s\", i, argv[i], (argv[i+1]==NULL)?\" \":\", \");

}

return;

}

/*

* parseline - Parse the command line and build the argv array.

*

* Characters enclosed in single quotes are treated as a single

* argument. Return true if the user has requested a BG job, false if

* the user has requested a FG job.

*/

int parseline(const char *cmdline, char **argv)

{

static char array[MAXLINE]; /* holds local copy of command line */

char *buf = array; /* ptr that traverses command line */

char *delim; /* points to first space delimiter */

int argc; /* number of args */

int bg; /* background job? */

strcpy(buf, cmdline);

buf[strlen(buf)-1] = ' '; /* replace trailing ' ' with space */

while (*buf && (*buf == ' ')) /* ignore leading spaces */

buf++;

/* Build the argv list */

argc = 0;

if (*buf == '\\'') {

buf++;

delim = strchr(buf, '\\'');

}

else {

delim = strchr(buf, ' ');

}

while (delim) {

argv[argc++] = buf;

*delim = '\\0';

buf = delim + 1;

while (*buf && (*buf == ' ')) /* ignore spaces */

buf++;

if (*buf == '\\'') {

buf++;

delim = strchr(buf, '\\'');

}

else {

delim = strchr(buf, ' ');

}

}

argv[argc] = NULL;

if (argc == 0) /* ignore blank line */

return 1;

/* should the job run in the background? */

if ((bg = (*argv[argc-1] == '&')) != 0) {

argv[--argc] = NULL;

}

return bg;

}

/*

* builtin_cmd - If the user has typed a built-in command then execute

* it immediately.

*/

int builtin_cmd(char **argv)

{

return 0; /* not a builtin command */

}

/*

* do_bgfg - Execute the builtin bg and fg commands

*/

void do_bgfg(char **argv)

{

return;

}

/*

* waitfg - Block until process pid is no longer the foreground process

*/

void waitfg(pid_t pid)

{

return;

}

/*****************

* Signal handlers

*****************/

/*

* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever

* a child job terminates (becomes a zombie), or stops because it

* received a SIGSTOP or SIGTSTP signal. The handler reaps all

* available zombie children, but doesn't wait for any other

* currently running children to terminate.

*/

void sigchld_handler(int sig)

{

return;

}

/*

* sigint_handler - The kernel sends a SIGINT to the shell whenver the

* user types ctrl-c at the keyboard. Catch it and send it along

* to the foreground job.

*/

void sigint_handler(int sig)

{

return;

}

/*

* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever

* the user types ctrl-z at the keyboard. Catch it and suspend the

* foreground job by sending it a SIGTSTP.

*/

void sigtstp_handler(int sig)

{

return;

}

/*********************

* End signal handlers

*********************/

/***********************************************

* Helper routines that manipulate the job list

**********************************************/

/* clearjob - Clear the entries in a job struct */

void clearjob(struct job_t *job) {

job->pid = 0;

job->jid = 0;

job->state = UNDEF;

job->cmdline[0] = '\\0';

}

/* initjobs - Initialize the job list */

void initjobs(struct job_t *jobs) {

int i;

for (i = 0; i

clearjob(&jobs[i]);

}

/* maxjid - Returns largest allocated job ID */

int maxjid(struct job_t *jobs)

{

int i, max=0;

for (i = 0; i

if (jobs[i].jid > max)

max = jobs[i].jid;

return max;

}

/* addjob - Add a job to the job list */

int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline)

{

int i;

if (pid

return 0;

for (i = 0; i

if (jobs[i].pid == 0) {

jobs[i].pid = pid;

jobs[i].state = state;

jobs[i].jid = nextjid++;

if (nextjid > MAXJOBS)

nextjid = 1;

strcpy(jobs[i].cmdline, cmdline);

if(verbose){

printf(\"Added job [%d] %d %s \", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);

}

return 1;

}

}

printf(\"Tried to create too many jobs \");

return 0;

}

/* deletejob - Delete a job whose PID=pid from the job list */

int deletejob(struct job_t *jobs, pid_t pid)

{

int i;

if (pid

return 0;

for (i = 0; i

if (jobs[i].pid == pid) {

clearjob(&jobs[i]);

nextjid = maxjid(jobs)+1;

return 1;

}

}

return 0;

}

/* fgpid - Return PID of current foreground job, 0 if no such job */

pid_t fgpid(struct job_t *jobs) {

int i;

for (i = 0; i

if (jobs[i].state == FG)

return jobs[i].pid;

return 0;

}

/* getjobpid - Find a job (by PID) on the job list */

struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {

int i;

if (pid

return NULL;

for (i = 0; i

if (jobs[i].pid == pid)

return &jobs[i];

return NULL;

}

/* getjobjid - Find a job (by JID) on the job list */

struct job_t *getjobjid(struct job_t *jobs, int jid)

{

int i;

if (jid

return NULL;

for (i = 0; i

if (jobs[i].jid == jid)

return &jobs[i];

return NULL;

}

/* pid2jid - Map process ID to job ID */

int pid2jid(pid_t pid)

{

int i;

if (pid

return 0;

for (i = 0; i

if (jobs[i].pid == pid) {

return jobs[i].jid;

}

return 0;

}

/* listjobs - Print the job list */

void listjobs(struct job_t *jobs)

{

int i;

for (i = 0; i

if (jobs[i].pid != 0) {

printf(\"[%d] (%d) \", jobs[i].jid, jobs[i].pid);

switch (jobs[i].state) {

case BG:

printf(\"Running \");

break;

case FG:

printf(\"Foreground \");

break;

case ST:

printf(\"Stopped \");

break;

default:

printf(\"listjobs: Internal error: job[%d].state=%d \",

i, jobs[i].state);

}

printf(\"%s\", jobs[i].cmdline);

}

}

}

/******************************

* end job list helper routines

******************************/

/***********************

* Other helper routines

***********************/

/*

* usage - print a help message

*/

void usage(void)

{

printf(\"Usage: shell [-hvp] \");

printf(\" -h print this message \");

printf(\" -v print additional diagnostic information \");

printf(\" -p do not emit a command prompt \");

exit(1);

}

/*

* unix_error - unix-style error routine

*/

void unix_error(char *msg)

{

fprintf(stdout, \"%s: %s \", msg, strerror(errno));

exit(1);

}

/*

* app_error - application-style error routine

*/

void app_error(char *msg)

{

fprintf(stdout, \"%s \", msg);

exit(1);

}

/*

* Signal - wrapper for the sigaction function

*/

handler_t *Signal(int signum, handler_t *handler)

{

struct sigaction action, old_action;

action.sa_handler = handler;

sigemptyset(&action.sa_mask); /* block sigs of type being handled */

action.sa_flags = SA_RESTART; /* restart syscalls if possible */

if (sigaction(signum, &action, &old_action)

unix_error(\"Signal error\");

return (old_action.sa_handler);

}

/*

* sigquit_handler - The driver program can gracefully terminate the

* child shell by sending it a SIGQUIT signal.

*/

void sigquit_handler(int sig)

{

printf(\"Terminating after receipt of SIGQUIT signal \");

exit(1);

}

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

Mobile Communications

Authors: Jochen Schiller

2nd edition

978-0321123817, 321123816, 978-8131724262

More Books

Students also viewed these Programming questions

Question

13.16. Derive the expected mean squares shown in Table 13.11.

Answered: 1 week ago