Advanced C Programming

Spring 2019 :: ECE 264 :: Purdue University

⚠ This is for Spring 2019, not the current semester.
Due 3/29

Backward file reader

Learning goals

You will learn or practice how to:

  1. Program with files.
  2. Program with structs to encapsulate data.
  3. Buffer data read from a file to improve program performance.
  4. Apply test-driven development (TDD).

Overview

You will write code to read files in backwards. You will need to design your own struct to fullfill the requirements of this homework. See the Requirements table below for more detail on what you will create.

Background information

A common use of a backwards file reader is to display recent events from the log file of a website. These files can be very large and difficult to inspect by hand, but reading the file backwards to forwards allows for more easy processing of logs. In this assignment you will implement such a reader to read files backwards.

Generally, file I/O is a very expensive operation in any program and can be the bottle neck which prevents better performance. In this assignment, you will help to alleviate performance issues by buffering file I/O operations into a custom structure, and then use that structure to read in a file backwards and display it line by line.

For this assignment you will need to use several new functions for interacting with files. A complete description of these functions can be found by typing man function_name but here is a brief description of each:

  1. FILE* fopen(const char* filename, const char* mode) - This function opens the file at filename with mode mode and returns a FILE struct which you can use to interact with the file. The modes you may need for this assignment are as follows:
    1. w - used to open a file for writing, this mode deletes a file's contents if it has any or creates a new file
    2. r - used to open a file for reading
  2. int fclose(FILE* stream) - This function is used to close a file you have opened with fopen so that all resources relating to the file are freed. You must have exactly one call to fclose for each call to fopen.
  3. int fseek(FILE* stream, long offset, int whence) - This function moves where in the file you are currently reading from/writing to. Stream is the FILE structure you obtained from fopen, offset is the distance to move (can be positive, negative, or zero) and whence is a constant describing where you should move relative to. Whence can be one of three constant values:
    1. SEEK_SET - seek to an absolute position from the beginning of the file
    2. SEEK_CUR - seek from the current location in the file
    3. SEEK_END - seek from the end of the file
  4. long ftell(FILE* stream) - This function returns your current position in the file measured from the start of the file.
  5. size_t fread(void* dest, size_t size, size_t count, FILE* stream) - This function reads in count elements of size size from stream and copies them into the buffer provided to dest. For example if you wanted to read in 10 ints from a file you might do the following: FILE* fp = fopen("ints_data.txt", "r"); int *array = malloc(sizeof(*array) * 10); fread(array, sizeof(*array), 10, fp); fclose(fp);

Warm-up exercise

This assignment includes a warm-up exercise to help you get ready. This accounts for 10% of your score for HW10. Scoring will be relatively light, but the usual base requirements apply.

The structure of the warmup.c file is described in the Requirements table below. You should write your own warmup.c.

Opt out.

In a hurry, and don't need the practice? This warm-up is here to help you learn what you need to succeed on the rest of this assignment—not to add additional work. Therefore, we give you an option. Those who feel that they do not need the practice may "opt out". by modifying warmup.c so that it does nothing but print the following message exactly and then exit:

I already know this and do not need to practice.

If you do that, then your score for HW10 will be based solely on the rest of this assignment. If you leave the warmup.c undone, if you do not turn in a warmup.c, or if the message it prints does not match perfectly, then you will receive 0 for the warmup portion of this assignment (10%).

Doing the assignment

Use 264get HW10 to fetch the starter code.

Use test-driven development to complete this assignment incrementally.

Requirements

  1. Your submission must contain each of the following files, as specified:
    file contents
    backwards_file.h types FileWrapper
    A struct to hold all information you will need in order to read a file backwards and buffer a specific amount.
    • You may include whatever fields you think are necessary to complete this assignment.
    • Hint: You will want to store a file pointer to the open file.
    You may add additional types and/or constants to backwards_file.h, if you wish.
    backwards_file.c functions
    create file wrapper(char✶ filename, char✶✶ a error)
    return type: FileWrapper ✶
    Open the file and create a FileWrapper with the information that will be needed by read_line(…).
    • OPTIONAL: Return NULL in case of an error (e.g., unable to open file).
    read line(FileWrapper✶ fw, char✶✶ a error)
    return type: unsigned char ✶
    Return the next line from the back of the file.
    • You will allocate the required memory for the string you read from the line.
    • You may assume the caller is responsibible for freeing the returned line.
    • Return NULL if you failed to open the file there was an error reading the file.
    • When a line ends with a '\n', include it in the string that is returned. For the last line in a file that doesn't end in '\n', return as is, without the '\n'.
    • The characters within each line should be in the same order they were in the file.
    • The behavior is similar in spirit to the tac command in bash. You might find it useful to try that command or read its man page.
    • OPTIONAL: After all lines of the file have been returned, any subsequent calls to read_line(…) should return NULL. To distinguish from file errors, set *a_error = NULL.
    free wrapper(FileWrapper✶ fw)
    return type: void
    Free all heap allocated memory assoicated with the FileWrapper and close the file pointer.
    test_backwards_file.c functions
    main(int argc, char✶ argv[])
    return type: int
    1. This is the same as in previous assignments.
    2. Tests must result in 100% code coverage.
    expected.txt test output
    1. This is the same as in previous assignments.
    2. If you are using miniunit.h, you may simply redirect the output of your working tests to expected.txt to create this.
    warmup.c functions
    print contents of file(char✶ filename)
    return type: void
    Read every byte in a file and print it to stdout.
    • This should not call malloc(…).
    print first n chars in file(char✶ filename, int n, char✶✶ a error)
    return type: void
    Read the first n bytes in a file and print them to stdout.
    • This should not call malloc(…).
    • If there are <n bytes in the file, then print the entire contents of the file.
    get first line of file(char✶ filename, char✶✶ a error)
    return type: char✶
    Read the first line of a file and return it as a string on the heap.
    • This should call malloc(…) once.
    • Caller is responsible for freeing the memory.
  2. For all functions that take a parameter called a_error, in case of any error with the file (e.g., file not found, unreadable, etc.), set *a_error to the error message string returned by strerror(errno).
    • OPTIONAL: Set *a_error = NULL if there was no file error.
  3. For the main part of this homework (excluding the warmup):
    • Do not read any byte more than once.
    • You must read the file in fixed-size chunks.
      • Pick a buffer size.
      • It could be a constant in your backwards_file.h (e.g., 128 bytes)
      • Alternatively, it may be dynamically determined in create_file_wrapper(…) (e.g., based on the file size).
      • Buffer size must not be 1 or the size of the file.
      • When calling fread(…), read exactly buffer_size bytes at a time.
      • Your last call to fread(…) (e.g., when reading the very beginning of the file) may read a smaller number of bytes.
      • Buffer size may not be <8 bytes or >1024 bytes.
    • Do not read one byte at a time (unless the file is just 1-2 bytes).
    • Do not read the entire file at once within a single call to create_file_wrapper(…) or read_line(…) unless the file is smaller than your buffer size.
  4. “line” means a sequence of bytes that begins at the beginning of a file or immediately after a '\n' and ends with a '\n'.
  5. Make no assumptions on the size of the file or the size of the buffer lines.
  6. Your structure may have any fields you wish as long as it is sufficient to complete the assignment.
  7. OPTIONAL: You may create an alias for unsigned char like this: typedef unsigned char uchar; and then substitute uchar in place of unsigned char in your code and the header file.
  8. Only the following external header files, functions, and symbols are allowed. You may use fputc(…) and stdout in either one (or both).
    header functions/symbols allowed in…
    stdbool.h bool, true, false backwards_file.c, backwards_file.h, test_backwards_file.c, warmup.c
    string.h memcpy, strcmp, strlen, strcpy, strchr, strrchr, memmove, strncat, strerror backwards_file.c, test_backwards_file.c, warmup.c
    errno.h errno backwards_file.c, test_backwards_file.c, warmup.c
    stdio.h FILE backwards_file.c, backwards_file.h, test_backwards_file.c, warmup.c
    stdio.h printf test_backwards_file.c, warmup.c
    stdio.h stdout, stderr test_backwards_file.c
    stdio.h fopen, fclose, fseek, ftell, fread, feof, ferror, SEEK_SET, SEEK_CUR, SEEK_END backwards_file.c, test_backwards_file.c, warmup.c
    stdio.h fgetc warmup.c
    stdlib.h malloc, free, EXIT_SUCCESS, EXIT_FAILURE backwards_file.c, test_backwards_file.c, warmup.c
    assert.h assert backwards_file.c, test_backwards_file.c, warmup.c
    All others are prohibited unless approved by the instructor. Do not use EOF. Feel free to ask if there is something you would like to use.
  9. OPTIONAL: You may add const to any parameter in the header file (or anything else).
  10. Submissions must meet the code quality standards and the course policies on homework and academic integrity.

Submit

To submit HW10, type 264submit HW10 backwards_file.c backwards_file.h test_backwards_file.c expected.txt warmup.c miniunit.h clog.h from inside your hw10 directory. If your code does not depend on miniunit.h or clog.h, those may be omitted. If your tests rely on text files, submit them. In general, you should submit everything needed to compile and your code and run your tests.

Pre-tester

The pre-tester for HW10 has been released and is ready to use.

How much work is this?

This assignment is an introduction to reading files and the concept of buffering. Although the actual code may not be difficult, there can be conceptual challenges when dealing with buffering data.

Q&A

Updates

3/21/2019 Fixed warmup functions
3/22/2019 Removed requirement about not modifying backwards_file.h; clarified: do not use EOF; added warmup.c to allows symbols table in requirements
3/23/2019 Removed the reference to diff testing. Miniunit is preferred (but still optional). Minor clarifications.
3/25/2019 Specification for buffer size was expanded; deadline extended
3/26/2019 Corrected table of allowed functions/symbols. There is no need to use printf(…) or stdout in backwards_file.c. Added ferror(…). Submit any text files your tests depend on.
3/28/2019 Added a few OPTIONAL suggestions to fill holes in the specification. Most came up in response to questions on Piazza or in class. These will not be tested. No bonus credit will be given.
  • OPTIONAL: After all lines of the file have been returned, any subsequent calls to read_line(…) should return NULL. To distinguish from bona fide errors, set *a_error = NULL;.
  • OPTIONAL: When there is no file error, set *a_error = NULL (in functions where that parameter exists).
  • OPTIONAL: You may create an alias for unsigned char like this: typedef unsigned char uchar;
  • OPTIONAL: You may add const to any parameter (or anywhere else), as long as it doesn't cause any problems.