This commit is contained in:
2025-03-21 10:54:32 +10:00
commit e297aa1659
8 changed files with 790 additions and 0 deletions

18
.clang-format Normal file
View File

@@ -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
---

19
Makefile Normal file
View File

@@ -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

349
hq.c Normal file
View File

@@ -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("<EOF>\n");
return;
}
if ((line = read_line(input)) != NULL) {
printf("%s\n", line);
} else {
printf("<EOF>\n");
}
free(line);
fclose(input);
} else {
printf("<no input>\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;
}

207
hq.h Normal file
View File

@@ -0,0 +1,207 @@
#ifndef HQ_H
#define HQ_H
#include <csse2310a3.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#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 "<prefix>(<state>)"
*
* 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

45
job.c Normal file
View File

@@ -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);
}

59
job.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef JOB_H
#define JOB_H
#include <stddef.h>
#include <stdlib.h>
/* 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

52
sigcat.c Normal file
View File

@@ -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;
}

41
sigcat.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef SIGCAT_H
#define SIGCAT_H
#include <csse2310a3.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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