diff --git a/Makefile b/Makefile index cf031b0..94ecd48 100644 --- a/Makefile +++ b/Makefile @@ -142,20 +142,28 @@ UPROGS=\ $U/_logstress\ $U/_forphan\ $U/_dorphan\ + $U/_join\ -fs.img: mkfs/mkfs README.md $(UPROGS) - mkfs/mkfs fs.img README.md $(UPROGS) +TDIR = tests + +fs.img: mkfs/mkfs README.md $(UPROGS) $(TDIR)/file1.txt $(TDIR)/file2.txt $(TDIR)/students.txt $(TDIR)/grades.txt $(TDIR)/products.txt $(TDIR)/prices.txt $(TDIR)/Fog_Emp.txt $(TDIR)/Fog_Perf.txt $(TDIR)/MC_Item.txt $(TDIR)/MC_Price.txt $(TDIR)/empty.txt + cp $(TDIR)/*.txt . + mkfs/mkfs fs.img README.md $(UPROGS) file1.txt file2.txt students.txt grades.txt products.txt prices.txt Fog_Emp.txt Fog_Perf.txt MC_Item.txt MC_Price.txt empty.txt -include kernel/*.d user/*.d clean: - rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ + rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg file1.txt file2.txt students.txt grades.txt products.txt prices.txt Fog_Emp.txt Fog_Perf.txt MC_Item.txt MC_Price.txt empty.txt \ */*.o */*.d */*.asm */*.sym \ $K/kernel fs.img \ mkfs/mkfs .gdbinit \ $U/usys.S \ $(UPROGS) +qemu-clean: check-qemu-version $K/kernel fs.img + $(QEMU) $(QEMUOPTS) + $(MAKE) clean + # try to generate a unique GDB port GDBPORT = $(shell expr `id -u` % 5000 + 25000) # QEMU's gdb stub command line changed in 0.11 diff --git a/README.md b/README.md index 85f6164..e0035be 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,27 @@ Fall 2025 Edition ![FogOS](docs/fogos.gif) +## Documentation +### Quick Links +- [Building the Software](docs/Documentation.md#building-the-software) +- [Running the Software](docs/Documentation.md#running-the-software) +- [Testing the Software](docs/Documentation.md#testing-the-software) +- [Cleaning Up](docs/Documentation.md#cleaning-up) +- [Project Structure](docs/Documentation.md#project-structure) + +### Quick Start +```bash +# Build the project +make + +# Run QEMU +make qemu + +# Exit: Ctrl+A then X + +# Clean up +make clean +``` + +For detailed documentation, see [Documentation.md](docs/Documentation.md) diff --git a/docs/Documentation.md b/docs/Documentation.md new file mode 100644 index 0000000..4df007c --- /dev/null +++ b/docs/Documentation.md @@ -0,0 +1,111 @@ +# Software Documentation + +## Building the Software + +### Prerequisites +- RISC-V toolchain (riscv64-unknown-elf-gcc or riscv64-linux-gnu-gcc) +- QEMU RISC-V emulator (version 7.2 or higher) +- Make build system + +### Build Steps +```bash +make +``` +This will: +- Compile the kernel from source files in the `kernel/` directory +- Compile user programs from the `user/` directory +- Copy test data files from `tests/` to the root directory +- Create the filesystem image (`fs.img`) containing all programs and data files + +## Running the Software + +### Start QEMU +```bash +make qemu +``` +This launches the RISC-V emulator with the compiled kernel and filesystem. + +### Exit QEMU +Press `Ctrl+A` then `X` to quit the emulator. + +**Important:** After exiting QEMU, run `make clean` to remove build artifacts and copied test files. + +### Run with automatic cleanup +```bash +make qemu-clean +``` +This runs QEMU and automatically cleans build artifacts after you exit. + +## Testing the Software + +### Understanding the join command +The `join` command merges two text files based on matching first fields (words) in each line. For each match found, it outputs a combined line in the format: +``` + +``` + +### Access test data files +Once in QEMU, the test data files are available in the filesystem: +- `file1.txt`, `file2.txt` - Sample department data (25 lines each) +- `MC_Item.txt`, `MC_Price.txt` - Inventory data +- `students.txt`, `grades.txt` - Student records +- `products.txt`, `prices.txt` - Product information +- `Fog_Emp.txt`, `Fog_Perf.txt` - Employee data +- `empty.txt` - Empty test file + +### Run join command + +**Basic usage (output to console):** +```bash +join file1.txt file2.txt +``` +Expected output: 25 joined lines matching names from both files, displaying department info with grades. + +**With output file:** +```bash +join file1.txt file2.txt output.txt +``` +This saves the joined results to `output.txt` while also displaying them on console. + +**Get help:** +```bash +join --help +``` + +**Example output format:** +When joining students.txt and grades.txt, each matching line combines: +- The matching first field (e.g., "001") +- The remainder of the line from students.txt (e.g., "Alice Engineering Robotics") +- The remainder of the line from grades.txt (e.g., "A+ Excellent Outstanding") + +``` +001 Alice Engineering Robotics A+ Excellent Outstanding +002 Bob Physics Quantum B+ Good Solid +003 Charlie Chemistry Polymers A- Very Strong +005 Ethan Biology Genetics B Fair Average +join completed +... +``` + +The output shows 4 total matches (one for each person in both files). + +## Cleaning Up + +### Remove all build artifacts and copied test files +```bash +make clean +``` +This removes: +- Compiled object files (*.o) +- Kernel binary and filesystem image +- Temporary files +- Test data files copied to root (preserves originals in `tests/`) +- Note: `compile_flags.txt` in root is preserved + +## Project Structure + +- `kernel/` - Operating system kernel source code +- `user/` - User-space programs and utilities +- `tests/` - Original test data files (preserved during clean) +- `mkfs/` - Filesystem creation utility +- `Makefile` - Build configuration and targets diff --git a/kernel/syscall.c b/kernel/syscall.c index 076d965..6dc5deb 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -102,6 +102,7 @@ extern uint64 sys_link(void); extern uint64 sys_mkdir(void); extern uint64 sys_close(void); + // An array mapping syscall numbers from syscall.h // to the function that handles the system call. static uint64 (*syscalls[])(void) = { diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 3044d00..6cc0bc6 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -105,3 +105,4 @@ sys_uptime(void) release(&tickslock); return xticks; } + diff --git a/tests/Fog_Emp.txt b/tests/Fog_Emp.txt new file mode 100644 index 0000000..300bcae --- /dev/null +++ b/tests/Fog_Emp.txt @@ -0,0 +1,40 @@ +EMP001 Alice Johnson Manager Sales Northeast +EMP002 Bob Smith Developer IT Backend +EMP003 Carol Davis Analyst Finance Budgets +EMP004 David Wilson Designer Marketing Creative +EMP005 Emma Brown Support Operations Customer +EMP006 Frank Miller Engineer IT Systems +EMP007 Grace Lee Specialist HR Recruiting +EMP008 Henry Chen Coordinator Marketing Events +EMP009 Iris Taylor Administrator Finance Payroll +EMP010 Jack Rodriguez Developer IT Frontend +EMP011 Kate Thompson Manager Operations Supply +EMP012 Luis Garcia Analyst Data Business +EMP013 Maria Gonzalez Designer UX Research +EMP014 Nathan Park Engineer Software Senior +EMP015 Olivia White Specialist Sales Enterprise +EMP016 Paul Kim Coordinator HR Benefits +EMP017 Quinn Adams Administrator IT Security +EMP018 Rachel Turner Manager Finance Treasury +EMP019 Samuel Lee Developer Mobile Applications +EMP020 Tara Patel Analyst Marketing Consumer +EMP021 Ulysses Jones Engineer DevOps Cloud +EMP022 Victoria Chang Specialist Operations Logistics +EMP023 William Hayes Coordinator Sales Regional +EMP024 Ximena Rivera Administrator Marketing Social +EMP025 Yuki Tanaka Manager IT Infrastructure +EMP026 Zachary Moore Developer Full Stack +EMP027 Abby Foster Analyst HR Metrics +EMP028 Blake Cooper Engineer Quality Assurance +EMP029 Chloe Ward Specialist Finance Audit +EMP030 Dylan Scott Coordinator IT Help +EMP031 Elena Morales Administrator Operations Facilities +EMP032 Felix Chen Manager Sales International +EMP033 Gina Roberts Developer Database Systems +EMP034 Hugo Martinez Analyst Operations Process +EMP035 Ivy Johnson Specialist Marketing Digital +EMP036 Jake Williams Engineer Network Security +EMP037 Kara Davis Coordinator Finance Accounting +EMP038 Logan Brown Administrator HR Training +EMP039 Mia Thompson Manager Operations Quality +EMP040 Noah Garcia Developer API Services diff --git a/tests/Fog_Perf.txt b/tests/Fog_Perf.txt new file mode 100644 index 0000000..689f6ab --- /dev/null +++ b/tests/Fog_Perf.txt @@ -0,0 +1,30 @@ +EMP001 Excellent Rating5 Promotion Ready +EMP003 Good Rating4 Steady Progress +EMP005 Outstanding Rating5 Top Performer +EMP007 Satisfactory Rating3 Meeting Goals +EMP009 Excellent Rating5 Leadership Potential +EMP011 Good Rating4 Strong Contributor +EMP013 Outstanding Rating5 Innovation Leader +EMP015 Satisfactory Rating3 Reliable Worker +EMP017 Good Rating4 Technical Expert +EMP019 Excellent Rating5 Rising Star +EMP021 Outstanding Rating5 Architecture Lead +EMP023 Satisfactory Rating3 Consistent Results +EMP025 Excellent Rating5 Strategic Thinker +EMP027 Good Rating4 Data Expert +EMP029 Outstanding Rating5 Process Optimizer +EMP031 Satisfactory Rating3 Team Player +EMP033 Excellent Rating5 Database Guru +EMP035 Good Rating4 Creative Marketer +EMP037 Outstanding Rating5 Financial Wizard +EMP039 Satisfactory Rating3 Quality Focused +EMP002 Good Rating4 Code Quality +EMP004 Excellent Rating5 Design Leader +EMP006 Outstanding Rating5 System Architect +EMP008 Satisfactory Rating3 Event Success +EMP010 Good Rating4 Frontend Expert +EMP012 Excellent Rating5 Analytics Pro +EMP014 Outstanding Rating5 Senior Mentor +EMP016 Satisfactory Rating3 Benefits Admin +EMP018 Good Rating4 Treasury Manager +EMP020 Excellent Rating5 Market Researcher diff --git a/tests/MC_Item.txt b/tests/MC_Item.txt new file mode 100644 index 0000000..225b5a7 --- /dev/null +++ b/tests/MC_Item.txt @@ -0,0 +1,45 @@ +SKU1001 Laptop Dell Inspiron 15 +SKU1002 Monitor Samsung 27inch 4K +SKU1003 Keyboard Mechanical RGB Gaming +SKU1004 Mouse Wireless Ergonomic Office +SKU1005 Headphones Noise Cancelling Premium +SKU1006 Webcam HD 1080p Conference +SKU1007 Printer Laser Color Network +SKU1008 Scanner Flatbed Document High +SKU1009 Tablet iPad Pro 12inch +SKU1010 Smartphone iPhone 15 Pro +SKU1011 Smartwatch Apple Watch Series +SKU1012 Speaker Bluetooth Portable Waterproof +SKU1013 Charger USB-C Fast Wireless +SKU1014 Cable HDMI 4K Premium +SKU1015 Adapter USB Hub Multi +SKU1016 Drive SSD External 1TB +SKU1017 Memory RAM DDR4 32GB +SKU1018 Graphics Card RTX 4080 +SKU1019 Processor Intel i7 13th +SKU1020 Motherboard ASUS Gaming ATX +SKU1021 Case PC Tower RGB +SKU1022 Fan Cooling Liquid AIO +SKU1023 Power Supply 850W Modular +SKU1024 Router WiFi 6 Mesh +SKU1025 Switch Network 24 Port +SKU1026 Camera DSLR Canon Professional +SKU1027 Lens Telephoto 70-200mm Canon +SKU1028 Tripod Carbon Fiber Professional +SKU1029 Light LED Panel Studio +SKU1030 Microphone USB Condenser Podcast +SKU1031 Interface Audio USB Recording +SKU1032 Mixer Digital 8 Channel +SKU1033 Drone Quadcopter 4K Camera +SKU1034 Battery Drone LiPo 3S +SKU1035 Controller Game Wireless Pro +SKU1036 Console Gaming Latest Generation +SKU1037 Headset Gaming Surround 7.1 +SKU1038 Chair Gaming Ergonomic Racing +SKU1039 Desk Gaming L-Shaped Standing +SKU1040 Monitor Arm Dual Display +SKU1041 Lamp Desk LED Adjustable +SKU1042 Fan Desk USB Quiet +SKU1043 Heater Space Ceramic Safety +SKU1044 Purifier Air HEPA Smart +SKU1045 Humidifier Ultrasonic Cool Mist diff --git a/tests/MC_Price.txt b/tests/MC_Price.txt new file mode 100644 index 0000000..604d552 --- /dev/null +++ b/tests/MC_Price.txt @@ -0,0 +1,29 @@ +SKU1001 $899.99 InStock Warehouse-A +SKU1003 $149.99 InStock Warehouse-B +SKU1005 $299.99 LowStock Warehouse-A +SKU1007 $449.99 InStock Warehouse-C +SKU1009 $1099.99 OutOfStock Backorder +SKU1011 $399.99 InStock Warehouse-A +SKU1013 $79.99 InStock Warehouse-B +SKU1015 $49.99 InStock Warehouse-C +SKU1017 $189.99 LowStock Warehouse-A +SKU1019 $329.99 InStock Warehouse-B +SKU1021 $129.99 InStock Warehouse-C +SKU1023 $159.99 LowStock Warehouse-A +SKU1025 $299.99 InStock Warehouse-B +SKU1027 $899.99 OutOfStock Backorder +SKU1029 $199.99 InStock Warehouse-C +SKU1031 $149.99 InStock Warehouse-A +SKU1033 $599.99 LowStock Warehouse-B +SKU1035 $69.99 InStock Warehouse-C +SKU1037 $199.99 InStock Warehouse-A +SKU1039 $399.99 LowStock Warehouse-B +SKU1041 $89.99 InStock Warehouse-C +SKU1043 $79.99 InStock Warehouse-A +SKU1045 $129.99 InStock Warehouse-B +SKU1002 $349.99 InStock Warehouse-A +SKU1004 $59.99 InStock Warehouse-B +SKU1006 $89.99 LowStock Warehouse-C +SKU1008 $249.99 InStock Warehouse-A +SKU1010 $999.99 InStock Warehouse-B +SKU1012 $99.99 InStock Warehouse-C diff --git a/tests/empty.txt b/tests/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/file1.txt b/tests/file1.txt new file mode 100644 index 0000000..2cd7416 --- /dev/null +++ b/tests/file1.txt @@ -0,0 +1,25 @@ +Alice Engineering Dept Robotics +Bob Physics Dept Quantum Studies +Charlie Chemistry Dept Polymers +Diana Math Dept Applied Modeling +Ethan Biology Dept Genetics Focus +Fiona Economics Dept Global Trade +George Literature Dept Classics +Hannah History Dept Ancient World +Ian Philosophy Dept Ethics +Julia Music Dept Composition +Kevin Art Dept Modern Painting +Laura CS Dept Machine Learning +Mark Stats Dept Data Analysis +Nina Linguistics Dept Semantics +Oliver Sociology Dept Inequality +Paula Psychology Dept Cognition +Quinn Anthropology Dept Culture +Rachel Astronomy Dept Cosmology +Steve PoliSci Dept Governance +Tina EnvSci Dept Climate Studies +Uma Business Dept Marketing +Victor Education Dept Pedagogy +Wendy Geography Dept GIS Mapping +Xavier Law Dept International +Yara Medicine Dept Physiology diff --git a/tests/file2.txt b/tests/file2.txt new file mode 100644 index 0000000..413c653 --- /dev/null +++ b/tests/file2.txt @@ -0,0 +1,25 @@ +GradeA GPA3.9 HighPerformance +GradeB GPA3.1 AveragePerformance +GradeA GPA3.8 StrongPerformance +GradeA GPA3.7 SolidPerformance +GradeB GPA3.4 GoodPerformance +GradeC GPA2.5 BelowPerformance +GradeC GPA2.7 WeakPerformance +GradeB GPA3.3 ConsistentWork +GradeA GPA3.9 ExcellentWork +GradeA GPA4.0 OutstandingWork +GradeB GPA3.2 SatisfactoryWork +GradeB GPA3.0 FairPerformance +GradeA GPA3.5 StrongAnalysis +GradeA GPA3.6 ClearThinking +GradeB GPA3.4 GroupWorkGood +GradeC GPA2.8 NeedsImprovement +GradeC GPA2.6 PoorRetention +GradeA GPA3.9 ExcellentResearch +GradeA GPA3.8 HighAchievement +GradeB GPA3.2 StableWork +GradeB GPA3.0 AverageResults +GradeA GPA3.8 GoodLearning +GradeA GPA3.9 HighAccuracy +GradeB GPA3.1 FairResults +GradeB GPA3.3 SolidWork diff --git a/tests/grades.txt b/tests/grades.txt new file mode 100644 index 0000000..07b9dfb --- /dev/null +++ b/tests/grades.txt @@ -0,0 +1,5 @@ +001 A+ Excellent Outstanding +002 B+ Good Solid +003 A- Very Strong +005 B Fair Average +006 C Below Standard diff --git a/tests/prices.txt b/tests/prices.txt new file mode 100644 index 0000000..e98bf5e --- /dev/null +++ b/tests/prices.txt @@ -0,0 +1,5 @@ +P001 $999 Premium High +P003 $599 Mid Reasonable +P004 $299 Budget Low +P007 $199 Clearance Sale +P008 $149 Discount Special diff --git a/tests/products.txt b/tests/products.txt new file mode 100644 index 0000000..7d85455 --- /dev/null +++ b/tests/products.txt @@ -0,0 +1,5 @@ +P001 Laptop Electronics Computing +P002 Phone Mobile Communications +P003 Tablet Portable Media +P004 Watch Wearable Tech +P005 Headphones Audio Sound diff --git a/tests/students.txt b/tests/students.txt new file mode 100644 index 0000000..7b57ae3 --- /dev/null +++ b/tests/students.txt @@ -0,0 +1,5 @@ +001 Alice Engineering Robotics +002 Bob Physics Quantum +003 Charlie Chemistry Polymers +004 Diana Math Modeling +005 Ethan Biology Genetics diff --git a/user/join.c b/user/join.c new file mode 100644 index 0000000..a53d34b --- /dev/null +++ b/user/join.c @@ -0,0 +1,347 @@ +#include "kernel/types.h" +#include "user/user.h" +#include "kernel/stat.h" +#include "kernel/fcntl.h" + + +// // Hector's functions +//void join_files(char file1_lines[][256], int count1, char file2_lines[][256], int count2, const char* output_file)); +//void print_joined_line(int output_file, char* field, char* rest1, char* rest2); +//char* get_rest_of_line(char* line); +// +// +// // Demetrius's functions +//char* get_first_field(char* line); +// int read_file_lines(char* filename, char lines[][256], int count2); + +// helper functions +//compare_strings(char* str1, char* str2) +//terminate_first_field(char* line) + +/** + * String comparison iterates through each char in both + * strings to see if they match if non return non-zero + */ + +static char file1_lines[50][256]; +static char file2_lines[50][256]; + +int compare_strings(char* str1, char* str2) { + while (*str1 && *str2) { + if (*str1 != *str2) { + return *str1 - *str2; + } + str1++; + str2++; + } + return *str1 - *str2; +} + +/** + * Extracts the first field (word) from a line + * Returns a pointer to the first field, or NULL if line is empty + */ + +char* get_first_field(char* line) { + if (line == 0 || *line == '\0') { + return 0; + } + + // Skip leading whitespace + while (*line == ' ' || *line == '\t') { + line++; + } + + // If line is empty after skipping whitespace + if (*line == '\0') { + return 0; + } + + return line; +} + +/** + * Function to get the rest of the line so it is everything + * after the first word +*/ +char* get_rest_of_line(char* line) { + + // return empty if line is not valid + if(line == 0 || *line == '\0') { + return ""; + } + + char* ptr = line; + // skip leading whitespace + while(*ptr == ' ' || *ptr == '\t') { + ptr++; + } + // skip first field until reached whitespace or end of line + while (*ptr != '\0' && *ptr != ' ' && *ptr != '\t' ) { + ptr++; + } + // skip whitespace between first field and rest + while(*ptr == ' ' || *ptr == '\t') { + ptr++; + } + return ptr; +} + +void terminate_first_field(char* line) { + char* ptr = line; + + // skip whitespace + while (*ptr == ' ' || *ptr == '\t') { + ptr++; + } + + // get the end of the first word + while (*ptr != '\0' && *ptr != ' ' && *ptr != '\n' && *ptr != '\t') { + ptr++; + } + // terminate first word + if(*ptr != '\0') { + *ptr = '\0'; + } +} + +/** + * Reads lines from a file into the provided array + * Returns the number of lines successfully read + * Returns 0 if file cannot be opened + * Returns -1 if metadata cannot be retrieved + * Returns -2 if file is empty + */ +int read_file_lines(char* filename, char lines[][256], int max_count) { + int file = open(filename, 0); // 0 = O_RDONLY + struct stat st; // stat struct contains a member called size, which holds the file's size in bytes + + if (file < 0) { + close(file); + return 0; + } + + if (fstat(file, &st) < 0) { //check metadata + close(file); + return -1; + } + + if (st.size == 0) { //check size to see if empty txt or not, join does not work if any or both files empty + close(file); + return -2; + } + + int line_count = 0; + char buffer[256]; + int pos = 0; + char c; + + while (line_count < max_count && read(file, &c, 1) > 0) { + if (c == '\n') { + // End of line found + buffer[pos] = '\0'; + + // Copy the line to the array + int i = 0; + while (i < 255 && buffer[i] != '\0') { + lines[line_count][i] = buffer[i]; + i++; + } + lines[line_count][i] = '\0'; + + line_count++; + pos = 0; // Reset position for next line + } else if (pos < 255) { + // Add character to buffer + buffer[pos] = c; + pos++; + } + } + + // Handle the last line if file doesn't end with newline + if (pos > 0 && line_count < max_count) { + buffer[pos] = '\0'; + int i = 0; + while (i < 255 && buffer[i] != '\0') { + lines[line_count][i] = buffer[i]; + i++; + } + lines[line_count][i] = '\0'; + line_count++; + } + + close(file); + return line_count; +} + +/** + * function prints joined line + * "first word" "rest line file 1" "rest line file 2" +*/ +void print_joined_line(int output_file, char* field, char* rest1, char* rest2) { + // Only print to console if NOT writing to a file + if (output_file < 0) { + printf("%s %s %s\n", field, rest1, rest2); + } + + // check if there is a valid file descriptor + if (output_file >= 0) { + // Write to the file + write(output_file, field, strlen(field)); + write(output_file, " ", 1); + write(output_file, rest1, strlen(rest1)); + write(output_file, " ", 1); + write(output_file, rest2, strlen(rest2)); + write(output_file, "\n", 1); + } +} + +/** + * Compares lines from both files and looks for matches in the + * first field + * If the fields match then print joined line +*/ +void join_files(char file1_lines[][256], int count1, char file2_lines[][256], int count2, const char* output_file) { + int matches_found = 0; // count for amount of matches found + int output_fd = -1; // value of file descriptor of output file (-1 no valid file) + + // Open the output file for writing (create it if it doesn't exist) + if(output_file != 0) { + // changes the value of output_fd to indicate we have a valid file + output_fd = open(output_file, O_CREATE | O_WRONLY | O_TRUNC); // open file with flags (create new file if it does not exist | + // write access only | empty file if already exists) + if (output_fd < 0) { + printf("Error: Cannot open output file '%s'\n", output_file); + return; + } + } + + // iterate through each line from file 1 + for (int i = 0; i < count1; i++) { + char line1_copy[256]; // make copy to avoid messing with original + int k = 0; + + // string copy + while (k < 255 && file1_lines[i][k] != '\0') { + line1_copy[k] = file1_lines[i][k]; + k++; + } + line1_copy[k] = '\0'; + + char* field1 = get_first_field(line1_copy); // reads full line but only gets first word to check for match + // skip is field is empty + if(field1 == 0) { + continue; + } + + terminate_first_field(field1); // get rid of first word to make comparing easier + + // iterate through lines in file two + for (int j = 0; j < count2; j++) { + char line2_copy[256]; // make copy to avoid messing with original + int m = 0; + + // string copy + while (m < 255 && file2_lines[j][m] != '\0') { + line2_copy[m] = file2_lines[j][m]; + m++; + } + line2_copy[m] = '\0'; + + char* field2 = get_first_field(line2_copy); // read full line but only gets first word + // skip if field is empty + if(field2 == 0) { + continue; + } + terminate_first_field(field2); // get rid of first word to make comparing easier + + // check if both words are equal returns 0 if true + if(compare_strings(field1, field2) == 0) { + matches_found++; + + // get rest of lines for both files from where we got field + char* rest1 = get_rest_of_line(file1_lines[i]); + char* rest2 = get_rest_of_line(file2_lines[j]); + + // print using format + print_joined_line(output_fd, field1, rest1, rest2); + } + } + } + + // print message if no matches were found + if(matches_found == 0) { + printf("No matches found\n"); + } + + // Close the output file + close(output_fd); +} + +void print_usage() { + printf("Usage: join file1.txt file2.txt [outputfile.txt]\n"); + printf("\nDescription:\n"); + printf(" Joins two files based on matching first fields in each line.\n"); + printf(" Output format: \n"); + printf("\nArguments:\n"); + printf(" file1.txt First input file\n"); + printf(" file2.txt Second input file\n"); + printf(" outputfile.txt (Optional) Output file for results\n"); + printf("\nOptions:\n"); + printf(" --help Display this help message\n"); +} + +int main(int argc, char *argv[]) { + // Handle --help flag + if (argc == 2 && compare_strings(argv[1], "--help") == 0) { + print_usage(); + exit(0); + } + + char* output_file = 0; + + // Validate command line arguments + if (argc == 4) { + output_file = argv[3]; + } else if (argc != 3) { + print_usage(); + exit(1); + } + + // Read both files + int count1 = read_file_lines(argv[1], file1_lines, 50); + if(count1 <= 0) { + if(count1 == 0) { + printf("ERROR: failed to open %s\n", argv[1]); + } else if(count1 == -1) { + printf("ERROR: failed to retrieve metadata from %s\n", argv[1]); + } else if(count1 == -2) { + printf("ERROR: %s is empty\n", argv[1]); + } + print_usage(); + exit(1); + } + + int count2 = read_file_lines(argv[2], file2_lines, 50); + if(count2 <= 0) { + if(count2 == 0) { + printf("ERROR: failed to open %s\n", argv[2]); + } else if(count2 == -1) { + printf("ERROR: failed to retrieve metadata from %s\n", argv[2]); + } else if(count2 == -2) { + printf("ERROR: %s is empty\n", argv[2]); + } + print_usage(); + exit(1); + } + + // Join both files + join_files(file1_lines, count1, file2_lines, count2, output_file); + if(output_file != 0) { + printf("Output saved to %s\n", output_file); + } + + printf("join completed\n"); + + exit(0); +}