Starting from:

$35

Project #2: Data Deduplication

CSCI 4061: Introduction to Operating Systems

Project #2: Data Deduplication

Background Data deduplication is a specialized method of data compression aimed at eliminating duplicate copies of repeating data in storage. Instead of storing identical data instances multiple times, deduplication retains a single copy of the data and references to that original copy for subsequent identical data instances. It is widely utilized in backup systems to minimize storage of repetitive data, cloud storage platforms to optimize space when multiple users upload identical files, virtualization environments to reduce redundancy in virtual machine images, and email systems to store single instances of common content or attachments. This technique enhances storage efficiency and reduces data transfer requirements across various domains. In this project, you will leverage the process functions fork(), exec() and wait() along with file, directory, and pipe operations, to carry out data deduplication within a file system hierarchy. This will involve finding and removing duplicate files and replacing them with symbolic (soft) links. You will use directory syscalls opendir(), readdir() and closedir() for traversing directories, system-level file I/O read() and write() as well as pipe() for data transfer between processes, symlink() for creating symbolic links and dup2() for redirection. 2. Project Overview For this project, you'll construct a hierarchical process tree, where processes are either associated with files (leaf processes) or folders (non-leaf processes). Each process creates a set of pipes for communication with its children. Non-leaf processes pass directory entry paths to their children and collect filenames and file hashes from their children, while leaf processes compute file hash values and relay them, with file paths, to their parent processes. The root process then identifies and handles file duplicates, replacing them with symbolic links to the original. Additionally, the root process will read these symbolic links, redirect standard output to a new file, write the full path as well as the content of the symbolic link file to text file. Figure 1: Directory Structure 2.1 Process Tree Creation ● Types of Processes: ○ Leaf Process: Associated with a file. This process will not spawn any child processes. ○ Non-leaf Process: Associated with a folder. This process is responsible for spawning child processes for each item (folder or file) within the directory it represents. ○ Root Process:This process is responsible for spawning one child (a non-leaf process), aggregating all file paths and hashes, deleting duplicates, and creating symbolic links. ● Example: ○ As shown in Figure 1, given a root directory ./root_directories/root with three folders: ■ The root process creates one non-leaf process (P1) for the root folder. ■ The non-leaf process P1 browses the root folder which contains 3 subfolders. So 3 non-leaf processes are created as children of P1. ■ For sub_1 which contains Madrigal_1.txt file and subsub_1-1 folder: ● The process for sub_1 spawns: ○ A leaf process for Madrigal_1.txt . ○ Another non-leaf process for subsub_1-1 folder which further spawns a leaf process for FairSong_2..txt 2.2 Inter-process Communication Every process will establish a pipe to communicate with each child process. For example, if a non-leaf process creates three children, it will construct three distinct pipes. Here’s the workflow of the process tree creation and IPC: ● Root Process: ○ Parse the command line arguments ○ Create the first non-leaf process to start the traversing process. ● Non-leaf Process: ○ Create a pipe for each child process. ○ Pass the complete path of each item in the current folder to its associated child process as an argument. ○ Collect all children’s pipe messages, relaying the leaves’ filenames as well as file hashes for all the files within its subtree. Send this information to its parent process through the pipe shared with its parent. ● Leaf Process: ○ Compute the hash value of the file's content. ○ Send this hash value, along with the file's full path, to the parent process through the pipe. ● Workflow: ○ As shown in Figure 1, the workflow is as follows: ■ Root process forks the first non-leaf process (P1) and creates the first pipe between the root and P1. It passes its full path and pipe’s write-end as arguments to P1. ■ Each non-leaf process recursively creates non-leaf processes and leaf-processes according to the directory structure. For each pair of parent and child processes, one pipe should be created and the parent shall pass its full path and pipe’s write-end as arguments to the child. ■ Each leaf-process calculates the hash of the leaf file content and writes it to the write-end of the pipe shared with its parent. ■ The non-leaf process shall wait for all its children and aggregate ALL pipe messages through the read_ends of ALL pipes shared with its children. 2.3 Duplicate File Handling and Symbolic Links Using the hash values and file paths sent to the root process: ● The root process identifies unique files and pinpoints duplicates. ● It deletes the duplicate files, replacing them with symbolic links pointing to the remaining copy. ● Note: files are identified by the hashes of their contents. We numbered the files with the same content to help with debugging. For example, FairSong_1.txt and FairSong_2.txt should be detected as duplicate files. The file with a smaller index should be retained. Thus, FairSong_1.txt is the retained copy and FairSong_2.txt should be deleted and replaced with a symbolic link 2.4 Redirection The root process performs the following steps: ● Reads the content of symbolic links which is the full path of the file that the symbolic link points to. ● Redirects and writes this full path as well as the content of the symbolic link to a new file within the root directory. For example, the output for each symbolic link should be like ./root_directories/root1/sub_2/Madrigal_2.txt → ./root_directories/root1/sub_1/Madrigal_1.txt, where Madrigal_2.txt is the symbolic link while Madrigal_1.txt is the file that Madrigal_2.txt points to. 3. Coding Details 3.1 Creating a process tree The process tree starts with the root process. The root process will take the name of the target directory as input. Because the given directory is a non-leaf folder, the root process will create the first non-leaf process. It should first prepare the pipe for further communication, then fork() the non-leaf process and use the exec() function to execute the non-leaf program, passing the arguments. The read end of the pipe should be used by the parent process to aggregate the file hashes, and the write end of the pipe should be used by the child process to enable communication with the parent. Figure 2: Illustration of Process Communication 3.1.1 Non-leaf Process Each non-leaf process takes two arguments: the path to the target directory and the write end (file descriptor number) of the pipe. Each non-leaf process should iterate through its given directory. There are two cases during iteration: 1) The current entry is a directory -> Another non-leaf process should be created. 2) The current entry is a file -> A leaf process should be created. In both scenarios, like the root process, it should begin by setting up the pipe for subsequent communication, followed by forking the child process and employing the exec() function to execute a program (non-leaf or leaf depending on the type of child process), transmitting the arguments to the child process. 3.1.2 Leaf Process The leaf process is responsible for obtaining the file hash and communicating with its parent through the write end of the pipe. Each file is uniquely identified by its hash of contents. Similar to the PA1, we provide the function hash_data_block() to calculate the hash of file contents. In particular, the format of the content written to the write end of the pipe is filename|hash|. Please note that your intermediate submission is to implement the leaf process functionality. 3.2 Locating the duplicates After traversing through the given root directory, the root process should construct a long string that contains all file names and file hashes obtained through the process tree. This root process should have collected a long string that contains all leaf file names as well as their hashes. You should partition the string to map the file name and file hashes by using the helper function parse_hash() that was provided in util.c. Then, you can iterate through all hashes to locate the duplicate hashes and their file names according to the hash. Please note that all given file names end with numbers. You should always retain the file with the smallest file number. 3.3 Create symbolic links and delete duplicates Delete all the duplicate files and create a symbolic link for each of them pointing to the unique file. You should always retain the file with the smallest file number. 4. Compilation Instructions You can create all of the necessary executable files with Command Line $ make all If you want to see if your code works for the intermediate test cases, you can use Command Line $ make inter Running the program with various root directories can be accomplished with Command Line $ ./root_process root_directories/root1 Or you can use the make to run with a specific directory: Command Line $ make root1 Or you can use the make to run with all test cases: Command Line $ make final 5. Project Folder Structure Please strictly conform to the folder structure that is provided to you. Your conformance will be graded. Project structure Contents (initial/required contents [1] ) include/ .h header files (hash.h, sha256.h, utils.h) lib/ .o library files (hash.o, sha256.o, utils.o) src/ .c source files (root_process.c, noleaf_process.c, leaf_process.c) root_directories/ Root directories used for testing *output/ program results expected/ expected output Makefile file containing build information and used for testing/compiling README.md file containing info outlined in 8. Submission Details *root_process executable file created during compilation *leaf_process executable file created during compilation * nonleaf_process executable file created during compilation * These files should not be included in your submission The files hash.o, sha256.o and utils.o are precompiled for you and should not be modified/deleted. They were compiled on a Linux CSELabs machine and are platform-dependent, so you will have to run your code on a CSELabs machine or equivalent Linux computer 1 This content is required at minimum, but adding additional content is OK as long as it doesn’t break the existing code. 6. Testing The Makefile contains three tests (root1, root2, root3). After running “make all”, you can run these test cases like such: Command Line $ make root1 The hierarchy of the 3 root directories are as below: 7. Assumptions / Notes ● Root directory is not empty ● 1<= Total number of files <= 10 ● All processes have access to the root_directories/ folder ● You should be using fork(), wait(), and exec() to create a process tree ● If you are dynamically allocating memory, make sure to free it ● Always initialize your buffer ○ e.g. memset(buffer, 0, BUFSIZE); 8. Submission Details There will be two submission periods. The intermediate submission is due 1 week before the final submission deadline. The first submission is mainly intended to make sure you are on pace to finish the project on time. The final submission is due ~2 weeks after the project is released. 8.1 Intermediate Submission For the Intermediate submission, your task is to implement the leaf process functionality(leaf_process.c) and come up with a plan on how you are going to implement the rest of the project. One student from each group should upload a .zip file to Gradescope containing all of your project files. We’ll be primarily focusing on *.c and your README, which should contain the following information: ● Project group number ● Group member names and x500s ● The name of the CSELabs computer that you tested your code on ○ e.g. csel-kh1250-01.cselabs.umn.edu ● Any changes you made to the Makefile or existing files that would affect grading ● Plan outlining individual contributions for each member of your group ● Plan on how you are going to construct the pipes and inter-process communication. (high-level pseudocode would be acceptable/preferred for this part) The member of the group who uploads the .zip file to Gradescope should add the other members to their group after submitting. Only one member in a group should upload. 8.2 Final Submission One student from each group should upload a .zip file to Gradescope containing all of the project files. The README should include the following details: ● Project group number ● Group member names and x500s ● The name of the CSELabs computer that you tested your code on ○ e.g. csel-kh1250-01.cselabs.umn.edu ● Members’ individual contributions ● Any changes you made to the Makefile or existing files that would affect grading ● Any assumptions that you made that weren’t outlined in 7. Assumptions / Notes ● How you designed your program for creating the process tree (again, high-level pseudocode would be acceptable/preferred for this part) ○ If your original design (intermediate submission) was different, explain how it changed ● Any code that you used AI helper tools to write with a clear justification and explanation of the code (Please see below for the AI tools acceptable use policy) The member of the group who uploads the .zip file to Gradescope should add the other members to their group after submitting. Only one member in a group should upload. Your project folder should include all of the folders that were in the original template. You can add additional files to those folders and edit the Makefile, but make sure everything still works. Before submitting your final project, run “make clean” to remove any existing output/ data and manually remove any erroneous files. 9. Reiteration of AI Tool Policy Artificial intelligence (AI) language models, such as ChatGPT, may be used in a limited manner for programming assignments with appropriate attribution and citation. For programming assignments, you may use AI tools to help with some basic helper function code (similar to library functions). You must not use these tools to design your solution, or to generate a significant part of your assignment code. You must design and implement the code yourself. You must clearly identify parts of the code that you used AI tools for, providing a justification for doing so. You must understand such code and be prepared to explain its functionality. Note that the goal of these assignments is to provide you with a deeper and hands-on understanding of the concepts you learn in the class, and not merely to produce "something that works". If you are in doubt as to whether you are using AI language models appropriately in this course, I encourage you to discuss your situation with me. Examples of citing AI language models are available at: libguides.umn.edu/chatgpt. You are responsible for fact checking statements and correctness of code composed by AI language models. For this assignment: The use of AI tools for generating code related to the primary concepts being applied in the assignment, such as process tree management, process operations, file I/O, directory operations, pipes and redirection, and links, is prohibited. 10. Rubric (tentative) ● [10%] README ● [10%] Intermediate submission ● [10%] Coding style: indentations, readability, comments where appropriate ● [50%] Test cases ● [10%] Correct use of fork(), pipe(), exec(), and write()/read() ● [10%] Error handling — should handle system call errors and terminate gracefully Additional notes: ● We will use the GCC version installed on the CSELabs machines to compile your code. Make sure your code compiles and runs on CSELabs. ● A list of CSELabs machines can be found at https://cse.umn.edu/cseit/classrooms-labs ○ Try to stick with the Keller Hall computers since those are what we’ll use to test your code ● Helpful GDB manual. From Huang: GDB Tutorial From Kauffman: Quick Guide to gdb