Skip to content

Commit 8485eb0

Browse files
committed
Add AGENTS.md with build instructions and invariants
1 parent ca2b53e commit 8485eb0

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# AGENTS.md
2+
3+
fand is a small Linux fan-control daemon written in C. It polls hwmon temperature
4+
sensors from `/sys`, maps them to PWM duty cycles via piecewise-linear curves, and
5+
writes the results to hwmon PWM devices. Config is YAML; the only external
6+
dependency is libyaml.
7+
8+
## Build & test
9+
10+
```sh
11+
make # builds ./main (installed as fand)
12+
make test # builds ./test_runner and runs it; all assertions must pass
13+
make install # installs binary + systemd unit (run as root)
14+
```
15+
16+
- Toolchain: `gcc -Wall -Wpedantic -g`, links `-lyaml -lm` (order matters: libs
17+
after sources). Requires C11 (uses `__VA_OPT__`).
18+
- CI (`.github/workflows/test.yml`) runs exactly `make test` on every push/PR —
19+
keep it green. Tests run unprivileged and must never touch real hwmon
20+
devices (they use in-memory objects and temp files under `/tmp`).
21+
- Run `make test` before committing anything C.
22+
23+
## Code layout
24+
25+
| File | Responsibility |
26+
|---|---|
27+
| `daemon.c` | `main()`, signal handling, main loop, config validation |
28+
| `config.c` | libyaml event-based parser; builds the full object tree |
29+
| `hwmon.c` | `hwmon_resolve_path()` — stable paths (`.../hwmon`), glob expansion |
30+
| `sensor.c` | `tempN_input` reader (millidegrees ÷ 1000 + offset; NAN on error) |
31+
| `curve.c` | Piecewise-linear curve, clamps 0–255 |
32+
| `fan.c` | `pwmN` writer with hysteresis, `pwmN_enable` manual/auto switching |
33+
| `zone.c` | Zone = sensors + fans; max sensor value drives all zone fans |
34+
| `common.h` | Constants (`MAX_ZONES`, `MAX_ZONE_SIZE`, `MAX_PATH`), version, log macros |
35+
| `test.c` | Hand-rolled test harness (ASSERT/ASSERT_EQ), no framework |
36+
37+
## Invariants (do not break)
38+
39+
- `pwmN_enable` semantics: `1` = manual control (fand owns the fan), `5` =
40+
hardware auto. `fand_config_enable()` sets 1 at start, `fand_config_disable()`
41+
must restore 5 on every exit path (shutdown, SIGHUP reload, fatal config
42+
error) so fans are never left in manual control by a dead daemon.
43+
- Curve inputs must be strictly increasing; `curve_create()` enforces this and
44+
returns NULL. Keep that validation if you touch `curve.c`.
45+
- Zones with zero fans or zero sensors are silently skipped by the parser.
46+
- Config parse failures must degrade gracefully: skip the invalid object with a
47+
`DBG()` message, don't crash. `fand_config_load()` returns NULL only when the
48+
file is unreadable/unparseable or no zones survive.
49+
- `hwmon_resolve_path()` must keep working for: explicit `hwmonN` paths
50+
(backward compat), paths ending in `/hwmon` (scan for first `hwmonN` child),
51+
and glob patterns. Callers own the returned allocation.
52+
- All sensor values are thousandths of a degree (e.g. 52300 = 52.3°C).
53+
54+
## Conventions
55+
56+
- C11, 4-space indent, `snake_case` functions, `static` for file-local
57+
functions, `struct`-per-subsystem headers.
58+
- Diagnostics go through `DBG()` (stderr). There is no log-level system;
59+
`DEBUG 1` in `common.h` is hardcoded.
60+
- No new runtime dependencies. Keep it libyaml + libc + libm.
61+
- Tests: `static void test_xxx(void)` in `test.c` using `ASSERT`/`ASSERT_EQ`,
62+
registered at the end of `main()`. Follow the existing pattern — no
63+
frameworks.
64+
- Bump `FAND_VERSION` in `common.h` and `pkgver` in `PKGBUILD` together when
65+
releasing (they have drifted: 0.1.1 vs 1.3).
66+
67+
## Runtime facts
68+
69+
- Default config path is `fand.conf` in the CWD; pass the path as argv[1].
70+
- Signals: `SIGTERM`/`SIGINT` = graceful shutdown (restores hardware control);
71+
`SIGHUP` = reload config (bad new config → keep the old one running).
72+
- Main loop polls every `poll_interval` seconds (default 1); per-fan
73+
`hysteresis` skips PWM writes within the threshold to reduce sysfs churn.
74+
- systemd unit `fand.service` expects `/usr/local/bin/fand /etc/fand.conf`.

0 commit comments

Comments
 (0)