Learning to read(2)
Our previous assignment focused on file I/O, and this one will go deeper by combining what we know about C strings with file I/O.
Imagine you have a file that looks something like this (cat /proc/meminfo | head -n 5
):
MemTotal: 1964472 kB
MemFree: 274432 kB
MemAvailable: 1578044 kB
Buffers: 60028 kB
Cached: 1288156 kB
…And you want to extract specific information from it. For example, maybe you want to read the MemAvailable
field so you know how much memory is currently available on your machine.
To do this, you’d need to:
- Open the file,
- Read line by line,
- Look for the specific key we’re interested in,
MemAvailable
. - When we find the key, extract its value, separated by delimiter(s). In this case, whitespace and
:
are the delimiters. - Copy the value back to a user-supplied buffer (now filled with
1578044 kB
) and return a value to indicate success. - Rewind the file so that the next call to our function will consider all possible keys again.
That’s actually exactly what you’ll do in this lab. But the situation gets more interesting, because you have a few restrictions to deal with:
- Instead of using the
f-
functions from the previous lab (such asfgets
), you’ll use the lower-levelopen(2)
andread(2)
system calls. Check out theman
pages for a refresher on how they work. - You’ll implement your own version of
fgets
usingread(2)
- If you research how to handle the delimiters, you may find some options in the C standard library. However, you’re not allowed to use them in this assignment, so you’ll need to implement delimiter handling yourself.
Finally, to tie this all together, you need a way to test your new functions. To do this, create a command line utility called lookup
that will search for a key in a given file and report its value(s). For example:
$ ./lookup MemAvailable ': ' /proc/meminfo
1578044 kB
$ ./lookup Buffers ': ' /proc/meminfo
60028 kB
$ ./lookup buffers ': ' /proc/meminfo
$ # (nothing prints, since the search is case-sensitive)
We’re passing in the key, delimiters (in quotes so spaces are included), and a list of file(s) to search.
Implementation Details
You can name your function however you’d like and the implementation details are up to you. Here’s one example:
int lookup_key(int fd, char *key, char* buf, size_t buf_sz);
Where the ‘buf’ contains the value that was found (if any), and the return value is used to report errors. Be sure to fully document your function so it’s easy to understand during code review.
For your implementation of fgets
, you have the same level of freedom, but since we are working with lower-level functionality you’ll need to accept a file descriptor (fd) as one of the arguments.
Hint 1: at a very basic level, fgets
is reading character by character until it finds \n
or fills its buffer. Make sure the buffer is always null-terminated! This means that if a buffer has a 100-character capacity, only 99 characters maximum can be used for storing the line.
Hint 2: your lookup_key
implementation should be able to handle situations where the buffer fills up before a newline is found. You can test this by inspecting the second-to-last character in the buffer; if it’s not a newline, then you haven’t reached the end of the line yet. If that is the case, read until you reach a newline character so that the rest of the line isn’t considered a key during the next call to lookup_key
, and ignore any excess characters beyond the buffer limit.
Testing
Craft at least 3 test cases to evaluate your implementation of the lookup
command line utility. Store them in a shell script called test.sh
. Here’s an example:
#!/usr/bin/env bash
./lookup badkey ': ' /proc/meminfo # No lines match
./lookup MemAvailable 'baddelim' /proc/meminfo # Won't remove ': ' before key
./lookup MemAvailable ': ' /proc/meminfo # Should work and find the value for MemAvailable
Add your 3 test cases to the script and provide a file called expected-output.txt
to show what the output should look like.
Grading and Submission
Check your code into lab2
within your lab repo. Provide a Makefile
that builds the program (please use the -Wall
option), and also include clean
and install
recipes (install to /usr/local/bin
). Additionally, you must have a new memcheck
recipe in your Makefile that uses valgrind to find memory and file descriptor errors. Here’s an example:
memcheck: lookup
valgrind \
--track-fds=yes \
--track-origins=yes \
--leak-check=full \
--trace-children=yes ./test.sh
To receive 70% credit:
- Implement your own
fgets
usingread(2)
. At a minimum, it should accept a file descriptor, buffer, and buffer size as its arguments.
To receive 90% credit:
- Complete all previous requirements
- Implement your key lookup function
- Implement the
lookup
command line utility
To receive 100% credit:
- Complete all previous requirements
- Write 3 test cases and provide their expected output
- Supply a Makefile that includes a
memcheck
operation