aboutsummaryrefslogtreecommitdiff
path: root/tshref.c
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-08-13 13:44:36 +0000
committer_ov4 <ov4@ov4.org>2026-08-13 13:44:36 +0000
commitc2bb9d17b85acec2130c7d49a253cba839aebd2a (patch)
tree729687bd718bfa0be408de16bc1dd3d084122517 /tshref.c
parent901c816ab47a4893da41471a943123406f1c0662 (diff)
reverse engineering
Diffstat (limited to 'tshref.c')
-rw-r--r--tshref.c675
1 files changed, 675 insertions, 0 deletions
diff --git a/tshref.c b/tshref.c
new file mode 100644
index 0000000..0afb216
--- /dev/null
+++ b/tshref.c
@@ -0,0 +1,675 @@
+// reverse engineering of tshref
+// ft. CSAPP 3e
+
+/*
+ * tsh - A tiny shell program with job control
+ *
+ * <Put your name and login ID here>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+/* 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<<16 /* 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)
+{
+ char *argv[MAXARGS];
+ int bg;
+ pid_t pid;
+ sigset_t mask;
+
+ bg = parseline(cmdline, argv);
+ if (argv[0] == NULL)
+ return;
+
+ if (builtin_cmd(argv))
+ return;
+
+ if (sigemptyset(&mask) < 0)
+ unix_error("sigemptyset error");
+ if (sigaddset(&mask, SIGCHLD) != 0 ||
+ sigaddset(&mask, SIGINT) != 0 ||
+ sigaddset(&mask, SIGTSTP) != 0)
+ unix_error("sigaddset error");
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
+ unix_error("sigprocmask error");
+
+ if ((pid = fork()) < 0)
+ unix_error("fork error");
+
+ if (pid == 0) {
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ if (setpgid(0, 0) < 0)
+ unix_error("setpgid error");
+ if (execve(argv[0], argv, environ) < 0) {
+ printf("%s: Command not found\n", argv[0]);
+ exit(0);
+ }
+ }
+
+ addjob(jobs, pid, bg ? BG : FG, cmdline);
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+
+ if (bg)
+ printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
+ else
+ waitfg(pid);
+}
+
+/*
+ * 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 '\n' 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)
+{
+ if (!strcmp(argv[0], "quit"))
+ exit(0);
+
+ if (!strcmp(argv[0], "jobs")) {
+ listjobs(jobs);
+ return 1;
+ }
+
+ if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
+ do_bgfg(argv);
+ return 1;
+ }
+
+ if (!strcmp(argv[0], "&"))
+ return 1;
+
+ return 0;
+}
+
+/*
+ * do_bgfg - Execute the builtin bg and fg commands
+ */
+void do_bgfg(char **argv)
+{
+ struct job_t *job = NULL;
+ long id;
+
+ if (argv[1] == NULL) {
+ printf("%s command requires PID or %%jobid argument\n", argv[0]);
+ return;
+ }
+
+ if (isdigit(argv[1][0])) {
+ id = strtol(argv[1], NULL, 10);
+ job = getjobpid(jobs, (pid_t)id);
+ if (id <= 0 || job == NULL) {
+ printf("(%ld): No such process\n", id);
+ return;
+ }
+ } else if (argv[1][0] == '%') {
+ id = strtol(argv[1] + 1, NULL, 10);
+ job = getjobjid(jobs, (int)id);
+ if (id <= 0 || job == NULL) {
+ printf("%s: No such job\n", argv[1]);
+ return;
+ }
+ } else {
+ printf("%s: argument must be a PID or %%jobid\n", argv[0]);
+ return;
+ }
+
+ if (!strcmp(argv[0], "bg")) {
+ if (kill(-job->pid, SIGCONT) < 0)
+ unix_error("kill (SIGCONT)");
+ job->state = BG;
+ printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
+ } else if (!strcmp(argv[0], "fg")) {
+ if (kill(-job->pid, SIGCONT) < 0)
+ unix_error("kill (SIGCONT)");
+ job->state = FG;
+ waitfg(job->pid);
+ } else {
+ printf("do_bgfg: Internal error: argv[0]=%s\n", argv[0]);
+ exit(0);
+ }
+}
+
+/*
+ * waitfg - Block until process pid is no longer the foreground process
+ */
+void waitfg(pid_t pid)
+{
+ struct job_t *job;
+
+ if (pid <= 0)
+ return;
+
+ job = getjobpid(jobs, pid);
+ if (job == NULL)
+ return;
+
+ while (job->pid == pid && job->state == FG)
+ sleep(1);
+
+ if (verbose)
+ printf("waitfg: Process (%d) no longer the fg process\n", pid);
+}
+
+/*****************
+ * 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)
+{
+ pid_t pid;
+ int status;
+
+ if (verbose)
+ printf("sigchld_handler: entering\n");
+
+ while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
+ if (WIFSTOPPED(status)) {
+ struct job_t *job = getjobpid(jobs, pid);
+ if (job == NULL) {
+ printf("Job (%d) not found\n", pid);
+ return;
+ }
+ job->state = ST;
+ fprintf(stdout, "Job [%d] (%d) stopped by signal %d\n",
+ pid2jid(pid), pid, WSTOPSIG(status));
+ } else if (WIFSIGNALED(status)) {
+ int jid = pid2jid(pid);
+ deletejob(jobs, pid);
+ fprintf(stdout, "Job [%d] (%d) terminated by signal %d\n",
+ jid, pid, WTERMSIG(status));
+ } else if (WIFEXITED(status)) {
+ deletejob(jobs, pid);
+ }
+ }
+
+ if (pid < 0 && errno != ECHILD)
+ unix_error("waitpid error");
+
+ if (verbose)
+ printf("sigchld_handler: exiting\n");
+}
+
+/*
+ * 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)
+{
+ pid_t pid;
+
+ if (verbose)
+ printf("sigint_handler: entering\n");
+
+ pid = fgpid(jobs);
+ if (pid > 0) {
+ if (kill(-pid, SIGINT) < 0)
+ unix_error("kill (SIGINT) error");
+ }
+
+ if (verbose)
+ printf("sigint_handler: exiting\n");
+}
+
+/*
+ * 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)
+{
+ pid_t pid;
+
+ if (verbose)
+ printf("sigtstp_handler: entering\n");
+
+ pid = fgpid(jobs);
+ if (pid > 0) {
+ if (kill(-pid, SIGTSTP) < 0)
+ unix_error("kill (SIGTSTP)");
+ }
+
+ if (verbose)
+ printf("sigtstp_handler: exiting\n");
+}
+
+/*********************
+ * 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 < MAXJOBS; i++)
+ clearjob(&jobs[i]);
+}
+
+/* maxjid - Returns largest allocated job ID */
+int maxjid(struct job_t *jobs)
+{
+ int i, max=0;
+
+ for (i = 0; i < MAXJOBS; 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 < 1)
+ return 0;
+
+ for (i = 0; i < MAXJOBS; 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\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
+ }
+ return 1;
+ }
+ }
+ printf("Tried to create too many jobs\n");
+ 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 < 1)
+ return 0;
+
+ for (i = 0; i < MAXJOBS; 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 < MAXJOBS; 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 < 1)
+ return NULL;
+ for (i = 0; i < MAXJOBS; 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 < 1)
+ return NULL;
+ for (i = 0; i < MAXJOBS; 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 < 1)
+ return 0;
+ for (i = 0; i < MAXJOBS; 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 < MAXJOBS; 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]\n");
+ printf(" -h print this message\n");
+ printf(" -v print additional diagnostic information\n");
+ printf(" -p do not emit a command prompt\n");
+ exit(1);
+}
+
+/*
+ * unix_error - unix-style error routine
+ */
+void unix_error(char *msg)
+{
+ fprintf(stdout, "%s: %s\n", msg, strerror(errno));
+ exit(1);
+}
+
+/*
+ * app_error - application-style error routine
+ */
+void app_error(char *msg)
+{
+ fprintf(stdout, "%s\n", 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) < 0)
+ 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\n");
+ exit(1);
+}
+
+
+