By the end of this lesson, cadets will be able to:
errno. (Outcomes 1, 3)There is no new Beej chapter for this lesson. It consolidates defensive habits you have practiced since Lesson 16, so the reading is focused and short:
errno, and input validation.inventory.h and main.c before class. Both appear in Section 4 so you can read them here.inventory.h and main.c (Section 4).
Come to class able to say, in one sentence each, what the loader functions promise and what main does with a failed line.
This lesson is a synthesis. Each piece below is something you have touched before; the goal today is to use them together and to apply them to code you did not write.
The Lesson 37 deck walks through reading existing code, the assert-vs-validate distinction, error reporting with errno, and input validation, then sets up the lab.
Most code you will work on already exists, and reading it is its own skill. Find the entry point and follow the data: where input enters, what transforms it, where it ends up. Read the contract (the header) before the implementation. Then do the thing that finds bugs: trace the inputs the author did not consider, such as a NULL pointer, an empty line, a value out of range, a missing field. Working code handles the cases its author imagined; the gaps are everything else.
This header is the spec for the lab. Read each prototype as a promise: what it takes, what it returns, and what it sets errno to on failure. You will match these promises exactly when you harden the loader.
#ifndef INVENTORY_H
#define INVENTORY_H
/*
* inventory.h - shared contract for the marketplace inventory loader.
*
* PROVIDED FILE. Do not modify. The autograder and main.c depend on
* these declarations exactly as written.
*/
/* Longest item name we store, including the terminating '\0'. */
#define NAME_MAX_LEN 64
/* A quantity above this is treated as bad data, not real stock. */
#define MAX_QTY 10000
/* Capacity of the in-memory inventory array. */
#define CAPACITY 256
/* One parsed marketplace listing. */
struct listing {
char name[NAME_MAX_LEN];
int price; /* whole dollars; must be >= 0 */
int quantity; /* must be 0..MAX_QTY inclusive */
char condition; /* 'N' new, 'U' used, or 'R' refurbished */
};
/*
* load_listing - parse one CSV line "name,price,quantity,condition"
* into *out.
*
* @param line the raw input line (may be NULL)
* @param out destination listing (may be NULL)
* @return 0 on success.
* -1 on failure, with errno set:
* EFAULT if line or out is NULL,
* EINVAL if the line is malformed or any field is out of range.
*
* On failure *out is left unchanged.
*/
int load_listing(const char *line, struct listing *out);
/*
* add_listing - append item to inventory, tracking the running count.
*
* @param inventory the array, with room for CAPACITY entries
* @param count pointer to the current number of stored items
* @param item the listing to append (must be non-NULL)
* @return 0 on success (item stored, *count incremented).
* -1 if the inventory is full, with errno set to ENOSPC.
*
* It is a programmer error (not a runtime condition) for count to be
* NULL or *count to be negative; those are guarded with assert.
*/
int add_listing(struct listing inventory[], int *count,
const struct listing *item);
#endif /* INVENTORY_H */
This is the code that calls your functions. You cannot harden load_listing well without understanding how it is used. Notice how main reads one line at a time, what it does when a line is rejected, and which stream the error message goes to.
/*
* main.c - driver for the marketplace inventory loader.
*
* PROVIDED FILE. Do not modify. Read it carefully: this is the code that
* calls your functions, and you cannot harden load_listing well without
* understanding how it is used.
*
* Usage:
* ./inventory < feed.txt
*
* Reads one listing per line from standard input. Valid listings are
* stored; invalid lines are reported to stderr (with errno explained)
* and skipped. At the end it prints the accepted inventory.
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "inventory.h"
#define LINE_BUF 256
static const char *condition_word(char c)
{
switch (c) {
case 'N': return "new";
case 'U': return "used";
case 'R': return "refurbished";
default: return "unknown";
}
}
int main(void)
{
struct listing inventory[CAPACITY];
int count = 0;
char line[LINE_BUF];
int line_no = 0;
while (fgets(line, sizeof line, stdin) != NULL) {
line_no++;
/* Strip a trailing newline so condition is the last real char. */
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
}
/* Skip blank lines silently. */
if (line[0] == '\0') {
continue;
}
struct listing item;
errno = 0;
if (load_listing(line, &item) != 0) {
/* perror turns the current errno into a readable reason and
writes it to stderr, not stdout. */
fprintf(stderr, "line %d: rejected: ", line_no);
perror(line);
continue;
}
if (add_listing(inventory, &count, &item) != 0) {
fprintf(stderr, "line %d: not stored: ", line_no);
perror("inventory");
continue;
}
}
printf("Accepted %d listing(s):\n", count);
for (int i = 0; i < count; i++) {
printf(" %-20s $%d qty %d (%s)\n",
inventory[i].name,
inventory[i].price,
inventory[i].quantity,
condition_word(inventory[i].condition));
}
return 0;
}
assert(condition); from <assert.h> aborts with a file-and-line message if the condition is false. Use it for invariants, conditions that can only be false if your own logic has a bug: an index your code computed being in bounds, a count never going negative. The catch is that -DNDEBUG compiles every assertion out of release builds, so an assertion is a development-time check, never a runtime guarantee. That is the reason you never validate untrusted input with an assertion.
When a function cannot do its job, it should fail in a way the caller can detect and explain. The common C pattern is to return a status (often 0 success, -1 failure) and set errno from <errno.h> to say why: EINVAL for invalid data, EFAULT for a bad address such as NULL, ENOSPC when there is no room. The caller turns that into text with perror or strerror(errno) and writes it to stderr, so error messages never get mixed into the program's real output.
Validation is the runtime handling of data you cannot trust, and the order rarely changes: guard pointers before dereferencing; check return values (sscanf returns how many fields it filled); range-check every field on both ends; allow-list enumerated values and reject everything else; and commit to the output only after every check passes, so a rejected input never leaves a half-filled result. The whole lesson reduces to one rule: assertions for your bugs, validation for bad input.
Keep this open while you code. It is cross-cutting: you will see it referenced from earlier defensive-programming touchpoints, and it lives here at its home lesson.
#include <assert.h>assert(condition); aborts the program with a file and line message if condition is false.-DNDEBUG. Release builds delete every assert. Never put anything you always need to happen inside an assert.assert(count >= 0); /* my bug if false -> assert */ assert(index < CAPACITY); /* my bug if false -> assert */
#include <errno.h>0 for success, -1 for failure).errno is a shared global the standard library sets on failure; your code can set it too. Pair it with your return so the caller learns that it failed and why.EINVAL (invalid argument/data), EFAULT (bad address / NULL), ENOSPC (no space left).perror("context") or strerror(errno). Send error text to stderr, not stdout.errno = 0; before a call when you intend to inspect it afterward.if (bad_data) { errno = EINVAL; return -1; }
...
if (load_listing(line, &item) != 0)
perror("load_listing"); /* writes reason to stderr */
NULL before dereferencing anything.scanf/sscanf return how many fields were assigned; anything short means the rest are garbage.= where == was meant, ignored return values, and missing bounds checks.Three things to do in class. Use the checklist to track your progress.
Thirteen tasks across five sections. Read the fragile loader, then type each guard at the prompt. The right pane shows the loader's behavior on a fixed test feed and the guards you have installed; both update as you go.
Enter your name. It will appear on the completion screen so you can include it in your screenshot for the Lesson 37 reflection.
Seven questions. Pick or type your answer and submit; answers lock once submitted. Pass at 70% or above.
Enter your name. It will appear on the score card so you can include it in your screenshot for the Blackboard reflection.