diff --git a/.gitignore b/.gitignore index 2cafb9a..3d8789c 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ dkms.conf # Nix related things .direnv/ +result*/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba72f97 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# CS 3502 Operating Systems Assignments + +This repository contains my work for CS 3502: Operating Systems. diff --git a/assignment1/default.nix b/assignment1/default.nix new file mode 100644 index 0000000..293fb6b --- /dev/null +++ b/assignment1/default.nix @@ -0,0 +1,22 @@ +let + basic-c = name: { stdenv }: + stdenv.mkDerivation { + inherit name; + src = ./.; + buildPhase = '' + runHook preBuild + gcc part2/${name}.c -o ${name} + runHook postBuild + ''; + installPhase = '' + runHook preInstall + mkdir -p $out/bin + install ${name} $out/bin/ + runHook postInstall + ''; + }; +in { + a1-hello = basic-c "hello"; + a1-employee = basic-c "employee"; + a1-logwriter = basic-c "logwriter"; +} diff --git a/assignment1/part1/neofetch.png b/assignment1/part1/neofetch.png new file mode 100644 index 0000000..a2bd8bc Binary files /dev/null and b/assignment1/part1/neofetch.png differ diff --git a/assignment1/part1/nix_gcc.png b/assignment1/part1/nix_gcc.png new file mode 100644 index 0000000..ecb0860 Binary files /dev/null and b/assignment1/part1/nix_gcc.png differ diff --git a/assignment1/part2/employee.c b/assignment1/part2/employee.c new file mode 100644 index 0000000..70c3465 --- /dev/null +++ b/assignment1/part2/employee.c @@ -0,0 +1,34 @@ +#include +#include + +int main() { + // Character array to store name + // In C, strings are arrays of characters ending with '\0' + char name[50]; + int employee_id; + float hours_worked; + + printf("OwlTech Employee Registration\n"); + printf("=============================\n"); + + printf("Enter your name: "); + // fgets reads a line including spaces + // stdin means "standard input" (keyboard) + fgets(name, sizeof(name), stdin); + + // Remove newline that fgets includes + name[strcspn(name, "\n")] = '\0'; + + printf("Enter your employee ID: "); + // & means "address of" - scanf needs to know where to store the value + scanf("%d", &employee_id); + printf("Hours worked this week: "); + scanf("%f", &hours_worked); + + printf("\nEmployee Summary:\n"); + printf("Name: %s\n", name); // %s for string + printf("ID: %d\n", employee_id); // %d for integer + printf("Hours: %.2f\n", hours_worked); // %.2f for float with 2 decimals + + return 0; +} diff --git a/assignment1/part2/employee.png b/assignment1/part2/employee.png new file mode 100644 index 0000000..df1ef0e Binary files /dev/null and b/assignment1/part2/employee.png differ diff --git a/assignment1/part2/hello.c b/assignment1/part2/hello.c new file mode 100644 index 0000000..a936748 --- /dev/null +++ b/assignment1/part2/hello.c @@ -0,0 +1,8 @@ +#include + +int main() { + printf("Welcome to OwlTech Industries!\n"); + printf("Systems Programming Division\n"); + + return 0; +} diff --git a/assignment1/part2/hello.png b/assignment1/part2/hello.png new file mode 100644 index 0000000..4778b67 Binary files /dev/null and b/assignment1/part2/hello.png differ diff --git a/assignment1/part2/log.png b/assignment1/part2/log.png new file mode 100644 index 0000000..b415757 Binary files /dev/null and b/assignment1/part2/log.png differ diff --git a/assignment1/part2/logwriter.c b/assignment1/part2/logwriter.c new file mode 100644 index 0000000..d5e8087 --- /dev/null +++ b/assignment1/part2/logwriter.c @@ -0,0 +1,39 @@ +#include +#include +#include + +int main() { + FILE *logfile; // Pointer to file structure + char message[100]; + time_t current_time; + + // Open file for appending ("a" mode) + // "w" would overwrite, "r" would read + logfile = fopen("owltech.log", "a"); + + // Always check if file opened successfully + if (logfile == NULL) { + printf("Error: Could not open log file\n"); + return 1; // Return non-zero to indicate error + } + + printf("Enter log message: "); + fgets(message, sizeof(message), stdin); + + // Get current time + time(¤t_time); + + // Write to file with timestamp + // fprintf works like printf but writes to a file + fprintf(logfile, "[%s] %s", ctime(¤t_time), message); + + // Always close files when done + fclose(logfile); + printf("Log entry saved successfully!\n"); + + // Display the log file contents + printf("\n--- Current Log Contents ---\n"); + system("cat owltech.log"); + + return 0; +} diff --git a/assignment1/part2/logwriter.png b/assignment1/part2/logwriter.png new file mode 100644 index 0000000..0d07bb4 Binary files /dev/null and b/assignment1/part2/logwriter.png differ diff --git a/assignment1/part3/gitconfig.png b/assignment1/part3/gitconfig.png new file mode 100644 index 0000000..8e7f91d Binary files /dev/null and b/assignment1/part3/gitconfig.png differ diff --git a/assignment1/test.nix b/assignment1/test.nix new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/assignment1/test.nix @@ -0,0 +1 @@ +{} diff --git a/assignment2/default.nix b/assignment2/default.nix new file mode 100644 index 0000000..7f9f5b6 --- /dev/null +++ b/assignment2/default.nix @@ -0,0 +1,26 @@ +let + basic-c = subdir: name: { stdenv }: + stdenv.mkDerivation { + inherit name; + src = ./.; + buildPhase = '' + runHook preBuild + gcc ${subdir}/${name}.c -o ${name} + runHook postBuild + ''; + installPhase = '' + runHook preInstall + mkdir -p $out/bin + install ${name} $out/bin/ + runHook postInstall + ''; + }; +in { + a2-p1-producer = basic-c "part1" "producer"; + a2-p1-consumer = basic-c "part1" "consumer"; + + a2-p2-bidirectional = basic-c "part2" "bidirectional"; + + a2-p3-producer = basic-c "part3" "producer"; + a2-p3-consumer = basic-c "part3" "consumer"; +} diff --git a/assignment2/part1/consumer.c b/assignment2/part1/consumer.c new file mode 100644 index 0000000..8f0bc4b --- /dev/null +++ b/assignment2/part1/consumer.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + int max_lines = -1; // -1 means unlimited + int verbose = 0; + + // Parse arguments (-n max_lines, -v verbose) + char opt; + while ((opt = getopt(argc, argv, "n:v")) != -1) { + switch (opt) { + case 'n': + max_lines = atoi(optarg); break; + case 'v': + verbose = 1; break; + default: + printf("Usage: %s [-n max_lines] [-v]\n", argv[0]); + return 1; + } + } + + // Read from stdin line by line + // Count lines and characters + // If verbose, echo lines to stdout + char buffer[256]; + int lines = 0; + int chars = 0; + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + int size = strlen(buffer); + if (verbose) + fwrite(buffer, sizeof(char), size, stdout); + chars += size; + if (size > 0 && buffer[size - 1] == '\n') + lines++; + if (lines == max_lines) + break; + } + + // Print statistics to stderr + fprintf(stderr, "Lines: %d\n", lines); + + return 0; +} diff --git a/assignment2/part1/producer.c b/assignment2/part1/producer.c new file mode 100644 index 0000000..291e39c --- /dev/null +++ b/assignment2/part1/producer.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + FILE *input = stdin; + int buffer_size = 4096; + + // Parse command line arguments + // -f filename (optional) + // b buffer_size (optional) + char opt; + while ((opt = getopt(argc, argv, "f:b:")) != -1) { + switch (opt) { + case 'f': + input = fopen(optarg, "r"); + if (input == NULL) { + fprintf(stderr, "Error: Could not open input file\n"); + return EXIT_FAILURE; + } + break; + case 'b': + buffer_size = atoi(optarg); break; + default: + printf("Usage: %s [-f filename] [-b buffer_size]\n", argv[0]); + return 1; + } + } + + // Allocate buffer + char *buffer = malloc(buffer_size); + + // Read from input and write to stdout + while (fgets(buffer, buffer_size, input) != NULL) { + fwrite(buffer, sizeof(char), strlen(buffer), stdout); + } + + // Cleanup + free(buffer); + + return 0; +} diff --git a/assignment2/part1/test b/assignment2/part1/test new file mode 100755 index 0000000..4ed3156 --- /dev/null +++ b/assignment2/part1/test @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +echo "Building executables..." + +cd "${FLAKE:-$(dirname $0)}" +if command -v nix; then + producer="$(nix build --no-link --print-out-paths .#a2-p1-producer)/bin/producer" + consumer="$(nix build --no-link --print-out-paths .#a2-p1-consumer)/bin/consumer" +else + gcc producer.c -o producer + gcc consumer.c -o consumer + producer="./producer" + consumer="./consumer" +fi + +echo -e "\nTEST - Sanity Check" + +out="$(seq -s " " 1 10 | "$producer" | "$consumer" -v)" +if test "$out" = "$(seq -s " " 1 10)"; then + echo "SUCCESS" +fi + +echo -e "\nTEST - Smaller Buffer Size" + +out="$(seq -s " " 1 10 | "$producer" -b 16 | "$consumer" -v)" +if test "$out" = "$(seq -s " " 1 10)"; then + echo "SUCCESS" +fi + +echo -e "\nTEST - Large External File" + +head -c 10000 random.txt +out="$("$producer" -f random.txt | "$consumer" -v)" +if test "$out" = "$(cat random.txt)"; then + echo "SUCCESS" +fi +rm random.txt + + +echo -e "\nTEST - Maximum Lines (5)" + +head -c 1000 random.txt +out="$("$producer" -f random.txt | "$consumer" -v -n 5)" +if test "$out" = "$(cat random.txt | head -n 5)"; then + echo "SUCCESS" +fi +rm random.txt diff --git a/assignment2/part2/bidirectional.c b/assignment2/part2/bidirectional.c new file mode 100644 index 0000000..c123f51 --- /dev/null +++ b/assignment2/part2/bidirectional.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include + +int main () { + int pipe1[2]; // Parent to child + int pipe2[2]; // Child to parent + + // Create both pipes + if (pipe(pipe1) == -1 || pipe(pipe2) == -1) { + fprintf(stderr, "Could not open pipes"); + return 1; + } + + // Fork process + pid_t pid = fork(); + + if (pid == 0) { + // Child process + + // Close unused pipe ends + close(pipe1[1]) ; // Close write end of pipe1 + close(pipe2[0]) ; // Close read end of pipe2 + + // Read message, send response + char *message = malloc(100); + read(pipe1[0], message, 100); + printf("%s", message); + free(message); + + char response[] = "Response from child to parent\n"; + write(pipe2[1], response, sizeof(response)); + } else { + // Parent process + + // Close unused pipe ends + close(pipe1[0]) ; // Close read end of pipe1 + close(pipe2[1]) ; // Close write end of pipe2 + + // Send message, read response + char message[] = "Sending message from parent to child\n"; + write(pipe1[1], message, sizeof(message)); + + char *response = malloc(100); + read(pipe2[0], response, 100); + printf("%s", response); + free(response); + + // Wait for child process to terminate + waitpid(pid, NULL, 0); + } + + return 0; +} diff --git a/assignment2/part2/test b/assignment2/part2/test new file mode 100755 index 0000000..f036c53 --- /dev/null +++ b/assignment2/part2/test @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +echo "Building executable..." + +cd "${FLAKE:-$(dirname $0)}" +if command -v nix; then + bidirectional="$(nix build --no-link --print-out-paths .#a2-p2-bidirectional)/bin/bidirectional" +else + gcc bidirectional.c -o bidirectional + bidirectional="./bidirectional" +fi + +out="$("$bidirectional")" +read -r -d '' expected < +#include +#include +#include +#include +#include +#include + +volatile sig_atomic_t shutdown_flag = 0; +volatile sig_atomic_t stats_flag = 0; + +void handle_sigint(int sig) { + shutdown_flag = 1; +} + +void handle_sigusr1(int sig) { + stats_flag = 1; +} + +int main(int argc, char *argv[]) { + struct sigaction sa_sigint; + sa_sigint.sa_handler = handle_sigint; + sigemptyset(&sa_sigint.sa_mask); + sa_sigint.sa_flags = 0; + sigaction(SIGINT, &sa_sigint, NULL); + + struct sigaction sa_sigusr1; + sa_sigusr1.sa_handler = handle_sigusr1; + sigemptyset(&sa_sigusr1.sa_mask); + sa_sigusr1.sa_flags = 0; + sigaction(SIGUSR1, &sa_sigusr1, NULL); + + int max_lines = -1; // -1 means unlimited + int verbose = 0; + + // Parse arguments (-n max_lines, -v verbose) + char opt; + while ((opt = getopt(argc, argv, "n:v")) != -1) { + switch (opt) { + case 'n': + max_lines = atoi(optarg); break; + case 'v': + verbose = 1; break; + default: + printf("Usage: %s [-n max_lines] [-v]\n", argv[0]); + return 1; + } + } + + clock_t start = clock(); + + // Read from stdin line by line + // Count lines and characters + // If verbose, echo lines to stdout + char buffer[256]; + int lines = 0; + int chars = 0; + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + int size = strlen(buffer); + if (verbose) + fwrite(buffer, sizeof(char), size, stdout); + chars += size; + if (size > 0 && buffer[size - 1] == '\n') + lines++; + if (lines == max_lines) + break; + + // Handle shutdown + if (shutdown_flag) { + fprintf(stderr, "Cancelled\n"); + return 1; + } + } + + // Print statistics to stderr (handle SIGUSR1) + if (stats_flag) { + clock_t end = clock(); + double time = ((double)(end - start)) / CLOCKS_PER_SEC; + double throughput = (double)chars / 1024.0 / 1024.0 / time; + printf("Stats:\n"); + printf("File Size: %d bytes\n", chars); + printf("Total Time: %.3f secs\n", time); + printf("Throughput: %.3f MB/s\n", throughput); + } + fprintf(stderr, "Lines: %d\n", lines); + + return 0; +} diff --git a/assignment2/part3/producer.c b/assignment2/part3/producer.c new file mode 100644 index 0000000..fb634fc --- /dev/null +++ b/assignment2/part3/producer.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include + +volatile sig_atomic_t shutdown_flag = 0; + +void handle_sigint(int sig) { + shutdown_flag = 1; +} + +int main(int argc, char *argv[]) { + struct sigaction sa; + sa.sa_handler = handle_sigint; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, NULL); + + FILE *input = stdin; + int buffer_size = 4096; + + // Parse command line arguments + // -f filename (optional) + // b buffer_size (optional) + char opt; + while ((opt = getopt(argc, argv, "f:b:")) != -1) { + switch (opt) { + case 'f': + input = fopen(optarg, "r"); + if (input == NULL) { + fprintf(stderr, "Error: Could not open input file\n"); + return EXIT_FAILURE; + } + break; + case 'b': + buffer_size = atoi(optarg); break; + default: + printf("Usage: %s [-f filename] [-b buffer_size]\n", argv[0]); + return 1; + } + } + + // Allocate buffer + char *buffer = malloc(buffer_size); + + // Read from input and write to stdout + while (fgets(buffer, buffer_size, input) != NULL) { + fwrite(buffer, sizeof(char), strlen(buffer), stdout); + + // Handle shutdown + if (shutdown_flag) { + fprintf(stderr, "Cancelled\n"); + return 1; + } + } + + // Cleanup + free(buffer); + + return 0; +} diff --git a/assignment2/part3/test b/assignment2/part3/test new file mode 100755 index 0000000..d81d0a7 --- /dev/null +++ b/assignment2/part3/test @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +echo "Building executables..." + +cd "${FLAKE:-$(dirname $0)}" +if command -v nix; then + producer="$(nix build --no-link --print-out-paths .#a2-p3-producer)/bin/producer" + consumer="$(nix build --no-link --print-out-paths .#a2-p3-consumer)/bin/consumer" +else + gcc producer.c -o producer + gcc consumer.c -o consumer + producer="./producer" + consumer="./consumer" +fi + +seq 1 10000000 >large.txt + +echo -e "\nTEST - Buffer size 1024" +"$producer" -b 1024 -f large.txt | "$consumer" & +kill -USR1 "$(pidof consumer)" +wait "$(jobs -rp)" + +echo -e "\nTEST - Buffer size 4096" +"$producer" -b 4096 -f large.txt | "$consumer" & +kill -USR1 "$(pidof consumer)" +wait "$(jobs -rp)" + +echo -e "\nTEST - Buffer size 16384" +"$producer" -b 16384 -f large.txt | "$consumer" & +kill -USR1 "$(pidof consumer)" +wait "$(jobs -rp)" + +rm large.txt diff --git a/assignment2/test.nix b/assignment2/test.nix new file mode 100644 index 0000000..f04cf34 --- /dev/null +++ b/assignment2/test.nix @@ -0,0 +1,12 @@ +let + bash-test = subdir: { runCommandLocal }: + runCommandLocal "${subdir}-test" { FLAKE = ../.; } + '' + mkdir -p $out/bin + install ${subdir}/test $out/bin/ + ''; +in { + a2-p1 = bash-test "part1"; + a2-p2 = bash-test "part2"; + a2-p3 = bash-test "part3"; +} diff --git a/flake.nix b/flake.nix index bccf8ee..03d2962 100644 --- a/flake.nix +++ b/flake.nix @@ -7,11 +7,25 @@ }; outputs = { self, nixpkgs, systems, ... }: - let eachSystem = nixpkgs.lib.genAttrs (import systems); - in { - packages = eachSystem (system: + let + subdirs = [ + "assignment1" + "assignment2" + ]; + + eachSystem = nixpkgs.lib.genAttrs (import systems); + + importFromSubdirs = file: eachSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; - in {}); + in pkgs.lib.mergeAttrsList + (builtins.map (d: + builtins.mapAttrs + (_: v: pkgs.callPackage v {}) + (import ./${d}/${file})) + subdirs)); + in { + packages = importFromSubdirs "default.nix"; + checks = importFromSubdirs "test.nix"; devShells = eachSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; diff --git a/shell.nix b/shell.nix index e155fa2..cc840a0 100644 --- a/shell.nix +++ b/shell.nix @@ -1,2 +1,4 @@ -{ pkgs }: -pkgs.mkShell {} +{ mkShell }: + +# GCC is provided by default +mkShell {}