A záróprojektben egyetlen, pontosan körülhatárolt feladatot
valósítunk meg: név=érték alakú, LF-fel lezárt rekordokból
álló bájtfolyamot ellenőrzünk, majd rövid, gépileg feldolgozható
összesítést készítünk róla. Ugyanazt a dg_*
parserkönyvtárat használja a fájlból vagy standard bemenetről olvasó
diag parancs és a TCP-kapcsolatokat kiszolgáló adapter. A
39. fejezet C++20 portja is ezt a szerződést viszi tovább, változtatás
nélkül.
A cél nem egy éles monitoringrendszer, hanem egy ellenőrizhető rendszerprogramozási szerződés: mely bájtok érvényesek, mennyi erőforrást használhatunk, ki birtokolja az állapotot, és mi történik részleges bemenet vagy leállítás esetén.
A bounds-ellenőrzött parser állapotátmeneteire, az eseményvezérelt I/O modelljére és az opaque könyvtári interfész elválasztására építünk. A könyvtári magot és a CLI-t a C könyvtári mag és CLI megvalósítási labor, a hálózati adaptert a Nonblocking TCP adapter és leállítás valósítja meg. Ez az alfejezet a kanonikus projektszerződés: a későbbi fejezetek kódja és tesztjei ehhez igazodnak, és ha eltérnek tőle, az hiba.
A rekordfolyam nulla vagy több, közvetlenül egymás után írt rekordból áll. Nincs köztük elválasztó, és nincs kötelező zárórekord. Az üres folyam érvényes. A bemenet végét a fájlvég jelzi.
A CLI használata:
diag [FILE|-]
Argumentum nélkül vagy - argumentummal a program a
standard bemenetet olvassa, egyébként a megadott fájlt. Kettőnél több
parancssori szó használati hiba.
A CLI nem keres új rekordhatárt sérült bemenet után. Az első hibánál megáll, mert az ismeretlen bájtok átugrása könnyen érvényesnek láttatna egy sérült folyamot. Részleges sikerjelentés nincs: hibás bemenetnél a standard kimenet üres marad.
Siker esetén a standard kimenetre pontosan egy sor kerül:
OK records=2 sum=19 bytes=11
| Mező | Szerződés |
|---|---|
records |
A teljes egészében ellenőrzött, érvényes rekordok száma |
sum |
Az érvényes rekordok értékeinek összege |
bytes |
A parser által elfogyasztott bemeneti oktettek száma |
A mezősorrend, az egyetlen ASCII szóköz elválasztó és a záró LF a szerződés része. A sor nem tartalmaz bemenetből átvett szöveget vagy útvonalat, ezért hossza korlátos, és nincs szükség escape-elésre.
Hiba esetén a diagnosztika a standard hibakimenetre kerül, így a gépi kimenetet nem szennyezi. Parserhibánál a sor a hibakódot és a hiba bájtoffsetjét tartalmazza:
ERR code=truncated offset=10
Más hibához nem tartozik értelmes bájtpozíció, ezért ott csak a kód
szerepel, például ERR code=open.
| Diagnosztikai kód | Forrás | Kilépési kód |
|---|---|---|
syntax, truncated, limit |
Parser: hibás, csonka vagy túl hosszú bemenet | 4 |
state |
Parser: API-használati hiba, például finish utáni
feed |
5 |
usage |
Túl sok argumentum | 2 |
open, read, close |
A bemenet megnyitása, olvasása vagy saját fájl lezárása | 3 |
alloc, setup |
Parserallokáció vagy a SIGPIPE kezelésének
beállítása |
5 |
write |
A jelentés kiírása vagy flush-a | 6 |
Sikeres futás kilépési kódja 0. A kilépési kódok a
program rendes vezérlési útjaira vonatkoznak, nem a folyamat külső,
kényszerített megszüntetésére. A hibakimenet írása best effort:
hibás stderr esetén üzenetet nem tudunk garantálni.
Projektszerződés: a bemenet 8 bites oktettekből áll.
A laborban CHAR_BIT == 8, ezért egy oktett egyben egy C
bájt. Ezt a könyvtár fordításkor ellenőrzi. A karakterek alább mindig a
megadott ASCII-kódú oktettet jelentik.
Egy rekord nyelvtana ABNF-hez hasonló jelöléssel:
rekord = név "=" érték LF
név = 1*16( %x61-7A ) ; a–z
érték = 1*5( %x30-39 ) ; 0–9, decimális értéke <= 65535
LF = %x0A
| Elem | Szerződés |
|---|---|
| Név | 1–16 ASCII kisbetű, kizárólag a–z |
| Elválasztó | Egyetlen = (0x3d) |
| Érték | 1–5 decimális számjegy, értéke legfeljebb 65535 |
| Vezető nullák | Megengedettek, de beleszámítanak az öt számjegybe |
| Rekordlezárás | Kötelező LF; CRLF nem elfogadott |
| Ismétlődő név | Megengedett; minden rekord külön számít |
| Teljes bemenet | Legfeljebb 1 048 576 oktett |
Minden más oktett a nyelvtan által nem engedett helyen szintaktikai
hiba: nagybetű, szóköz, \r, 0x00, üres név
vagy érték, 17. névbetű, 6. számjegy, illetve olyan számjegy, amellyel
az érték 65535 fölé nőne. A parser bájtokat vizsgál, nem C-sztringeket:
a beágyazott nullbájt nem rövidíti meg a bemenetet, hanem hiba.
A parser nem tárolja el a neveket: a projekt a rekordfolyam szerkezeti érvényességét és összesített számait vizsgálja, nem metrikanevekből épít adatbázist.
A a=7 és a cpu=12 rekord együtt,
hexadecimálisan:
61 3d 37 0a
63 70 75 3d 31 32 0a
Az első rekord 4, a második 7 oktett, összesen:
Az eredmény OK records=2 sum=19 bytes=11, mert
.
A bemenet tetszőleges darabolásban érkezhet: egy olvasás visszaadhat két
oktettet, egy későbbi a maradékot. A parser eredménye nem függhet ettől
a darabolástól.
Ha a második rekord záró LF-je hiányzik, a fájlvégkor:
ERR code=truncated offset=10, a kilépési kód
4;A TCP-adapter ugyanerre a nyelvtanra épül, de kérés–válasz protokollként működik. Egy kérés pontosan egy rekord, az első LF-ig bezárólag. A válasz az az egyetlen sor, amelyet a CLI erre az egy rekordból álló bemenetre a standard kimenetre (siker) vagy a standard hibakimenetre (parserhiba) írna. Az offset ezért a kérés első oktettjéhez viszonyított.
| Kérés | Válasz |
|---|---|
a=7\n |
OK records=1 sum=7 bytes=4\n |
cpu=12\n |
OK records=1 sum=12 bytes=7\n |
A=1\n |
ERR code=syntax offset=0\n |
| Parserallokációs hiba | ERR code=alloc\n |
Mivel az LF minden kérést egyértelműen lezár, egy szintaktikailag hibás kérés után a kapcsolat a következő kéréssel folytatódhat. Keretszintű hiba viszont kapcsolatbontást okoz válasz nélkül: ilyen a kapcsolat bemeneti tárolóját (4096 oktett) LF nélkül megtöltő adat, illetve a félbemaradt kérés a kliens küldési irányának lezárásakor.
A limitek a protokoll megvalósításának részei, nem pusztán üzemeltetési ajánlások. Mindegyiket egyetlen helyen, névvel ellátott konstans rögzíti; a számításokat ezekből vezetjük le.
| Erőforrás | Projektlimit | Konstans, forrás |
|---|---|---|
| Egy munkamenet teljes bemenete | 1 048 576 oktett | DG_MAX_INPUT, include/diag.h |
| Egy rekord, LF-fel | legfeljebb 23 oktett | a nyelvtanból: |
| Parser saját tárolója | egyetlen, rögzített méretű objektum | struct dg_parser |
| CLI olvasópuffere | 4096 C bájt | src/main.c |
| Aktív TCP-kapcsolatok | 64 | a szerver kapcsolattáblája |
| Kapcsolatonkénti bemeneti tároló | 4096 C bájt | DIAG_IN_CAP, include/diag_tcp.h |
| Kapcsolatonkénti kimeneti tároló | 1024 C bájt | DIAG_OUT_CAP, include/diag_tcp.h |
A parser inkrementálisan dolgozik, ezért egyik puffernek sem kell egy
teljes munkamenetet vagy rekordot befogadnia. A TCP bemeneti tároló a
leghosszabb érvényes kérésnél (23 oktett) jóval nagyobb, így pipelined
kérések is elférnek benne. A válasz legfeljebb 32 oktett
(OK records=1 sum=65535 bytes=23\n), tehát a kimeneti
tároló is elegendő.
A TCP-kiszolgáló (diag_server [PORT]) csak a
127.0.0.1 címen figyel; 0 vagy hiányzó port
esetén a kernel választ portot. Indulás után a standard kimenetre
egyetlen LISTEN 127.0.0.1:PORT sort ír. Kilépési kódja
rendes leállítás után 0, hibás argumentumnál
2, indítási vagy eseményciklus-hibánál 5. TLS,
hitelesítés és hálózati publikálás nem része ennek a projektnek.
Legyen a munkamenet méretlimitje, az eddig elfogyasztott oktettek száma. A parser minden új oktett előtt ellenőrzi:
Ha
,
az új oktettet nem fogyasztja el, és limit hibát ad
offsettel. Nem vezetünk be
alakú összeget, amely túlcsordulhatna: a számláló oktettenként,
egyesével nő, és az invariáns szerint sosem lépi át
-et.
Az érték túlcsordulását ugyanígy a művelet előtt zárjuk ki. Ha az eddigi érték és az új számjegy, akkor a érték akkor fér el, ha
A részletes levezetést és az összeg felső korlátját a megvalósítási labor adja.
Az ellenőrzési sorrend determinisztikus:
=,
számjegy vagy LF.finish) a félbemaradt rekord.A határ elérése önmagában nem hiba. Pontosan
oktettnyi érvényes bemenet után a fájlvég sikeres; egy további oktett
már limit hibát jelent, elfogyasztás nélkül.
A hibát kiváltó oktettet a parser nem fogyasztja el, ezért szintaktikai hibánál az offset az első elutasított oktett helye, csonkolásnál a bemenet vége.
A TCP-adapter monoton órán mért határidői:
| Határidő | Érték | Kezdőesemény |
|---|---|---|
| Tétlen, üres kapcsolat | 30 s | Az utolsó teljesen feldolgozott kérés vagy az elfogadás |
| Befejezetlen kérés | 5 s | A parseradapter első DIAG_MORE eredménye |
| Függő válasz | 5 s | A válasz sorba állítása |
| Teljes szerverleállítás | 3 s | A leállítás kezdete |
Egy-egy új oktett vagy részleges küldés nem indítja újra a határidőt. Ezek alkalmazásszintű határidők, nem valós idejű ütemezési garanciák: a lejáratot az eseményciklus észleli.
A kapcsolatok puszta adattömbjeinek felső korlátja:
Ez nem RSS-garancia. Nem tartalmazza a struktúratagokat, az allocator metaadatait, a libc belső állapotát, a végrehajtási vermet vagy a kernel socketpuffereit. Azokat külön kell mérni. Az aktívkapcsolat-limit a már elfogadott kapcsolatokra vonatkozik, nem a kernel várakozó kapcsolati sorára.
A projekt fájlszintű felosztása:
| Modul | Feladat | Hibakimenet |
|---|---|---|
include/diag.h, src/diag.c |
Opaque parserállapot: dg_create, dg_feed,
dg_finish, dg_get_report,
dg_get_error, dg_code_name,
dg_destroy |
Ragadós dg_error; létrehozási hiba:
NULL |
src/main.c |
CLI: argumentumok, bemeneti FILE, report formázása |
Kilépési kód és diagnosztika |
include/diag_tcp.h, src/tcp_io.c |
Kapcsolatonkénti nonblocking olvasás, írás, backpressure | enum diag_step |
src/tcp_reply.c |
Core-adapter a TCP-hez: egy kérés feldolgozása és a válasz formázása | enum diag_core_result |
src/diag_server.c |
Szerver-vezérlő: listener, epoll, határidők,
leállítás |
Kapcsolat lezárása; kilépési kód |
A kapcsolóelemek pontos jelentése:
dg_feed kölcsönzött
dg_view bájtszeletet kap. A parser a hívás után nem őrzi
meg a pointert, és teljes rekordot sem tárol: állapota a fázis, a
névhossz, a számjegyszám és a részérték.dg_report.bytes a sikeresen elfogyasztott oktettek száma;
hiba esetén ez egyben a hiba offsetje. A TCP-adapter a kérés hosszát
adja vissza consumed értékként.dg_finish
jelzi. A nulla hosszúságú feed nem fájlvég. Félbemaradt
rekordnál a finish truncated hibát ad.dg_destroy hívással, finish nélkül
megszüntethető; ilyenkor nem keletkezik report. A TCP-adapter minden
kéréshez rövid életű parsert hoz létre, és a hívás végén mindig
megszünteti.feed és finish sikertelen, az eredeti
hibaadat változatlan. Sikeres finish után a további
finish sikeres, a további feed viszont
state hiba: ez API-használati hiba, nem a bemenet
hibája.dg_report és a
dg_error értékként kimásolható, a dg_code_name
statikus szöveget ad. A CLI és a TCP-adapter ugyanazt a sorformátumot
írja.Az alábbi teszt a fenti nyelvtan példáit rögzíti, és a megvalósítási labor könyvtárához linkelve fut. A táblázat a specifikációból származik, nem az implementáció kimenetéből; így a megvalósítást a szerződéshez mérjük, nem önmagához.
/* tests/spec_examples.c -- a 17.1 specifikációs példái a 17.2 magon */
#include "diag.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct example {
const char *input; /* a bemenet bájtjai */
size_t size; /* beágyazott 0x00 miatt külön hossz */
dg_code code; /* DG_OK vagy a várt hibakód */
size_t offset; /* hiba esetén */
size_t records; /* siker esetén */
unsigned long long sum;
size_t bytes;
};
#define EX(s) s, sizeof s - 1
static const struct example examples[] = {
{ EX(""), DG_OK, 0, 0, 0, 0 },
{ EX("a=7\ncpu=12\n"), DG_OK, 0, 2, 19, 11 },
{ EX("a=65535\n"), DG_OK, 0, 1, 65535, 8 },
{ EX("abcdefghijklmnop=1\n"), DG_OK, 0, 1, 1, 19 },
{ EX("a=7\ncpu=12"), DG_TRUNCATED, 10, 0, 0, 0 },
{ EX("cpu="), DG_TRUNCATED, 4, 0, 0, 0 },
{ EX("a=65536\n"), DG_SYNTAX, 6, 0, 0, 0 },
{ EX("a=000001\n"), DG_SYNTAX, 7, 0, 0, 0 },
{ EX("abcdefghijklmnopq=1\n"),DG_SYNTAX, 16, 0, 0, 0 },
{ EX("a=1\r\n"), DG_SYNTAX, 3, 0, 0, 0 },
{ EX("a=\n"), DG_SYNTAX, 2, 0, 0, 0 },
{ EX("=1\n"), DG_SYNTAX, 0, 0, 0, 0 },
{ EX("A=1\n"), DG_SYNTAX, 0, 0, 0, 0 },
{ EX("a=1\0\n"), DG_SYNTAX, 3, 0, 0, 0 },
};
int main(void)
{
int failures = 0;
for (size_t i = 0; i < sizeof examples / sizeof examples[0]; ++i) {
const struct example *ex = &examples[i];
dg_parser *parser = dg_create(NULL);
if (parser == NULL)
return EXIT_FAILURE;
dg_view view = { (const unsigned char *)ex->input, ex->size };
bool ok = dg_feed(parser, view) && dg_finish(parser);
dg_error e = dg_get_error(parser);
dg_report r = dg_get_report(parser);
bool pass = ok ? ex->code == DG_OK && r.records == ex->records &&
r.sum == ex->sum && r.bytes == ex->bytes
: e.code == ex->code && e.offset == ex->offset;
if (!pass) {
(void)fprintf(stderr, "example %zu failed\n", i);
++failures;
}
dg_destroy(parser);
}
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}A "a=000001\n" példa a hatodik számjegynél (offset 7)
hibás, akkor is, ha az érték kicsi: a számjegyszám-korlát a nyelvtan
része, nem az értékhatár következménye.
Fordítás és futtatás a 17.2 buildje után:
gcc -std=c17 -O2 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror \
-Iinclude tests/spec_examples.c build/gcc/libdiag.a \
-o build/gcc/spec_examples
build/gcc/spec_examplesBuildjegyzőkönyv (lab-gcc profil). A
teszt, a 17.2 könyvtára és CLI-je Linux x86-64 környezetben, GCC 13.3.0,
glibc 2.39 és GNU binutils 2.42 mellett hibaüzenet és figyelmeztetés
nélkül fordult, és mind a 14 példa sikeres volt. Clanggal ebben a
jegyzőkönyvben nem fordítottunk.
| Erőforrás vagy nézet | Tulajdonos | Élettartam és érvénytelenítés |
|---|---|---|
CLI által megnyitott bemeneti FILE |
CLI | fopen sikerétől a cleanup-ágban végzett egyszeri
fclose-ig |
stdin, stdout, stderr |
Futási környezet; a CLI csak kölcsönzi | A CLI nem zárja le |
| Listener fd | Szerver-vezérlő | Sikeres létrehozástól leállításig |
| Elfogadott socket fd | Kapcsolatrekesz | Átadás után kizárólag a rekesz teardownja zárja le |
| CLI parserobjektuma | CLI | dg_create sikerétől dg_destroy-ig |
| TCP-kérés parserobjektuma | diag_core_reply hívás |
A hívás végéig; visszatérés előtt megszűnik |
| Kapcsolat bemeneti és kimeneti tömbje | Kapcsolatrekesz | A rekesz élettartama; a bemenet tömörítéskor változik |
| Parsernek átadott bájtszelet | Nem tulajdonló nézet | Csak a dg_feed hívás alatt használható |
| Következő kiírandó válaszrész | Nem tulajdonló nézet | A kimeneti tömb újrafelhasználása érvényteleníti |
A kapcsolat életciklusa szöveges ownership diagramként:
szerver-vezérlő (owner)
-> kapcsolatrekesz (owner)
-> socket fd
-> bemeneti tömb -> diag_core_reply alatti borrowed szelet
-> rövid életű parser (a hívás alatt)
-> kimeneti tömb -> részleges írás alatti borrowed szelet
A parser élettartama a TCP-adapterben rövidebb a kapcsolaténál: a válasz a hívás végéig saját másolatként a kimeneti tömbbe kerül. A kimeneti tömb tartalma viszont egy részleges írás után változatlanul megmarad, amíg az utolsó bájt el nem ment.
Az elfogadás utáni inicializálási hiba külön ownership eset. Ha a kapcsolatrekesz még nem vette át a socketet, az elfogadó ág zárja le. Sikeres átadás után már nem zárhatja le másodszor.
A CLI egy munkamenetet dolgoz fel: a fájlvégnél hívja a
dg_finish műveletet, majd kiírja a jelentést.
A TCP-kapcsolaton a recv nulla visszatérési értéke jelzi
a kliens küldési irányának lezárását. Ha ekkor nincs tárolt, félbemaradt
kérés, a kapcsolat rendben lezárul; ha van, az keretszintű hiba, és a
kapcsolat válasz nélkül bomlik. A recv ideiglenes „nincs
adat” eredménye nem fájlvég.
A kapcsolat állapotai és az eseményérdeklődés a TCP-adapter állapottábláját követik. Reset vagy
írási hiba esetén a válasz nem kézbesíthető; ilyenkor teardown
következik. A helyi sikeres send() sem bizonyítja, hogy a
távoli alkalmazás feldolgozta a választ.
Kezelt leállításkor:
A signalkezelés mechanikáját nem itt vezetjük le. A követelmény az, hogy a felszabadítás és a reportkészítés rendes vezérlési környezetben történjen.
Az elfogadás a minőségi kapuk elvére épül: minden követelményhez megfigyelhető ellenőrzés tartozik.
| Eset | Elvárt eredmény |
|---|---|
| Üres bemenet | OK records=0 sum=0 bytes=0, kilépési kód 0 |
| A bemutatott 11 oktettes folyam | OK records=2 sum=19 bytes=11 |
| Ugyanez minden lehetséges kettévágással | Azonos végeredmény |
| Fájlvég félbemaradt rekordban | truncated, offset a bemenet vége, kilépési kód 4 |
| Nyelvtanon kívüli oktett | syntax, az első elutasított oktett offsetje |
65535, illetve 65536 érték |
Elfogadás, illetve syntax az utolsó számjegynél |
262 144 darab a=0\n rekord; majd plusz egy oktett |
Siker 1 048 576 bájttal, illetve limit 1 048 576
offsettel |
diag FILE extra |
ERR code=usage, kilépési kód 2, üres stdout |
| Nem létező fájl | ERR code=open, kilépési kód 3 |
| TCP: két érvényes kérés egy szegmensben, majd egy hibás | Három válaszsor, sorrendben |
| TCP: részleges válaszírás, ismételt várakozás | Nincs adatvesztés, duplikálás vagy pufferfelülírás |
| TCP: 64 aktív kapcsolat után új elfogadás | Az új kapcsolat nem kap rekeszt, nincs állapotbővítés |
| Létrehozási hiba és shutdown | Minden saját fd és tároló pontosan egyszer szabadul |
A pontos méretlimit fixture-je a legrövidebb rekordból épül: oktett.
Gyakori hiba a bytes számláló növelése a teljes
read eredményével akkor is, ha a parser hibát talált a
darab közepén. A javítás: a jelentett bájtszám mindig a parser által
elfogyasztott mennyiség, nem az olvasott.
Ugyancsak hibás a finish meghívása átmeneti olvasási
szünetben, a kimeneti tömb újrafelhasználása részleges írás közben,
illetve az fd másolatainak külön tulajdonosként kezelése. Az fd egész
értékének másolása nem hoz létre új erőforrást.
A bemutatott kétrekordos folyamból a második rekord értékének utolsó
számjegye és a záró LF is hiányzik: a=7\ncpu=1. Add meg a
CLI kimenetét és kilépési kódját. Miért nem szabad a második rekordot
megszámolni, és miért nincs részleges OK jelentés?
Bővítsd a tests/spec_examples.c táblázatát a következő
esetekkel: üres sor (\n), dupla =, 16 betűs
név öt számjeggyel, cpu LF nélkül, valamint két rekord,
amelyek közül a második hibás. Minden várt értéket a nyelvtanból vezess
le, ne a program futtatásából.
Egy TCP-adapter a választ lokális tömbben formázza, eltárolja annak pointerét a kapcsolatban, majd visszatér. Másik hibája, hogy sikertelen rekeszinicializáláskor visszatér az elfogadási ágból, de nem zárja le a socketet. Készíts ownership-alapú javítási tervet.
A bemenet a=7\ncpu=1, 9 oktett. A parser mind a 9
oktettet elfogyasztja, mert mindegyik a nyelvtan szerinti helyen áll. A
fájlvégnél a második rekord a számfázisban van, ezért a
finish csonkolást jelez:
ERR code=truncated offset=9
A kilépési kód 4, a standard kimenet üres. A második rekord csak a
záró LF után számítható be, mert addig nem tudható, hogy érvényes-e.
Részleges OK jelentés azért nincs, mert a szerződés a
teljes munkamenetről szól: egy csonka folyam összegét a fogyasztó
tévesen teljesnek tekinthetné.
Ellenőrzési feltétel: minden bemeneti darabolás ugyanezt az eredményt adja.
| Bemenet | Várt eredmény |
|---|---|
"\n" |
syntax, offset 0: üres név |
"a==1\n" |
syntax, offset 2: a második = nem
számjegy |
"abcdefghijklmnop=65535\n" |
siker: 1 rekord, összeg 65535, 23 bájt |
"cpu" |
truncated, offset 3 |
"a=1\nb=x\n" |
syntax, offset 6 |
Az utolsó esetben az első rekord 4 oktettje elfogyott, a
b és a = is; az x a 6. offseten
áll. A tesztek a tényleges visszatérési értéket ellenőrizzék, ne
kizárólag kikapcsolható assert állításokra épüljenek.
A válasz tárolója legyen a kapcsolatrekesz kimeneti tömbje; így a részleges írás után is él. A lokális tömbre mutató pointer a függvényből visszatéréskor dangling lesz.
A socketnek az elfogadás pillanatától legyen azonosítható tulajdonosa. Inicializálási hiba esetén az elfogadó ág zárja le; sikeres átadás után a rekesz teardownja felel érte.
Ellenőrzési feltétel: késleltetett írással és létrehozási hiba-injektálással se maradjon érvénytelen pointer vagy nyitva felejtett fd.
A projekt lényegi eredménye a közös, ellenőrizhető szerződés:
egyértelmű rekordnyelvtan, rögzített CLI-kimenet és kilépési kódok,
korlátozott erőforrás-használat, I/O-tól független parser és explicit
ownership. A CLI és a TCP-adapter ugyanazt a dg_* magot
használja, és ugyanazt a sorformátumot írja; csak az adatmozgatás és a
lezárás mechanikája különbözik. A következő megvalósítási lépések és a
39. fejezet C++ portja ehhez a szerződéshez mérendő, nem pusztán néhány
sikeres futáshoz.