100 lines
4.7 KiB
Markdown
100 lines
4.7 KiB
Markdown
# Record Manager — Instructor Reference Solution (M1–M5)
|
||
|
||
**PRIVATE — never ships to students.** This is the reference implementation for
|
||
the Record Manager cumulative project (`assignments/projects/`), milestone by
|
||
milestone, plus the graded-interface contract and grading notes.
|
||
|
||
## Layout
|
||
|
||
```
|
||
include/record.h M1 record_t + record_new/free + status_t
|
||
include/buf.h M2 dynamic string buffer (+ readline)
|
||
include/list.h M3 opaque list_t + append/find/remove (+ len/get/sort helpers)
|
||
include/store.h M4 store_save / store_load (text file, '|' delimiter)
|
||
src/record.c M1 deep-copy record lifecycle
|
||
src/buf.c M2 growable buffer: append/appendc/cstr/reset/free/readline
|
||
src/list.c M3 singly linked list; remove frees node + record
|
||
src/store.c M4 one record per line; load appends + reports count
|
||
src/main.c M5 menu (add/list/search/delete/save/load/help/quit)
|
||
Makefile builds recman + recman_asan; `make test` runs unit + smoke
|
||
tests/unit.c direct tests of buf/list/store (links library sources)
|
||
tests/smoke.sh end-to-end CLI session
|
||
```
|
||
|
||
## Build + verify
|
||
|
||
```bash
|
||
make # recman
|
||
make asan # recman_asan (ASan+UBSan)
|
||
make test # unit + smoke, both under ASan/UBSan
|
||
```
|
||
|
||
Reference must be `-std=c17 -Wall -Wextra -Wpedantic -Werror` clean and report
|
||
`0 bytes lost` under Valgrind.
|
||
|
||
## Graded interface (what hidden tests touch)
|
||
|
||
`record_new`, `record_free`, `list_new`, `list_free`, `list_append`, `list_find`,
|
||
`list_remove`, `store_save`, `store_load` — exact signatures in
|
||
`assignments/projects/README.md`. This reference adds three non-graded helpers
|
||
(`list_len`, `list_get`, `list_sort`) that the menu and store layer need; they
|
||
are extra, not substitutes.
|
||
|
||
### Contract decisions (documented for graders)
|
||
|
||
- **`list_remove`**: returns `0` on success, `STATUS_ERR_NOTFOUND` when no record
|
||
matches, `STATUS_ERR_ARG` on NULL args. Frees the matching record + node.
|
||
- **`store_load`**: returns the **number of records read** (≥ 0) on success;
|
||
negative `status_t` on error. "0 on success" from the interface table is
|
||
satisfied when the file is empty/blank. It **appends** into the existing list.
|
||
- **Delimiter `|`**: none of the three flavors' fields (email/phone, assignment/
|
||
score, category/amount) can contain `|`; `name` may contain spaces — the
|
||
delimiter is safe across all flavors.
|
||
- **Empty fields**: saved as empty strings; loaded back as empty strings
|
||
(never NULL from `store_load`).
|
||
|
||
## Milestone-by-milestone notes
|
||
|
||
### M2 — dynamic string buffer (week 7)
|
||
|
||
Hidden tests likely: appends beyond initial capacity, `buf_cstr` NUL-termination
|
||
after every append, reset-then-reuse, `buf_readline` on a long line (>256 chars),
|
||
EOF/empty-line behavior. Sticky-OOM flag keeps the buffer usable after a failed
|
||
realloc. **Common mistake:** forgetting the `+1` for the NUL in `buf_grow`, or
|
||
realloc-ing to a shrinking capacity.
|
||
|
||
### M3 — linked list (week 11)
|
||
|
||
Hidden tests likely: append to empty list, find across many nodes, remove head /
|
||
remove middle / remove missing, `list_free` of a 3-node list under Valgrind
|
||
(0 lost), NULL-arg safety. **Common mistakes:** freeing the record and then
|
||
walking `next` (order matters — save `next` first); `list_remove` freeing the
|
||
node but not the record (double-free later); storing `record_t *` by value
|
||
instead of pointer.
|
||
|
||
### M4 — text file save/load (week 13)
|
||
|
||
Hidden tests likely: save→load round-trip preserves order + all three fields;
|
||
load into a non-empty list appends; file with blank lines; missing file returns
|
||
error, not crash; fields containing spaces survive (the `|` delimiter).
|
||
**Common mistakes:** assuming `fgets` succeeds; writing with `fprintf` and never
|
||
checking `ferror`; not stripping `\n`; `strtok` mangles empty fields (use
|
||
`strchr` splitting instead — empty field1/field2 must survive).
|
||
|
||
### M5 — refactor + search/sort + hardening (week 15)
|
||
|
||
Hidden tests likely: multi-file build with one `make`; case-insensitive
|
||
substring `search` across **all three** fields; `sort` alphabetical by name;
|
||
ASan/UBSan-clean; Valgrind 0 lost. **Common mistakes:** search matching only
|
||
`name`; sort comparing `record_t*` pointers instead of `->name`; `-Werror`
|
||
violations from unused includes after the refactor.
|
||
|
||
## Grading sanity checks
|
||
|
||
1. `make` from a clean tree (no build/ artifacts committed).
|
||
2. `make asan && ./build/recman_asan` → add 3 records → delete middle → save →
|
||
quit → reload → `list` shows the surviving two in order.
|
||
3. `valgrind --leak-check=full ./build/recman` with the same session → `0 bytes
|
||
lost` (the menu leaks nothing).
|
||
4. Feed the `tests/` fixtures: long input line (buf), 10k-record list (list),
|
||
save/load with spaces and empties (store).
|