diff --git a/Dockerfile b/Dockerfile index dce0316..5e141a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,13 +19,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /build # Copy source files -COPY System_Programming_Projects/ System_Programming_Projects/ COPY src/ src/ COPY include/ include/ COPY tests/ tests/ COPY Makefile . -# Build the project (supports both legacy and new structure) +# Build the project RUN make clean && make all # Run tests during build to ensure correctness @@ -46,9 +45,8 @@ RUN useradd -m -s /bin/bash sysuser WORKDIR /app -# Copy only the built binaries (handles both structures) -COPY --from=builder /build/bin/* /app/ 2>/dev/null || \ - (cp /build/myshell /app/ && cp /build/queue_test /app/) +# Copy the built binaries +COPY --from=builder /build/bin/* /app/ # Set ownership RUN chown -R sysuser:sysuser /app diff --git a/Makefile b/Makefile index e3605e2..754f1d8 100644 --- a/Makefile +++ b/Makefile @@ -16,42 +16,23 @@ TEST_DIR := tests BUILD_DIR := build BIN_DIR := bin -# Legacy support -LEGACY_DIR := System_Programming_Projects - # Source files SHELL_SRCS := $(SRC_DIR)/shell/myshell.c $(SRC_DIR)/shell/shell_main.c QUEUE_SRCS := $(SRC_DIR)/queue/queue.c QUEUE_TEST := $(TEST_DIR)/queue_test.c -# Fallback to legacy structure if new structure doesn't exist -ifeq ($(wildcard $(SRC_DIR)/shell/myshell.c),) - SHELL_SRCS := $(LEGACY_DIR)/shell/myshell.c $(LEGACY_DIR)/shell/shell_main.c - QUEUE_SRCS := $(LEGACY_DIR)/queue/queue.c - QUEUE_TEST := $(LEGACY_DIR)/queue/queue_test.c - INC_DIR := $(LEGACY_DIR)/queue -endif - # Build targets MYSHELL := $(BIN_DIR)/myshell QUEUE_TEST_BIN := $(BIN_DIR)/queue_test -# Legacy targets (for backward compatibility) -MYSHELL_LEGACY := myshell -QUEUE_TEST_LEGACY := queue_test - # ============================================================================ # Phony targets # ============================================================================ -.PHONY: all clean test check debug help install \ - legacy legacy-test legacy-clean format +.PHONY: all clean test check debug help install format # Default target all: $(BIN_DIR) $(MYSHELL) $(QUEUE_TEST_BIN) -# Legacy build (backward compatible) -legacy: $(MYSHELL_LEGACY) $(QUEUE_TEST_LEGACY) - # ============================================================================ # Build rules # ============================================================================ @@ -60,25 +41,18 @@ legacy: $(MYSHELL_LEGACY) $(QUEUE_TEST_LEGACY) $(BIN_DIR): @mkdir -p $(BIN_DIR) -# Build shell (new structure) +# Build shell $(MYSHELL): $(SHELL_SRCS) | $(BIN_DIR) @echo "Building myshell..." $(CC) $(CFLAGS) -I$(INC_DIR) -o $@ $^ $(LDFLAGS) @echo "✓ myshell built successfully" -# Build queue test (new structure) +# Build queue test $(QUEUE_TEST_BIN): $(QUEUE_SRCS) $(QUEUE_TEST) | $(BIN_DIR) @echo "Building queue_test..." $(CC) $(CFLAGS) $(THREADS) -I$(INC_DIR) -o $@ $^ $(LDFLAGS) @echo "✓ queue_test built successfully" -# Legacy targets (backward compatible) -$(MYSHELL_LEGACY): $(SHELL_SRCS) - $(CC) $(CFLAGS) -o $@ $^ - -$(QUEUE_TEST_LEGACY): $(LEGACY_DIR)/queue/queue.c $(LEGACY_DIR)/queue/queue_test.c - $(CC) $(CFLAGS) $(THREADS) -o $@ $^ - # ============================================================================ # Test and validation # ============================================================================ @@ -89,10 +63,6 @@ test: $(QUEUE_TEST_BIN) @$(QUEUE_TEST_BIN) @echo "✓ All tests passed" -# Legacy test -legacy-test: $(QUEUE_TEST_LEGACY) - @./$(QUEUE_TEST_LEGACY) - # Code quality check (runs tests and validates build) check: all test @echo "✓ All checks passed" @@ -126,12 +96,9 @@ format: clean: @echo "Cleaning build artifacts..." @rm -rf $(BIN_DIR) $(BUILD_DIR) - @rm -f $(MYSHELL_LEGACY) $(QUEUE_TEST_LEGACY) *.o + @rm -f *.o @echo "✓ Clean complete" -legacy-clean: - @rm -f $(MYSHELL_LEGACY) $(QUEUE_TEST_LEGACY) *.o - # ============================================================================ # Installation (local) # ============================================================================ @@ -160,10 +127,6 @@ help: @echo " install - Install to PREFIX (default: /usr/local)" @echo " help - Show this help message" @echo "" - @echo "Legacy targets (backward compatibility):" - @echo " legacy - Build in legacy mode" - @echo " legacy-test - Run tests in legacy mode" - @echo "" @echo "Examples:" @echo " make # Build all" @echo " make test # Run tests" diff --git a/System_Programming_Projects/queue/queue.c b/System_Programming_Projects/queue/queue.c deleted file mode 100644 index 932f9e4..0000000 --- a/System_Programming_Projects/queue/queue.c +++ /dev/null @@ -1,139 +0,0 @@ -#include -#include -#include -#include - -/** - * @file queue.c - * @brief Thread-safe FIFO queue for producer-consumer patterns. - * - * Synchronization is provided with a mutex and per-waiter condition variable. - * If a consumer arrives before an item, it parks on a wait queue and is - * handed the next enqueued element directly, minimizing unnecessary wakeups. - */ - -typedef struct ItemNode { - void *data; - struct ItemNode *next; -} ItemNode; - -typedef struct WaitNode { - void *data_transfer; - cnd_t cond; - struct WaitNode *next; - int awakened; -} WaitNode; - -static ItemNode *item_head = NULL, *item_tail = NULL; -static WaitNode *wait_head = NULL, *wait_tail = NULL; -static mtx_t queue_lock; -static atomic_size_t visited_count; - -/** Initialize the queue state and synchronization primitives. */ -void initQueue(void) -{ - item_head = item_tail = NULL; - wait_head = wait_tail = NULL; - atomic_store(&visited_count, 0); - mtx_init(&queue_lock, mtx_plain); -} - -/** Destroy queue synchronization primitives. */ -void destroyQueue(void) -{ - mtx_destroy(&queue_lock); -} - -/** - * Enqueue an item in a FIFO manner, waking a waiting consumer if present. - * - * @return 0 on success, -1 if allocation fails. - */ -int enqueue(void *item) -{ - mtx_lock(&queue_lock); - - if (wait_head != NULL) { - WaitNode *w = wait_head; - wait_head = w->next; - if (wait_head == NULL) { - wait_tail = NULL; - } - - w->data_transfer = item; - w->awakened = 1; - cnd_signal(&w->cond); - mtx_unlock(&queue_lock); - return 0; - } - - ItemNode *node = malloc(sizeof(ItemNode)); - if (!node) { - fprintf(stderr, "enqueue: allocation failed\n"); - mtx_unlock(&queue_lock); - return -1; - } - node->data = item; - node->next = NULL; - if (item_tail == NULL) { - item_head = item_tail = node; - } else { - item_tail->next = node; - item_tail = node; - } - mtx_unlock(&queue_lock); - return 0; -} - -/** Dequeue an item, blocking if the queue is empty. */ -void *dequeue(void) -{ - mtx_lock(&queue_lock); - - if (item_head != NULL && wait_head == NULL) { - ItemNode *node = item_head; - item_head = node->next; - if (item_head == NULL) { - item_tail = NULL; - } - void *result = node->data; - free(node); - atomic_fetch_add_explicit(&visited_count, 1, memory_order_relaxed); - mtx_unlock(&queue_lock); - return result; - } - - WaitNode self; - self.next = NULL; - self.awakened = 0; - self.data_transfer = NULL; - cnd_init(&self.cond); - - if (wait_tail == NULL) { - wait_head = wait_tail = &self; - } else { - wait_tail->next = &self; - wait_tail = &self; - } - while (!self.awakened) { - cnd_wait(&self.cond, &queue_lock); - } - - void *result = self.data_transfer; - cnd_destroy(&self.cond); - atomic_fetch_add_explicit(&visited_count, 1, memory_order_relaxed); - mtx_unlock(&queue_lock); - - return result; -} - -/** - * @brief Return the number of items that have traversed the queue. - * - * The count is incremented when an element is dequeued, providing a - * non-blocking statistic for monitoring throughput. - */ -size_t visited(void) -{ - return atomic_load_explicit(&visited_count, memory_order_relaxed); -} diff --git a/System_Programming_Projects/queue/queue.h b/System_Programming_Projects/queue/queue.h deleted file mode 100644 index 9a9bcef..0000000 --- a/System_Programming_Projects/queue/queue.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @file queue.h - * @brief Thread-safe queue API. - */ - -#ifndef SYSTEM_PROGRAMMING_PROJECTS_QUEUE_H -#define SYSTEM_PROGRAMMING_PROJECTS_QUEUE_H - -#include - -void initQueue(void); -void destroyQueue(void); -int enqueue(void *item); -void *dequeue(void); -size_t visited(void); - -#endif /* SYSTEM_PROGRAMMING_PROJECTS_QUEUE_H */ diff --git a/System_Programming_Projects/queue/queue_test.c b/System_Programming_Projects/queue/queue_test.c deleted file mode 100644 index aa91e93..0000000 --- a/System_Programming_Projects/queue/queue_test.c +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @file queue_test.c - * @brief Simple producer-consumer harness for the queue implementation. - */ - -#define _POSIX_C_SOURCE 200809L -#include "queue.h" - -#include -#include -#include -#include -#include - -#define NUM_PRODUCERS 2 -#define NUM_CONSUMERS 2 -#define ITEMS_PER_PRODUCER 50 - -typedef struct { - int id; - int items_to_handle; -} thread_args; - -static int producer(void *arg) -{ - thread_args *args = arg; - for (int i = 0; i < args->items_to_handle; ++i) { - int *payload = malloc(sizeof(int)); - if (!payload) { - return -1; - } - *payload = args->id * 1000 + i; - if (enqueue(payload) != 0) { - free(payload); - return -1; - } - } - return 0; -} - -static int consumer(void *arg) -{ - thread_args *args = arg; - for (int i = 0; i < args->items_to_handle; ++i) { - int *payload = dequeue(); - if (payload) { - free(payload); - } - } - return 0; -} - -int main(void) -{ - initQueue(); - - const int total_items = NUM_PRODUCERS * ITEMS_PER_PRODUCER; - thread_args producer_args[NUM_PRODUCERS]; - thread_args consumer_args[NUM_CONSUMERS]; - thrd_t producer_threads[NUM_PRODUCERS]; - thrd_t consumer_threads[NUM_CONSUMERS]; - - struct timespec start, end; - clock_gettime(CLOCK_MONOTONIC, &start); - - for (int i = 0; i < NUM_PRODUCERS; ++i) { - producer_args[i].id = i; - producer_args[i].items_to_handle = ITEMS_PER_PRODUCER; - thrd_create(&producer_threads[i], producer, &producer_args[i]); - } - for (int i = 0; i < NUM_CONSUMERS; ++i) { - consumer_args[i].id = i; - consumer_args[i].items_to_handle = total_items / NUM_CONSUMERS; - thrd_create(&consumer_threads[i], consumer, &consumer_args[i]); - } - - for (int i = 0; i < NUM_PRODUCERS; ++i) { - thrd_join(producer_threads[i], NULL); - } - for (int i = 0; i < NUM_CONSUMERS; ++i) { - thrd_join(consumer_threads[i], NULL); - } - - clock_gettime(CLOCK_MONOTONIC, &end); - double elapsed = (end.tv_sec - start.tv_sec) + - (end.tv_nsec - start.tv_nsec) / 1e9; - - printf("Dequeued items: %zu\n", visited()); - printf("Elapsed: %.6f seconds\n", elapsed); - printf("Throughput: %.2f items/sec\n", visited() / elapsed); - - destroyQueue(); - return (visited() == (size_t)total_items) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/System_Programming_Projects/shell/myshell.c b/System_Programming_Projects/shell/myshell.c deleted file mode 100644 index 4ba67f0..0000000 --- a/System_Programming_Projects/shell/myshell.c +++ /dev/null @@ -1,295 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX_PIPE_CMDS 10 - -int process_arglist(int count, char **arglist); -int prepare(void); -int finalize(void); - -/** - * @file myshell.c - * @brief Minimal shell core demonstrating fork/execvp pipelines and redirection. - * - * The implementation focuses on low-level process management. It forks once per - * command, wires pipelines with pipe()/dup2(), and uses execvp() to overlay - * child processes. SIGCHLD is handled to prevent zombie processes. - */ - -/** - * @brief Handle SIGCHLD and reap children to prevent zombies. - * - * The handler loops with waitpid(WNOHANG) to reap any terminated child - * processes. Errors other than ECHILD are treated as fatal. - */ -static void handle_signal_child(int signum) -{ - (void)signum; - int old_errno = errno; - while (1) { - int child_status = 0; - pid_t child_pid = waitpid(-1, &child_status, WNOHANG); - if (child_pid > 0) { - continue; - } - if (child_pid == 0 || errno == ECHILD) { - break; - } - - fprintf(stderr, "waitpid error in SIGCHLD handler: %s\n", strerror(errno)); - exit(1); - } - errno = old_errno; -} - -/** - * @brief Install signal handling needed by the shell. - * - * - Ignore SIGINT in the shell so Ctrl+C only terminates children. - * - Handle SIGCHLD to reap children asynchronously. - * - * @return 0 on success, non-zero on failure. - */ -int prepare(void) -{ - struct sigaction ignore_int; - memset(&ignore_int, 0, sizeof(ignore_int)); - ignore_int.sa_handler = SIG_IGN; - ignore_int.sa_flags = SA_RESTART; - if (sigaction(SIGINT, &ignore_int, NULL) == -1) { - fprintf(stderr, "sigaction(SIGINT) failed: %s\n", strerror(errno)); - return 1; - } - - struct sigaction reap_children; - memset(&reap_children, 0, sizeof(reap_children)); - reap_children.sa_handler = handle_signal_child; - reap_children.sa_flags = SA_RESTART | SA_NOCLDSTOP; - if (sigaction(SIGCHLD, &reap_children, NULL) == -1) { - fprintf(stderr, "sigaction(SIGCHLD) failed: %s\n", strerror(errno)); - return 1; - } - - return 0; -} - -static void close_pipe_set(int pipes[][2], int pipe_count) -{ - for (int i = 0; i < pipe_count; ++i) { - close(pipes[i][0]); - close(pipes[i][1]); - } -} - -/** - * @brief Execute the parsed command list with support for pipelines, - * background execution, and simple redirection. - * - * The function relies on fork/execvp for process creation and uses pipes to - * connect multiple processes in a pipeline. Foreground children inherit the - * default SIGINT disposition while background ones ignore it. - * - * @param count Number of arguments (excluding terminating NULL). - * @param arglist Parsed command line terminated by NULL. - * - * @return 1 on success in the parent shell, 0 on failure. - */ -int process_arglist(int count, char **arglist) -{ - if (count <= 0 || arglist == NULL) { - return 1; - } - - int background = 0; - if (count >= 1 && strcmp(arglist[count - 1], "&") == 0) { - background = 1; - arglist[count - 1] = NULL; - count -= 1; - if (count == 0) { - return 1; - } - } - - int cmd_starts[MAX_PIPE_CMDS + 1] = {0}; - int cmd_count = 1; - for (int i = 0; i < count; ++i) { - if (strcmp(arglist[i], "|") == 0) { - arglist[i] = NULL; - cmd_starts[cmd_count++] = i + 1; - } - } - - if (cmd_count >= 2) { - if (background) { - fprintf(stderr, "Background execution is not supported with pipelines\n"); - return 0; - } - if (cmd_count > MAX_PIPE_CMDS) { - fprintf(stderr, "Pipeline too long (max %d commands)\n", MAX_PIPE_CMDS); - return 0; - } - - int pipes[MAX_PIPE_CMDS - 1][2]; - for (int i = 0; i < cmd_count - 1; ++i) { - if (pipe(pipes[i]) == -1) { - fprintf(stderr, "pipe failed: %s\n", strerror(errno)); - close_pipe_set(pipes, i); - return 0; - } - } - - pid_t child_pids[MAX_PIPE_CMDS] = {0}; - for (int i = 0; i < cmd_count; ++i) { - pid_t pid = fork(); - if (pid == -1) { - fprintf(stderr, "fork failed: %s\n", strerror(errno)); - close_pipe_set(pipes, cmd_count - 1); - return 0; - } - - if (pid == 0) { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_DFL; - sigaction(SIGINT, &sa, NULL); - - if (i > 0 && dup2(pipes[i - 1][0], STDIN_FILENO) == -1) { - fprintf(stderr, "dup2 failed: %s\n", strerror(errno)); - exit(1); - } - if (i < cmd_count - 1 && dup2(pipes[i][1], STDOUT_FILENO) == -1) { - fprintf(stderr, "dup2 failed: %s\n", strerror(errno)); - exit(1); - } - - close_pipe_set(pipes, cmd_count - 1); - - char **cmd_argv = &arglist[cmd_starts[i]]; - if (cmd_argv[0] == NULL) { - fprintf(stderr, "Empty command in pipeline\n"); - exit(1); - } - - execvp(cmd_argv[0], cmd_argv); - fprintf(stderr, "execvp failed (%s): %s\n", cmd_argv[0], strerror(errno)); - exit(1); - } - - child_pids[i] = pid; - } - - close_pipe_set(pipes, cmd_count - 1); - for (int i = 0; i < cmd_count; ++i) { - int status = 0; - while (waitpid(child_pids[i], &status, 0) == -1) { - if (errno == EINTR) { - continue; - } - if (errno == ECHILD) { - break; - } - fprintf(stderr, "waitpid failed: %s\n", strerror(errno)); - return 0; - } - } - - return 1; - } - - int redirect_in = 0; - int redirect_out = 0; - char *redir_file = NULL; - if (count >= 2 && strcmp(arglist[count - 2], "<") == 0) { - redirect_in = 1; - redir_file = arglist[count - 1]; - arglist[count - 2] = NULL; - } else if (count >= 2 && strcmp(arglist[count - 2], ">") == 0) { - redirect_out = 1; - redir_file = arglist[count - 1]; - arglist[count - 2] = NULL; - } - - pid_t pid = fork(); - if (pid == -1) { - fprintf(stderr, "fork failed: %s\n", strerror(errno)); - return 0; - } - - if (pid == 0) { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = (background ? SIG_IGN : SIG_DFL); - sigaction(SIGINT, &sa, NULL); - - if (redirect_in) { - int fd = open(redir_file, O_RDONLY); - if (fd == -1) { - fprintf(stderr, "open for input failed (%s): %s\n", redir_file, strerror(errno)); - exit(1); - } - if (dup2(fd, STDIN_FILENO) == -1) { - fprintf(stderr, "dup2 failed: %s\n", strerror(errno)); - close(fd); - exit(1); - } - close(fd); - } - if (redirect_out) { - int fd = open(redir_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd == -1) { - fprintf(stderr, "open for output failed (%s): %s\n", redir_file, strerror(errno)); - exit(1); - } - if (dup2(fd, STDOUT_FILENO) == -1) { - fprintf(stderr, "dup2 failed: %s\n", strerror(errno)); - close(fd); - exit(1); - } - close(fd); - } - - execvp(arglist[0], arglist); - fprintf(stderr, "execvp failed (%s): %s\n", arglist[0], strerror(errno)); - exit(1); - } - - if (!background) { - int status = 0; - while (waitpid(pid, &status, 0) == -1) { - if (errno == EINTR) { - continue; - } - if (errno == ECHILD) { - break; - } - fprintf(stderr, "waitpid failed: %s\n", strerror(errno)); - return 0; - } - } - - return 1; -} - -/** - * @brief Restore default signal dispositions before exit. - * - * @return 0 after handlers are restored. - */ -int finalize(void) -{ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_DFL; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGCHLD, &sa, NULL); - return 0; -} diff --git a/System_Programming_Projects/shell/shell_main.c b/System_Programming_Projects/shell/shell_main.c deleted file mode 100644 index de6b604..0000000 --- a/System_Programming_Projects/shell/shell_main.c +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @file shell_main.c - * @brief Minimal driver for myshell.c to demonstrate command execution. - */ - -#define _POSIX_C_SOURCE 200809L -#include -#include -#include - -int prepare(void); -int process_arglist(int count, char **arglist); -int finalize(void); - -static int parse_line(char *line, char ***out_args) -{ - *out_args = NULL; - int capacity = 16; - int count = 0; - char **args = malloc((size_t)capacity * sizeof(char *)); - if (!args) { - return -1; - } - - char *token = strtok(line, " \t\n"); - while (token != NULL) { - if (count >= capacity - 1) { - capacity *= 2; - char **tmp = realloc(args, (size_t)capacity * sizeof(char *)); - if (!tmp) { - free(args); - *out_args = NULL; - return -1; - } - args = tmp; - } - args[count++] = token; - token = strtok(NULL, " \t\n"); - } - - args[count] = NULL; - *out_args = args; - return count; -} - -int main(void) -{ - if (prepare() != 0) { - fprintf(stderr, "Failed to initialize shell\n"); - return EXIT_FAILURE; - } - - char *line = NULL; - size_t len = 0; - while (1) { - fputs("myshell> ", stdout); - fflush(stdout); - ssize_t read = getline(&line, &len, stdin); - if (read == -1) { - break; - } - if (read == 0 || strcmp(line, "\n") == 0) { - continue; - } - - char **args = NULL; - int argc = parse_line(line, &args); - if (argc < 0) { - fprintf(stderr, "Failed to parse command line (allocation error)\n"); - continue; - } - if (argc == 0) { - free(args); - continue; - } - - int ok = process_arglist(argc, args); - if (!ok) { - fprintf(stderr, "process_arglist reported an error\n"); - } - free(args); - } - - free(line); - finalize(); - return EXIT_SUCCESS; -}