CS376: Operating Systems - File I/O (100 Points)

Assignment Goals

The goals of this assignment are:
  1. Create and modify files using the C standard I/O interface

Background Reading and References

Please refer to the following readings and examples offering templates to help get you started:

The Assignment

Core Concepts — why this matters. This lab reinforces the essential OS topic of file organization and record-oriented I/O. As you fwrite/fread fixed-size records and fseek to a record by number, you are exercising the file offset that the kernel tracks in struct file. To connect this user-space work to the kernel structures underneath, read Part 3 of the Kernel Data Structures reference.

The goal is to create a simple Flight Logbook System that allows users (pilots) to store and manage their flight records. This system should enable the addition, modification, viewing, and searching of flight logs. Each log entry will represent a flight, containing details such as date, aircraft type, departure and arrival locations, flight time, and any remarks.

Begin by creating a data structure that stores the following items:

  • Date
  • Aircraft Type
  • Departure
  • Arrival
  • Flight Time
  • Remarks

What to Do

  1. Allow users to add new flight logs by entering the details above. Create a data structure containing these entries, and append that data structure to the file.
  2. Allow the user to search for entries given a date (which you can compare as a string). Read each data structure from the file and compare the data to the input, printing the record data if it matches.
  3. Allow the user to print out a particular record number. Seek to that record number (don’t forget to multiply it by the size of each structure), and print the details at that entry.

Getting Started

Step 1: Define the Data Structure

First, we need to define a struct in C to represent each flight log entry. This struct will include fields for the date, aircraft type, departure and arrival locations, flight time, and any remarks. Since C does not have built-in string types, we’ll use character arrays for storing these details. The length of each array should be chosen to accommodate the expected maximum size of each field.

#define MAX_DATE_LEN 11
#define MAX_AIRCRAFT_TYPE_LEN 32
#define MAX_LOCATION_LEN 64
#define MAX_FLIGHT_TIME_LEN 6
#define MAX_REMARKS_LEN 256

typedef struct {
    char date[MAX_DATE_LEN];
    char aircraftType[MAX_AIRCRAFT_TYPE_LEN];
    char departure[MAX_LOCATION_LEN];
    char arrival[MAX_LOCATION_LEN];
    char flightTime[MAX_FLIGHT_TIME_LEN];
    char remarks[MAX_REMARKS_LEN];
} FlightLog;

Step 2: Implement Add, Search, and Print Functions

Adding New Flight Logs

To add a new flight log, we’ll need a function that accepts user input for each field, creates a FlightLog struct, and appends it to a file. It’s crucial to perform validation on user inputs to ensure data integrity.

void addFlightLog(const char* filename) {
    FlightLog log;

    printf("Enter date (DD/MM/YYYY): ");
    scanf("%10s", log.date);
    printf("Enter aircraft type: ");
    scanf("%31s", log.aircraftType);
    printf("Enter departure location: ");
    scanf("%63s", log.departure);
    printf("Enter arrival location: ");
    scanf("%63s", log.arrival);
    printf("Enter flight time (HH:MM): ");
    scanf("%5s", log.flightTime);
    printf("Enter remarks: ");
    scanf(" %255[^\n]", log.remarks); // The space before % is to consume any newline left in the input buffer

    FILE *file = fopen(filename, "ab"); // Open the file in append binary mode
    if (file != NULL) {
        fwrite(&log, sizeof(FlightLog), 1, file);
        fclose(file);
    } else {
        printf("Failed to open the file.\n");
    }
}

Searching Flight Logs by Date

To search for entries by date, iterate over each FlightLog entry in the file, comparing the input date with the date in the log.

void searchFlightLogsByDate(const char* filename, const char* targetDate) {
    FILE *file = fopen(filename, "rb");
    FlightLog log;

    if (file == NULL) {
        printf("Failed to open the file.\n");
        return;
    }

    while(fread(&log, sizeof(FlightLog), 1, file)) {
        if (strcmp(log.date, targetDate) == 0) {
            // Print the matching log details
            printf("Match found: %s, %s, %s -> %s, %s, %s\n", log.date, log.aircraftType, log.departure, log.arrival, log.flightTime, log.remarks);
        }
    }

    fclose(file);
}

Printing a Particular Record

To print a specific record by its number, calculate the byte offset in the file and use fseek to navigate to the correct position before reading the log entry.

void printFlightLog(const char* filename, int recordNumber) {
    FILE *file = fopen(filename, "rb");
    FlightLog log;

    if (file == NULL) {
        printf("Failed to open the file.\n");
        return;
    }

    fseek(file, (recordNumber - 1) * sizeof(FlightLog), SEEK_SET);
    if (fread(&log, sizeof(FlightLog), 1, file)) {
        // Print the log details
        printf("Record %d: %s, %s, %s -> %s, %s, %s\n", recordNumber, log.date, log.aircraftType, log.departure, log.arrival, log.flightTime, log.remarks);
    } else {
        printf("Failed to read the record.\n");
    }

    fclose(file);
}

Making a Deep Copy

When you write the contents of a struct, be careful if that struct contains char* data or other pointers. write will simply write the address, and not the data! This is called a shallow copy. To perform a deep copy, you should write the actual fields of the struct one-by-one, and read them in the same way. So, you’d have one read or write for each field of the struct.

In the provided code snippets, a deep copy issue is inherently avoided by using fixed-size character arrays instead of pointer-based strings (char*). This design choice ensures that when instances of FlightLog are written to or read from a file, the actual content of these arrays is directly handled, thus preserving the integrity of the data without the risks associated with shallow copies.

Using the CSAPP Reliable I/O Functions

To deal with short counts, you can use the built-in loops of rio_readn and rio_writen in the csapp.c and csapp.h file. The rio_readn and rio_writen functions are part of the Robust I/O (RIO) package provided by the “Computer Systems: A Programmer’s Perspective” (CS:APP) textbook. These functions are designed to handle the complexities and potential partial read/write issues associated with Unix I/O operations known as short counts. They provide a more reliable way to perform reading and writing operations on files, network sockets, and other I/O channels. They are designed to mirror the standard read and write functions, and work as follows:

  • rio_readn: This function attempts to read n bytes from a file descriptor into a user buffer. It deals with the partial read problem by continuing to read until all n bytes have been read or an end-of-file (EOF) is reached.
  • rio_writen: Conversely, this function writes n bytes from a user buffer to a file descriptor. It addresses the partial write issue by continuing to write until all n bytes have been written.

Using rio_readn

#include "rio.h"

int main() {
    int fd = open("myfile.txt", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    ssize_t n;
    char buf[1024];
    n = rio_readn(fd, buf, sizeof(buf));
    if (n < 0) {
        perror("read");
        return 1;
    }

    printf("Read %zd bytes\n", n);
    close(fd);
    return 0;
}

Using rio_writen

#include "rio.h"

int main() {
    int fd = open("myfile.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    const char* buf = "Hello, world!";
    ssize_t n = rio_writen(fd, (void *)buf, strlen(buf));
    if (n != strlen(buf)) {
        perror("write");
        return 1;
    }

    printf("Wrote %zd bytes\n", n);
    close(fd);
    return 0;
}

Compiling the Program

Save the csapp.c and csapp.h files into your project directory and compile them with the following command (assuming your program is called myprogram.c and consists of only that single file otherwise):

gcc -lpthread -o myprogram myprogram.c csapp.c

On some systems, the correct parameter is -pthread instead of -lpthread.

Makefile

Be sure to include a Makefile with your submission that builds and tests your program. If you prefer, you can include a shell script of test cases that you execute via the Makefile.

Makefile Requirements

Your submission must include a Makefile supporting the following standard targets:

Target What it must do
make Compile your program (with csapp.c if used) with no errors.
make run Build if needed and run your flight-log program interactively.
make test Build if needed and run your test cases (e.g., add, search, and print records), printing observable pass/fail output.
make clean Remove all executables, object files, generated log files, and core dumps.

Submission

In your submission, please include answers to any questions asked on the assignment page, as well as the questions listed below, in your README file. If you wrote code as part of this assignment, please describe your design, approach, and implementation in a separate document prepared using a word processor or typesetting program such as LaTeX. This document should include specific instructions on how to build and run your code, and a description of each code module or function that you created suitable for re-use by a colleague. In your README, please include answers to the following questions:
  • Describe what you did, how you did it, what challenges you encountered, and how you solved them.
  • Please answer any questions found throughout the narrative of this assignment.
  • If collaboration with a buddy was permitted, did you work with a buddy on this assignment? If so, who? If not, do you certify that this submission represents your own original work?
  • Please identify any and all portions of your submission that were not originally written by you (for example, code originally written by your buddy, or anything taken or adapted from a non-classroom resource). It is always OK to use your textbook and instructor notes; however, you are certifying that any portions not designated as coming from an outside person or source are your own original work.
  • Approximately how many hours it took you to finish this assignment (I will not judge you for this at all...I am simply using it to gauge if the assignments are too easy or hard)?
  • Your overall impression of the assignment. Did you love it, hate it, or were you neutral? One word answers are fine, but if you have any suggestions for the future let me know.
  • Using the grading specifications on this page, discuss briefly the grade you would give yourself and why. Discuss each item in the grading specification.
  • Any other concerns that you have. For instance, if you have a bug that you were unable to solve but you made progress, write that here. The more you articulate the problem the more partial credit you will receive (it is fine to leave this blank).

Assignment Rubric

Description Pre-Emerging (< 50%) Beginning (50%) Progressing (85%) Proficient (100%)
Program Correctness (70%) Code does not compile or execute, or produces critical errors. Code compiles and executes but contains significant logic errors, leading to incorrect results. Code compiles, executes, and produces correct results in most cases but may have minor issues. Code is correct, produces accurate results, and handles edge cases effectively.
Makefile and Compilation (15%) No Makefile provided or compilation issues. Basic Makefile provided but with errors or missing targets. Functional Makefile, minor issues with targets or dependencies. Well-structured Makefile, enabling seamless compilation without errors.
Documentation (README) (15%) No README or severely lacking in information. Basic README provided but lacking essential information or clarity. Good README, covers most essential aspects with minor omissions or clarity issues. Comprehensive, clear, and detailed README, providing complete instructions on program usage.

Please refer to the Style Guide for code quality examples and guidelines.