This lab will have you make a non-interactive command-line utility. This kind of program is an extremely common kind of thing to write in C. Many of the command-line utilities you’ve used so far (e.g. ls
, cd
, mv
) are like this.
This program will give you practice with using the C file API (fopen
, fread
, fwrite
, fseek
, ftell
, and fclose
) and help you learn about what is in binary files.
1. Getting started
- Right click and copy this link.
ssh
into your VM,cd
to your labs directory, and usewget
to download this file to your VM like you did on lab 1.- Run
unzip lab3.zip
on your VM, and it will unzip all the files for this lab. - Rename
abc123_lab3.c
to your username withmv
: e.g.mv abc123_lab3.c jfb42_lab3.c
- Connect to your VM in your SFTP client, navigate to your labs directory, and open your
_lab3.c
file for editing locally.
Now you’re ready!
Compiling and running
I’ve given you some code to get started. On your VM:
gcc -Wall -Werror --std=c99 -o lab3 abc123_lab3.c
If you run ./lab3
now, it will print out directions on how to use it. This is called usage info and most command-line programs will print something like this. If you look at the source code, main
is at the end of the file, and you can see that it prints this info out if the command-line arguments aren’t of the correct form.
What is this program?
This is a very simplistic “food database” program that can create, display, and add new food items to “food database” files. Something like a simplistic version of what a grocery store would use for stock keeping and pricing purposes.
A food database file contains a list of food items, each of which has a name, a department, a price, and a stock. This is a binary file, not a text file, so you cannot read/edit it in a text editor. That’s why this program exists!
Binary files
All the .db
files given to you are binary files. They are a custom format created for this assignment. The format is:
char magic[4] = "FOOD"; // notice no 0 terminator, just 4 characters
int endianness; // 1 for little, 0 for big
// then 0 or more food items, each of which is 32 bytes and consists of:
char name[16]; // includes 0 terminator, so never more than 15 characters
int dept; // one of the non-Invalid values of the Department enum
double price; // >= 0.01
unsigned int stock;
Other examples of magic numbers are .gif
files which start with GIF
and .png
files which start with the byte 0x89
, followed by PNG
. Many, many more binary file formats are documented on this site.
The char magic[4]
is the magic number that identifies this as a food database file. Many binary file formats start with some kind of unique sequence of bytes that indicates to programs that yes, this really is a file of the right type. Because remember, the file extension doesn’t mean anything!
Run hexdump -C good.db
(that’s capital -C
, not lowercase -c
. case matters!). You’ll see some output that starts with:
00000000 46 4f 4f 44 01 00 00 00 42 72 65 61 64 00 00 00 |FOOD....Bread...|
00000010 00 00 00 00 00 00 00 00 01 00 00 00 d7 a3 70 3d |..............p=|
00000020 0a d7 ff 3f 32 00 00 00 44 6f 75 67 68 6e 75 74 |...?2...Doughnut|
00000030 00 00 00 00 00 00 00 00 01 00 00 00 ae 47 e1 7a |.............G.z|
The leftmost column is the file offset, shown in hexadecimal. Each row displays 16 bytes, so that’s why the offset is increasing by 16 (0x10
) on each row.
The middle columns are the actual bytes of the file, with each byte displayed as a 2-digit hex number.
The right column shows those same bytes interpreted as printable ASCII if possible, or as .
if not. For example, you can see the bytes 46 4f 4f 44
are interpreted as FOOD
. But right after that are the bytes 01 00 00 00
, which aren’t printable ASCII characters, so they’re shown as ....
.
There are bits of text mixed in here and there, but most of the file is non-text data that is unreadable to us. You can kind of puzzle out what the data is knowing the file format from above, but it’s not easy.
Your goal
Your program will let you create new empty .db
files, view the contents of .db
files in a couple ways, and add new food items to the ends of them.
Although simple, it will show you the basics of using binary files, parsing and validating command-line arguments, and giving good error messages.
I have already given you some code to check which command-line arguments were passed, and you have to write all the actual database manipulation.
Your program will be able to:
- Create new database files, by using
./lab3 new filename
. - Print all the foods in a database file, by using
./lab3 print filename
. - Print the foods in one department, by using
./lab3 printdept filename deptname
. - Add a new food, by using
./lab3 add filename deptname name price stock
. -
Give the user good errors when they give invalid inputs:
$ ./lab3 print ahsifahsoihagh could not open file. $ ./lab3 print bad_magic.db file doesn't appear to be a food database (bad magic). $ ./lab3 print bad_endian.db file doesn't appear to be a food database (bad endianness). $ ./lab3 print bad_length.db file doesn't appear to be a food database (bad length). $ ./lab3 printdept good.db nfasfpoansi invalid department name! $
The fatal()
function
There is a function given to you called fatal()
. It’s very simple: it takes a string, prints that string to stderr
(which by default looks the same as printing it to stdout
), then exits the program with an exit code of 1.
This is a quick-and-dirty way of “falling over and dying” which many Java programs implement by throwing an exception that escapes main
. It’s not the most graceful way of handling errors, but that’s the nice thing about writing command-line tools - no one expects your program to keep running if something goes wrong! So just exit! It’s fine!
A note on debugging when you have command-line arguments
If your program crashes, you may try running it in gdb
like this, and get a confusing error about “no such file or directory:”
$ gdb ./lab3 print bad_length.db
...
/afs/pitt.edu/home/a/b/abc123/private/cs0449/labs/print: No such file or directory.
(gdb)
That’s weird. And when you try to use the run
command, it just prints out the usage.
You have to run gdb
a different way to pass command-line arguments to your program:
$ gdb --args ./lab3 print bad_length.db
Now when you run
, it will pass those arguments to your program.
Your job
You will implement the code in lab3.c
. These are the functions you will be filling in:
new_database
open_database
read_food
print_all_foods
print_foods_in_department
add_food
Do not try to do everything first, and then try to compile and run. Follow the instructions below to see how to test your work at each step.
2. Implementing ./lab3 new filename
- Implement
new_database
by following the comments inside it. - Compile it like before:
gcc -Wall -Werror --std=c99 -o lab3 abc123_lab3.c
- Run it like
./lab3 new mine.db
. It should not appear to do anything, but… -
ls -l
should show thatmine.db
exists and has a size of 8 like:-rw-r--r-- 1 abc123 UNKNOWN1 8 Sep 14 15:40 mine.db
- (That
8
beforeSep
is the file size in bytes.)
- (That
-
hexdump -C mine.db
should show:00000000 46 4f 4f 44 01 00 00 00 |FOOD....| 00000008
- If you do
./lab3 new mine.db
again, it should say “could not create database file.”.- this prevents you from overwriting existing databases.
If the output isn’t exactly what is shown above, you need to fix something! It should be exactly 8 bytes long, and they should have the values shown above.
3. Implementing ./lab3 print filename
- implement
open_database
.- This is a long one! But it’s important that we do a lot of checking so that we are fairly confident that we really are looking at a food database file.
- implement
read_food
.- This is much shorter.
- implement
print_all_foods
.- This is really short!
When you run ./lab3 print good.db
it should output:
$ ./lab3 print good.db
[Bakery ] Bread ($1.99 each, 50 in stock)
[Bakery ] Doughnut ($0.99 each, 100 in stock)
[Bakery ] Bagel ($1.99 each, 47 in stock)
[Canned ] Soup ($2.99 each, 24 in stock)
[Canned ] Cat Food ($1.19 each, 80 in stock)
[Canned ] Dog Food ($2.29 each, 54 in stock)
[Frozen ] Ice Cream ($5.99 each, 108 in stock)
[Frozen ] Blueberries ($7.49 each, 19 in stock)
[Frozen ] Pizza Rolls ($4.99 each, 14 in stock)
[Produce] Apple ($0.99 each, 15985 in stock)
[Produce] Banana ($0.39 each, 7777 in stock)
[Produce] Lettuce ($1.49 each, 68 in stock)
$
with no segmentation faults at the end or anything.
If you try to run it on the other database files (or on a file that doesn’t exist), they should all fail with specific error messages:
$ ./lab3 print aojsfoaisfioahs
could not open file.
$ ./lab3 print bad_magic.db
file doesn't appear to be a food database (bad magic).
$ ./lab3 print bad_endian.db
file doesn't appear to be a food database (bad endianness).
$ ./lab3 print big_endian.db
big-endian databases are unsupported.
$ ./lab3 print bad_length.db
file doesn't appear to be a food database (bad length).
$
4. Implementing ./lab3 printdept filename deptname
Just need to implement print_foods_in_department
. It’s very similar to print_all_foods
.
Test it with the four departments (bakery, canned, frozen, produce) and some invalid ones too:
$ ./lab3 printdept good.db bakery
[Bakery ] Bread ($1.99 each, 50 in stock)
[Bakery ] Doughnut ($0.99 each, 100 in stock)
[Bakery ] Bagel ($1.99 each, 47 in stock)
$ ./lab3 printdept good.db CANNED
[Canned ] Soup ($2.99 each, 24 in stock)
[Canned ] Cat Food ($1.19 each, 80 in stock)
[Canned ] Dog Food ($2.29 each, 54 in stock)
$ ./lab3 printdept good.db cats
invalid department name!
$
5. Implementing ./lab3 add filename deptname name price stock
add_food
has a lot of correctness checks you need to do. Notes:
- you can
sscanf
directly into a struct variable’s fields. sscanf
returns how many “things” it was able to parse. if it returns 0, it failed.- like if you tell it to parse an int out of “ajfoajsfojasf”
- you have to cast
new_food.stock
to an int to see if it’s less than 0, because it’s unsigned.- casting looks just like in Java.
(int)whatever
- casting looks just like in Java.
- be careful about the maximum length of
name
.new_food.name
is 16char
s long. so how long of a string can you actually put in it? - we have to copy
name
intonew_food.name
becausenew_food.name
is the right number of characters to be written out to the file, andname
is not.- I told you
strcpy
was bad and evil. Well, it is, but you’re checking the length before using it, so it’ll be safe.
- I told you
Now you should be able to add foods to any database and see them when you use ./lab3 print
on it. Importantly, adding a food should not corrupt the database:
$ ./lab3 print mine.db
$ ./lab3 add mine.db bakery "Apple Pie" 4.99 12
$ ./lab3 print mine.db
[Bakery ] Apple Pie ($4.99 each, 12 in stock)
$ ./lab3 add mine.db frozen Peas 1.99 40
$ ./lab3 print mine.db
[Bakery ] Apple Pie ($4.99 each, 12 in stock)
[Frozen ] Peas ($1.99 each, 40 in stock)
$ ./lab3 add mine.db produce Kiwi 0.99 500
$ ./lab3 print mine.db
[Bakery ] Apple Pie ($4.99 each, 12 in stock)
[Frozen ] Peas ($1.99 each, 40 in stock)
[Produce] Kiwi ($0.99 each, 500 in stock)
$
(yes, you can use “double quoted strings” in bash too)
Be sure to test all the correctness checks, too.
$ ./lab3 add mine.db asjfojas fojas foja sofj
invalid department name!
$ ./lab3 add mine.db bakery fojas foja sofj
price is not formatted correctly!
$ ./lab3 add mine.db bakery fojas 0 sofj
invalid price!
$ ./lab3 add mine.db bakery fojas 10 sofj
stock is not formatted correctly!
$ ./lab3 add mine.db bakery fojas 10 -5
invalid stock!
$ ./lab3 add mine.db bakery 1234567890123456 10 100
item name too long!
$ ./lab3 add mine.db bakery "" 10 100
item name too short!
$
Submission
We will be compiling your program with these compiler options, so make sure your program compiles with them:
gcc -Wall -Werror --std=c99 -o lab3 abc123_lab3.c
Just like before:
- Download your
_lab3.c
file to your computer with your SFTP client. - Go to the canvas for this course.
- Click Gradescope on the left.
- Click on Lab 3.
- Drag your
_lab3.c
file into the Gradescope submission thingy. - Wait for the autograder to complete!
If the autograder says something is wrong with your submission, fix it on your VM, re-download it to your computer , and re-submit it as many times as needed to fix the problem.
Note that future assignments may have a limited number (or rate) of resubmissions, so do not rely on the autograder to be your only method of finding and fixing problems!