From e297aa16595456926a853d1a22d85aee5ffc3ea8 Mon Sep 17 00:00:00 2001 From: slapelachie Date: Fri, 21 Mar 2025 10:54:32 +1000 Subject: [PATCH] init --- .clang-format | 18 +++ Makefile | 19 +++ hq.c | 349 ++++++++++++++++++++++++++++++++++++++++++++++++++ hq.h | 207 ++++++++++++++++++++++++++++++ job.c | 45 +++++++ job.h | 59 +++++++++ sigcat.c | 52 ++++++++ sigcat.h | 41 ++++++ 8 files changed, 790 insertions(+) create mode 100644 .clang-format create mode 100644 Makefile create mode 100644 hq.c create mode 100644 hq.h create mode 100644 job.c create mode 100644 job.h create mode 100644 sigcat.c create mode 100644 sigcat.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c9444ea --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +--- +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 79 +IndentWidth: 4 +ContinuationIndentWidth: 8 +UseTab: Never + +PointerAlignment: Right +DerivePointerAlignment: false +BreakBeforeBraces: Attach +AlignAfterOpenBracket: DontAlign +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +--- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e364e64 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +CC=gcc +CFLAGS=-std=gnu99 -Wall -pedantic -I/local/courses/csse2310/include +LDFLAGS=-L/local/courses/csse2310/lib -lcsse2310a3 + +.PHONY: all debug profile clean +.DEFAULT_GOAL := all + +all: sigcat hq + +debug: CFLAGS += -g +debug: all + +profile: CFLAGS += -pg -fprofile-arcs -ftest-coverage +profile: all + +hq: hq.o job.o + +clean: + rm -f sigcat hq *.o diff --git a/hq.c b/hq.c new file mode 100644 index 0000000..278a115 --- /dev/null +++ b/hq.c @@ -0,0 +1,349 @@ +#include "hq.h" + +int get_valid_number(char *rawNumber) { + if (strlen(rawNumber) == 0) { + return -1; + } + + for (int i = 0; i < strlen(rawNumber); i++) { + if (!isdigit(rawNumber[i]) && !isspace(rawNumber[i])) { + return -1; + } + } + + return atoi(rawNumber); +} + +// Not in job.c as requires get_valid_number +struct Job *get_job_from_raw_id(char *rawJobId) { + int jobId = get_valid_number(rawJobId); + struct Job *job = get_job_from_id(jobId); + return job; +} + +char **prepare_args_for_exec(char **arguments, int numberArgs) { + char **newArguments = malloc(sizeof(char *) * (numberArgs + 1)); + + for (int i = 0; i < numberArgs; i++) { + newArguments[i] = arguments[i]; + } + newArguments[numberArgs] = '\0'; + + return newArguments; +} + +void handle_child_fds(struct Job *job) { + // close unused filedescriptors + close(job->readFd[0]); + close(job->writeFd[1]); + + // redirect process stdin and stdout to pipe + dup2(job->writeFd[0], STDIN_FILENO); + dup2(job->readFd[1], STDOUT_FILENO); + + // close fds + close(job->readFd[1]); + close(job->writeFd[0]); +} + +int execute_command(char *command, char **arguments, int numberArgs) { + char **nullpArguments = NULL; + int value = 0; + + nullpArguments = prepare_args_for_exec(arguments, numberArgs); + value = execvp(command, nullpArguments); + free(nullpArguments); + + return value; +} + +void handle_spawn(char **arguments, int numberArgs) { + struct Job *job = (struct Job *)malloc(sizeof(struct Job)); + memset(job, 0, sizeof(struct Job)); + + if (numberArgs < 1) { + printf("Error: Insufficient arguments\n"); + return; + } + + // Set command name and command status + job->command = strdup(arguments[0]); + job->status = (char *)malloc(sizeof(char) * (strlen("running") + 1)); + strcpy(job->status, "running"); + + // Setup pipes + if (pipe(job->readFd) || pipe(job->writeFd)) { + perror("Pipe:"); + } + + int pid = fork(); + if (!pid) { + handle_child_fds(job); + + if (execute_command(job->command, arguments, numberArgs) == -1) { + exit(EXIT_CHLD_ERR); + } + exit(EXIT_OK); + } else { + // close unused filedescriptors + close(job->readFd[1]); + close(job->writeFd[0]); + + job->pid = pid; + job->jobId = add_job(job); + printf("New Job ID [%d] created\n", job->jobId); + } +} + +void report_job(struct Job *job) { + printf("[%d] %s:%s\n", job->jobId, job->command, job->status); +} + +void handle_report(char **arguments, int numberArgs) { + if (numberArgs > 0) { + struct Job *job = get_job_from_raw_id(arguments[0]); + + if (!job) { + printf("Error: Invalid job\n"); + return; + } + + printf("[Job] cmd:status\n"); + report_job(job); + } else { + printf("[Job] cmd:status\n"); + for (int i = 0; i < programJobs.length; i++) { + struct Job *currentJob = programJobs.jobs[i]; + report_job(currentJob); + } + } +} + +void handle_signal(char **arguments, int numberArgs) { + if (numberArgs < 2) { + printf("Error: Insufficient arguments\n"); + return; + } + + struct Job *job = get_job_from_raw_id(arguments[0]); + if (!job) { + printf("Error: Invalid job\n"); + return; + } + + int signal = get_valid_number(arguments[1]); + if (signal < 1 || signal > 31) { + printf("Error: Invalid signal\n"); + return; + } + + kill(job->pid, signal); +} + +void handle_sleep(char **arguments, int numberArgs) { + char *endPtr; + struct timespec sleepTime; + memset(&sleepTime, 0, sizeof(struct timespec)); + + if (numberArgs < 1) { + printf("Error: Insufficient arguments\n"); + return; + } + + int errno = 0; + sleepTime.tv_sec = strtod(arguments[0], &endPtr); + if (errno != 0 || strlen(arguments[0]) == 0 || strcmp(endPtr, "") != 0) { + printf("Error: Invalid sleep time\n"); + return; + } + + // sleep is interrupted by sigchld + // https://stackoverflow.com/questions/17118105 + while (nanosleep(&sleepTime, &sleepTime)) { + ; + } +} + +void handle_send(char **arguments, int numberArgs) { + if (numberArgs < 2) { + printf("Error: Insufficient arguments\n"); + return; + } + + struct Job *job = get_job_from_raw_id(arguments[0]); + if (!job) { + printf("Error: Invalid job\n"); + return; + } + + dprintf(job->writeFd[1], "%s\n", arguments[1]); +} + +void handle_recieve(char **arguments, int numberArgs) { + if (numberArgs < 1) { + printf("Error: Insufficient arguments\n"); + return; + } + + struct Job *job = get_job_from_raw_id(arguments[0]); + if (!job) { + printf("Error: Invalid job\n"); + return; + } + + if (is_ready(job->readFd[0])) { + FILE *input = NULL; + char *line = NULL; + + if ((input = fdopen(job->readFd[0], "r")) == NULL) { + printf("\n"); + return; + } + + if ((line = read_line(input)) != NULL) { + printf("%s\n", line); + } else { + printf("\n"); + } + + free(line); + fclose(input); + } else { + printf("\n"); + } +} + +void handle_eof(char **arguments, int numberArgs) { + if (numberArgs < 1) { + printf("Error: Insufficient arguments\n"); + return; + } + + struct Job *job = get_job_from_raw_id(arguments[0]); + if (!job) { + printf("Error: Invalid job\n"); + return; + } + + close(job->writeFd[1]); +} + +void handle_cleanup(void) { + for (int i = 0; i < programJobs.length; i++) { + struct Job *currentJob = programJobs.jobs[i]; + + // Close all pipes and kill + close(currentJob->writeFd[1]); + close(currentJob->readFd[0]); + kill(currentJob->pid, SIGKILL); + } + // Don't need to wait, done in update_status signal handler + // If I wait here then the status does not get updated +} + +void handle_commands(char *command, char **arguments, int numberArgs) { + // I really don't like using so many if statements but I don't know of + // another way + + if (strcmp(command, "spawn") == 0) { + handle_spawn(arguments, numberArgs); + } else if (strcmp(command, "report") == 0) { + handle_report(arguments, numberArgs); + } else if (strcmp(command, "signal") == 0) { + handle_signal(arguments, numberArgs); + } else if (strcmp(command, "sleep") == 0) { + handle_sleep(arguments, numberArgs); + } else if (strcmp(command, "send") == 0) { + handle_send(arguments, numberArgs); + } else if (strcmp(command, "rcv") == 0) { + handle_recieve(arguments, numberArgs); + } else if (strcmp(command, "eof") == 0) { + handle_eof(arguments, numberArgs); + } else if (strcmp(command, "cleanup") == 0) { + handle_cleanup(); + } else { + printf("Error: Invalid command\n"); + } +} + +void process_input(char *input) { + int numberTokens = 0; + char *foo = strdup(input); + char **tokens = split_space_not_quote(foo, &numberTokens); + + // Check if empty command + if (numberTokens == 0) { + return; + } + + // Get command and it's arguments + char *command = strdup(tokens[0]); + char **arguments = NULL; + if (numberTokens > 1) { + arguments = &tokens[1]; + } + + handle_commands(command, arguments, numberTokens - 1); + + free(tokens); + free(foo); + free(command); +} + +void build_status(char **dest, char *prefix, int state) { + size_t needed_size = snprintf(NULL, 0, "%s(%d)", prefix, state); + *dest = (char *)realloc(*dest, needed_size + sizeof(char)); + sprintf(*dest, "%s(%d)", prefix, state); +} + +void update_status(int s) { + int pid; + int state; + while ((pid = waitpid(-1, &state, WNOHANG)) > 0) { + struct Job *job = get_job_from_pid(pid); + if (!job) { + return; + } + + if (WIFSIGNALED(state)) { + build_status(&(job->status), "signalled", WTERMSIG(state)); + } else { + build_status(&(job->status), "exited", WEXITSTATUS(state)); + } + } +} + +void setup_signal_handlers(void) { + signal(SIGINT, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = update_status; + sa.sa_flags = SA_RESTART; + + sigaction(SIGCHLD, &sa, 0); +} + +int main(int argc, char *argv[]) { + setup_signal_handlers(); + + programJobs.jobs = NULL; + programJobs.length = 0; + + printf("> "); + fflush(stdout); + + char *line = NULL; + while ((line = read_line(stdin)) != NULL) { + process_input(line); + + printf("> "); + fflush(stdout); + free(line); + } + + handle_cleanup(); + deinit_jobs(); + return EXIT_OK; +} diff --git a/hq.h b/hq.h new file mode 100644 index 0000000..e95f94a --- /dev/null +++ b/hq.h @@ -0,0 +1,207 @@ +#ifndef HQ_H +#define HQ_H +#include +#include +#include +#include +#include +#include +#include +#include + +#include "job.h" + +// Program exit codes +enum ExitCodes { + EXIT_OK = 0, + EXIT_CHLD_ERR = 99 +}; + +/* get_valid_number() + * ------------------ + * Gets a number from the given string + * + * Example: + * "1" -> 1 + * "a" -> -1 + * "1a" -> -1 + * "-2" -> -1 + * + * rawNumber: the raw number to convert to an integer + * + * Returns: the integer representation of the raw number, if the raw number + * is not a number, returns -1 + */ +int get_valid_number(char *rawNumber); + +/* get_job_from_raw_id() + * --------------------- + * Similar to get_kob_from_id, however also checks if the given raw id is a + * valid number + * + * rawId: the raw id for the job + * + * Returns: the job if the job id exists, otherwise NULL + */ +struct Job *get_job_from_raw_id(char *rawId); + +/* prepare_args_for_exec() + * ----------------------- + * NULL terminates the list of arguments so it can be used for execvp + * + * arguments: the list of arguments + * numberArgs: the number of arguments passed + * + * Returns: the null terminated array + */ +char **prepare_args_for_exec(char **arguments, int numberArgs); + +/* handle_child_fds() + * ------------------ + * Handles opening, duplicating and closing fds for use in child fork + * + * job: the job in which the fds are stored + */ +void handle_child_fds(struct Job *job); + +/* execute_command() + * ----------------- + * Executes the given command with the given arguments, adds a null terminator + * to the list of arguments before executing + * + * arguments: the list of arguments + * numberArgs: the number of arguments passed + * + * Returns: the return status of the command + */ +int execute_command(char *command, char **arguments, int numberArgs); + +/* handle_spawn() + * -------------- + * Handles the spawn command + * + * arguments: the arguments to give to the command + * number_args: the number of arguments passed + */ +void handle_spawn(char **arguments, int numberArgs); + +/* report_job() + * ------------ + * Prints the job information to stdout + * + * job: the job to report on + */ +void report_job(struct Job *job); + +/* handle_report() + * --------------- + * Handles the report command + * Prints a brief description of the status of each job + * + * arguments: should only be one argument containing the job id + * number_args: the number of arguments passed + */ +void handle_report(char **arguments, int numberArgs); + +/* handle_signal() + * --------------- + * Handles the signal command + * Sends a signal to a job given a job id and signal number + * + * arguments: the array of arguments for the command + * numberArgs: the number of arguments passed + */ +void handle_signal(char **arguments, int numberArgs); + +/* handle_sleep() + * -------------- + * Handles the sleep command + * Puts this program to sleep for the specified seconds + * + * arguments: the array of arguments for the command + * numberArgs: the number of arguments passed + */ +void handle_sleep(char **arguments, int numberArgs); + +/* handle_send() + * ------------- + * Handles the send command + * Sends the given message to the given job id's stdin + * + * arguments: the array of arguments for the command + * numberArgs: the number of arguments passed + */ +void handle_send(char **arguments, int numberArgs); + +/* handle_recieve() + * ------------- + * Handles the rcv command + * Reads next line in the given job id's stdout + * + * arguments: the array of arguments for the command + * numberArgs: the number of arguments passed + */ +void handle_recieve(char **arguments, int numberArgs); + +/* handle_eof() + * ------------- + * Handles the eof command + * Closes the write end of the programs pipe + * + * arguments: the array of arguments for the command + * numberArgs: the number of arguments passed + */ +void handle_eof(char **arguments, int numberArgs); + +/* handle_cleanup() + * ---------------- + * Handles the cleanup commands + * Sends a SIGKILL to all running jobs + */ +void handle_cleanup(void); + +/* handle_commands() + * ----------------- + * Function to coordinate what each command does + * + * command: the command string + * arguments: the arguments for the given command + * number_args: the amount of arguments passed + */ +void handle_commands(char *command, char **arguments, int numberArgs); + +/* build_status() + * -------------- + * Builds the status for a job and outputs it to the dest + * Builds in the form of "()" + * + * dest: the character array to output to + * prefix: the prefix for the status + * state: the exit number / signal number of the status + */ +void build_status(char **dest, char *prefix, int state); + +/* update_status() + * --------------- + * Updates all job status' if they have changed, called for handling of + * SIGCHLD + * + * signal: unused but required for a signal handler + */ +void update_status(int signal); + +/* setup_signal_handlers() + * ----------------------- + * Sets up the required signal handlers + * Ignores SIGINT, handles SIGCHLD with update_status + */ +void setup_signal_handlers(void); + +/* process_input() + * --------------- + * Processes the input from the interactive cli + * + * input: the input from the interactive cli + */ +void process_input(char *input); +#endif diff --git a/job.c b/job.c new file mode 100644 index 0000000..28e8df4 --- /dev/null +++ b/job.c @@ -0,0 +1,45 @@ +#include "job.h" + +struct Job *get_job_from_pid(int pid) { + for (int i = 0; i < programJobs.length; i++) { + struct Job *job = programJobs.jobs[i]; + if (job->pid == pid) { + return job; + } + } + + return NULL; +} + +struct Job *get_job_from_id(int id) { + for (int i = 0; i < programJobs.length; i++) { + struct Job *job = programJobs.jobs[i]; + if (job->jobId == id) { + return job; + } + } + + return NULL; +} + +int add_job(struct Job *job) { + programJobs.jobs = (struct Job **)realloc( + programJobs.jobs, sizeof(struct Job *) * (programJobs.length + 1)); + + programJobs.jobs[programJobs.length] = job; + programJobs.length++; + + return programJobs.length - 1; +} + +void deinit_jobs(void) { + for (int i = 0; i < programJobs.length; i++) { + struct Job *job = programJobs.jobs[i]; + free(job->command); + free(job->status); + free(programJobs.jobs[i]); + } + + free(programJobs.jobs); +} + diff --git a/job.h b/job.h new file mode 100644 index 0000000..b775edd --- /dev/null +++ b/job.h @@ -0,0 +1,59 @@ +#ifndef JOB_H +#define JOB_H +#include +#include + +/* A job */ +struct Job { + int jobId; + int pid; + int writeFd[2]; + int readFd[2]; + char *command; + char *status; +}; + +/* List of jobs including the length */ +struct Jobs { + struct Job **jobs; + int length; +}; + +// TODO: remove this as global +struct Jobs programJobs; + +/* get_job_from_pid() + * ------------------ + * Gets a job given by the pid of the job + * + * pid: the process id of the job + * + * Returns: the job if the pid exists, otherwise NULL + */ +struct Job *get_job_from_pid(int pid); + +/* get_job_from_id() + * ------------------ + * Gets a job given by the job id + * + * id: the id of the job + * + * Returns: the job if the job id exists, otherwise NULL + */ +struct Job *get_job_from_id(int id); + +/* add_job() + * Adds the given job to the list of current jobs + * + * job: the pointer to the job to add + * + * Returns: the job id of the given job + */ +int add_job(struct Job *job); + +/* deinit_jobs() + * ------------- + * Deinitialises all jobs and frees memory + */ +void deinit_jobs(void); +#endif diff --git a/sigcat.c b/sigcat.c new file mode 100644 index 0000000..e087cc0 --- /dev/null +++ b/sigcat.c @@ -0,0 +1,52 @@ +#include "sigcat.h" + +void setup_default_handlers(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = default_signal_handler; + sa.sa_flags = SA_RESTART; + + for (int i = SIGMIN; i <= SIGMAX; i++) { + // Cannot process SIGKILL or SIGSTOP + if (i == SIGKILL || i == SIGSTOP) { + continue; + } + + sigaction(i, &sa, 0); + } +} + +void default_signal_handler(int s) { + fprintf(outputStream, "sigcat received %s\n", strsignal(s)); + fflush(outputStream); + + if (s == SIGUSR1 || s == SIGUSR2) { + usr_signal_handler(s); + } +} + +void usr_signal_handler(int s) { + if (s == SIGUSR1) { + outputStream = stdout; + } else if (s == SIGUSR2) { + outputStream = stderr; + } +} + +int main(int argc, char *argv[]) { + // Defined in header + outputStream = stdout; + + setup_default_handlers(); + + // Forever loop input until recieve EOF + char *line; + while ((line = read_line(stdin)) != NULL) { + strcat(line, "\n"); + fprintf(outputStream, line); + fflush(outputStream); + free(line); + } + + return 0; +} diff --git a/sigcat.h b/sigcat.h new file mode 100644 index 0000000..ded61ef --- /dev/null +++ b/sigcat.h @@ -0,0 +1,41 @@ +#ifndef SIGCAT_H +#define SIGCAT_H +#include +#include +#include +#include +#include +#include + +#define SIGMIN 1 +#define SIGMAX 31 + +// Holds the current output stream +FILE *outputStream; + +/* setup_default_handlers() + * ------------------------ + * Sets up handlers for signals 1-31 excluding 9, 19, 10 and 12 + */ +void setup_default_handlers(void); + +/* default_signal_handler() + * ------------------------ + * The default handler for signals, prints to the current output_stream + * For example: sigcat recieved Hangup + * + * s: the signal number + */ +void default_signal_handler(int s); + +/* usr_signal_handler() + * ------------------------ + * The USR handler for signals, changes the output_stream based on the signal + * recieved. SIGUSR1 sets the output_stream to stdout and SIGUSR2 sets the + * output_stream to stderr + * + * s: the signal number + */ +void usr_signal_handler(int s); + +#endif