Salle 11
send / receive
Garder une copie d’un système de fichiers sur une autre machine semble simple : comparer les deux côtés et copier ce qui diffère. Mais comparer, c’est lire. Pour trouver les quelques blocs modifiés depuis la veille, un outil qui procède fichier par fichier lit, ou au moins liste, chaque fichier des deux côtés, à chaque fois, et ne voit un changement qu’à travers les contenus qu’il compare ou les dates et tailles auxquelles il se fie.
ZFS ne compare pas. Chaque pointeur de bloc garde le txg où son bloc est né, et un instantané pris au txg F ne contient aucun bloc né après F. La différence entre deux instantanés, ce sont donc les blocs du plus récent nés après le txg du plus ancien. La copie sur écriture réécrit chaque bloc au-dessus d’un bloc modifié : un sous-arbre dont la racine est ancienne n’a pas changé en dessous, et une seule comparaison à son sommet le saute en entier, sans le lire. Ce que le parcours trouve devient un flux d’enregistrements, chacun, sauf le premier, portant une somme de contrôle de tout ce qui le précède, que le côté réception vérifie enregistrement par enregistrement, avant d’appliquer chacun.
1 Envoyer @s4, depuis l’instantané de votre choix
tank/fs a quatre instantanés, un par txg de son histoire. La figure envoie le dernier,
@s4 : son arbre, chaque pointeur de bloc avec son txg de naissance logique. Choisissez
l’instantané de départ que le récepteur détient déjà, ou aucun pour un flux complet, puis déroulez l’envoi pas à
pas : à chaque pointeur de bloc, la naissance est comparée à F, le txg de l’instantané de départ, les
enregistrements tombent dans le flux avec leur somme de contrôle, et le récepteur les vérifie et les applique
jusqu’à ce que ses fichiers soient ceux de @s4. « Inverser un bit » abîme le flux en route.
Flux incrémental de @s2 à @s4 (F = 20) : visite de 12 pointeurs de bloc sur 19, 15 enregistrements, 6 728 octets.
Histoire de tank/fs
- txg 10 crée les fichiers a, b et c (objets 1 à 3), de quatre blocs de 512 octets chacun ; instantané
@s1 - txg 20 réécrit le bloc 2 du fichier a ; instantané
@s2 - txg 30 perce un trou dans les blocs 1 et 2 de b, crée d (objet 4) ; instantané
@s3 - txg 40 supprime c ; instantané
@s4
Côté envoi : l’arbre de @s4
12 pointeurs de bloc visités3 sautés4 en dessous, jamais lus15 enregistrements6 728 octets
- objset40> 20
- bloc de dnodes 040> 200meta-dnode1a2b3libre
- a : L120≤ 20a010a110a2′20a310
- b : L130> 20b010≤ 20trou30> 20trou30> 20b310≤ 20
- bloc de dnodes 130> 204d5libre6libre7libre
- d : L130> 20d030> 20d130> 20d230> 20d330> 20
Le flux
- 0BEGIN@s2 → @s4 (vérifié et appliqué)pas de somme
- 1OBJECTobjet 1, a (vérifié et appliqué)50788f0f
- 2FREEa 2 048–fin (vérifié et appliqué)43af0187
- 3OBJECTobjet 2, b (vérifié et appliqué)abe05a34
- 4FREEb 2 048–fin (vérifié et appliqué)e8b7bb43
- 5FREEb 512–1 536 (vérifié et appliqué)8c224c03
- 6FREEOBJECTSobjet 3 (vérifié et appliqué)356ecbe7
- 7OBJECTobjet 4, d (vérifié et appliqué)21559e25
- 8FREEd 2 048–fin (vérifié et appliqué)7331f09e
- 9WRITEd @0 « d0 » (vérifié et appliqué)753d4dba
- 10WRITEd @512 « d1 » (vérifié et appliqué)c439bd12
- 11WRITEd @1 024 « d2 » (vérifié et appliqué)a695a488
- 12WRITEd @1 536 « d3 » (vérifié et appliqué)cc0bcf92
- 13FREEOBJECTSobjets 5–7 (vérifié et appliqué)d3567f4c
- 14ENDsomme 58c87268 (vérifié et appliqué)b31db7bb
Côté réception
Le récepteur détient @s2 (txg 20).
- aobjet 1a0a1a2′a3
- bobjet 2b0troutroub3
- dobjet 4d0d1d2d3
END vérifié (58c87268). Les fichiers reçus sont ceux de @s4.
| Depuis | F | Visités | Sautés + dessous | Enregistrements | Octets |
|---|---|---|---|---|---|
| aucun (complet) | 0 | 19 / 19 | 0 + 0 | 21 | 11 672 |
| @s1 | 10 | 14 / 19 | 5 + 0 | 16 | 7 552 |
| @s2 | 20 | 12 / 19 | 3 + 4 | 15 | 6 728 |
| @s3 | 30 | 3 / 19 | 3 + 13 | 7 | 2 184 |
Version texte de cette figure
Flux incrémental de @s2 à @s4 (F = 20) : visite de 12 pointeurs de bloc sur 19, 15 enregistrements, 6 728 octets.
Le parcours
- objset, né au txg 40 : 40 ≤ 20 est faux, visité. send_cb ne produit aucun enregistrement ; le bloc est lu pour atteindre ce qui est en dessous.
- L1 du meta-dnode, né au txg 40 : 40 ≤ 20 est faux, visité. send_cb ne produit aucun enregistrement ; le bloc est lu pour atteindre ce qui est en dessous.
- bloc de dnodes 0, né au txg 40 : 40 ≤ 20 est faux, visité. send_cb produit un OBJECT_RANGE, écrit seulement dans un flux brut.
- Dnode 0, le numéro du meta-dnode : send_cb ne produit rien.
- Dnode 1, fichier a : send_cb produit OBJECT ; dump_dnode l’écrit, puis un FREE depuis la fin du fichier. Écrits : OBJECT 1 (a), FREE a depuis 2 048 jusqu’à la fin.
- a : L1, né au txg 20 : 20 ≤ 20, sauté. Jamais lu, pas plus que ce qui est en dessous (4 pointeurs de bloc).
- Dnode 2, fichier b : send_cb produit OBJECT ; dump_dnode l’écrit, puis un FREE depuis la fin du fichier. Écrits : OBJECT 2 (b), FREE b depuis 2 048 jusqu’à la fin.
- b : L1, né au txg 30 : 30 ≤ 20 est faux, visité. send_cb ne produit aucun enregistrement ; le bloc est lu pour atteindre ce qui est en dessous.
- b, bloc 0, né au txg 10 : 10 ≤ 20, sauté. Jamais lu.
- b, bloc 1 (trou), né au txg 30 : 30 ≤ 20 est faux, visité. send_cb produit HOLE : un FREE de sa plage. Gardé en attente : dump_free et dump_freeobjects y fusionnent ce qui suit.
- b, bloc 2 (trou), né au txg 30 : 30 ≤ 20 est faux, visité. send_cb produit HOLE : un FREE de sa plage. Gardé en attente : dump_free et dump_freeobjects y fusionnent ce qui suit.
- b, bloc 3, né au txg 10 : 10 ≤ 20, sauté. Jamais lu.
- Dnode 3, libre : send_cb produit OBJECT ; dump_dnode change un dnode vide en FREEOBJECTS. Écrit : FREE b depuis 512, 1 024 octets.
- bloc de dnodes 1, né au txg 30 : 30 ≤ 20 est faux, visité. send_cb produit un OBJECT_RANGE, écrit seulement dans un flux brut.
- Dnode 4, fichier d : send_cb produit OBJECT ; dump_dnode l’écrit, puis un FREE depuis la fin du fichier. Écrits : FREEOBJECTS 3, OBJECT 4 (d), FREE d depuis 2 048 jusqu’à la fin.
- d : L1, né au txg 30 : 30 ≤ 20 est faux, visité. send_cb ne produit aucun enregistrement ; le bloc est lu pour atteindre ce qui est en dessous.
- d, bloc 0, né au txg 30 : 30 ≤ 20 est faux, visité. send_cb produit DATA : un WRITE avec les 512 octets du bloc. Écrit : WRITE d à 0 (d0).
- d, bloc 1, né au txg 30 : 30 ≤ 20 est faux, visité. send_cb produit DATA : un WRITE avec les 512 octets du bloc. Écrit : WRITE d à 512 (d1).
- d, bloc 2, né au txg 30 : 30 ≤ 20 est faux, visité. send_cb produit DATA : un WRITE avec les 512 octets du bloc. Écrit : WRITE d à 1 024 (d2).
- d, bloc 3, né au txg 30 : 30 ≤ 20 est faux, visité. send_cb produit DATA : un WRITE avec les 512 octets du bloc. Écrit : WRITE d à 1 536 (d3).
- Dnode 5, libre : send_cb produit OBJECT ; dump_dnode change un dnode vide en FREEOBJECTS. Gardé en attente : dump_free et dump_freeobjects y fusionnent ce qui suit.
- Dnode 6, libre : send_cb produit OBJECT ; dump_dnode change un dnode vide en FREEOBJECTS. Gardé en attente : dump_free et dump_freeobjects y fusionnent ce qui suit.
- Dnode 7, libre : send_cb produit OBJECT ; dump_dnode change un dnode vide en FREEOBJECTS. Gardé en attente : dump_free et dump_freeobjects y fusionnent ce qui suit.
Les enregistrements
- BEGIN
- OBJECT 1 (a), drr_checksum 50788f0f
- FREE a depuis 2 048 jusqu’à la fin, drr_checksum 43af0187
- OBJECT 2 (b), drr_checksum abe05a34
- FREE b depuis 2 048 jusqu’à la fin, drr_checksum e8b7bb43
- FREE b depuis 512, 1 024 octets, drr_checksum 8c224c03
- FREEOBJECTS 3, drr_checksum 356ecbe7
- OBJECT 4 (d), drr_checksum 21559e25
- FREE d depuis 2 048 jusqu’à la fin, drr_checksum 7331f09e
- WRITE d à 0 (d0), drr_checksum 753d4dba
- WRITE d à 512 (d1), drr_checksum c439bd12
- WRITE d à 1 024 (d2), drr_checksum a695a488
- WRITE d à 1 536 (d3), drr_checksum cc0bcf92
- FREEOBJECTS 5 à 7, drr_checksum d3567f4c
- END, drr_checksum b31db7bb
END vérifié (58c87268). Les fichiers reçus sont ceux de @s4.
L’idée, à trois profondeurs
En une phrase
Un envoi incrémental parcourt l’arbre de l’instantané le plus récent et saute chaque pointeur de bloc né au txg du plus ancien ou avant, avec tout ce qui est en dessous, puis écrit le reste en un flux d’enregistrements dont le récepteur vérifie la somme de contrôle courante avant de les rejouer.
En un paragraphe
zfs send -i @from @to parcourt l’arbre de @to avec une borne basse, F, le txg où
@from a été créé. Un pointeur de bloc né à F ou avant était déjà dans @from, et tout le
sous-arbre en dessous aussi, puisque la copie sur écriture donne une
nouvelle naissance à chaque bloc au-dessus d’une modification : le parcours s’arrête là, sans le lire. Tout ce qu’il
visite devient des enregistrements : un OBJECT pour chaque dnode rencontré, un WRITE pour chaque bloc de données,
un FREE pour chaque trou, un FREEOBJECTS pour chaque emplacement de dnode vide. Le flux commence par BEGIN, qui
nomme les deux instantanés par leur GUID, et finit par END. Chaque enregistrement sauf BEGIN porte la somme
fletcher-4 de tous les octets du flux jusqu’à cette somme ; le récepteur tient la même somme et refuse le flux au
premier en-tête en désaccord, avant d’appliquer l’enregistrement qu’il suit. Sans instantané de départ, F vaut 0,
tous les blocs sont visités et le flux reconstruit le système de fichiers entier.
En détail
Un flux d’enregistrements. Un flux d’envoi est une suite de
dmu_replay_record_t, chacun un en-tête de 312 octets d’un
des onze types, parfois suivi d’une charge utile : les données d’un WRITE suivent son en-tête. BEGIN porte
le nombre magique 0x2F5bacbacULL, le GUID de l’instantané
envoyé et celui de l’instantané de départ.
La borne. Le parcours qui alimente un envoi commence ici.
send_traverse_thread
Extrait de module/zfs/dmu_send.c, lignes
1203–1221, OpenZFS zfs-2.4.4.
Voir sur GitHub
static __attribute__((noreturn)) void
send_traverse_thread(void *arg)
{
struct send_thread_arg *st_arg = arg;
int err = 0;
struct send_range *data;
fstrans_cookie_t cookie = spl_fstrans_mark();
err = traverse_dataset_resume(st_arg->os->os_dsl_dataset,
st_arg->fromtxg, &st_arg->resume,
st_arg->flags | TRAVERSE_LOGICAL, send_cb, st_arg);
if (err != EINTR)
st_arg->error_code = err;
data = range_alloc(DATA, 0, 0, 0, B_TRUE);
bqueue_enqueue_flush(&st_arg->q, data, sizeof (*data));
spl_fstrans_unmark(cookie);
thread_exit();
}-
L1211–L1213
Le dataset est parcouru depuis
fromtxg, le txg de création de l’instantané de départ, 0 pour un flux complet.TRAVERSE_LOGICALrejoint les drapeaux de l’envoi, dontTRAVERSE_PRE: le rappel d’un bloc passe avant ses enfants. -
L1217–L1218
Un marqueur de fin de flux suit la dernière plage. Les plages que
send_cbmet en file rejoignent le fil principal par d’autres files, dans l’ordre, et y deviennent des enregistrements.
Le test qui saute un sous-arbre. Chaque pointeur de bloc de l’instantané y passe.
traverse_visitbp
Extrait de module/zfs/dmu_traverse.c, lignes
249–279, OpenZFS zfs-2.4.4.
Voir sur GitHub
if (BP_GET_LOGICAL_BIRTH(bp) == 0) {
/*
* Since this block has a birth time of 0 it must be one of
* two things: a hole created before the
* SPA_FEATURE_HOLE_BIRTH feature was enabled, or a hole
* which has always been a hole in an object.
*
* If a file is written sparsely, then the unwritten parts of
* the file were "always holes" -- that is, they have been
* holes since this object was allocated. However, we (and
* our callers) can not necessarily tell when an object was
* allocated. Therefore, if it's possible that this object
* was freed and then its object number reused, we need to
* visit all the holes with birth==0.
*
* If it isn't possible that the object number was reused,
* then if SPA_FEATURE_HOLE_BIRTH was enabled before we wrote
* all the blocks we will visit as part of this traversal,
* then this hole must have always existed, so we can skip
* it. We visit blocks born after (exclusive) td_min_txg.
*
* Note that the meta-dnode cannot be reallocated.
*/
if (!send_holes_without_birth_time &&
(!td->td_realloc_possible ||
zb->zb_object == DMU_META_DNODE_OBJECT) &&
td->td_hole_birth_enabled_txg <= td->td_min_txg)
return (0);
} else if (get_birth_time(td, bp) <= td->td_min_txg) {
return (0);
}-
L249–L276
Un trou de naissance 0 n’a jamais été écrit, ou date d’avant la fonctionnalité
hole_birth. Ces lignes le sauteraient dans certains cas, mais seulement sisend_holes_without_birth_timevaut 0, et il vaut 1 par défaut : ces trous sont visités. La figure n’en a pas ; un trou percé dans b porte le txg de la libération, et passe par le test ci-dessous. -
L277–L278
La règle.
td_min_txgest la borne, F. Né à F ou avant, le bloc est dans l’instantané de départ : la fonction retourne avant de le lire ou d’appeler le rappel, et rien en dessous n’est atteint. Dans la figure, la puce passe en pointillés et son sous-arbre s’efface.
Naissance logique. get_birth_time renvoie la naissance
logique quand TRAVERSE_LOGICAL est posé, ce qui est le cas pour un envoi. Un pointeur de bloc
a deux naissances, et le commentaire qui les définit dit que la physique peut être plus
petite que la logique pour la déduplication et le clonage de blocs, plus grande pour les blocs déplacés et
réécrits, et que la logique est celle de la réplication incrémentale. L’envoi compare la
logique, le txg où l’utilisateur a modifié la donnée : un bloc dédupliqué ou cloné sur une copie plus
ancienne est bien envoyé, puisque l’utilisateur l’a écrit après F, et un bloc seulement réécrit ou déplacé
sur le disque n’est pas renvoyé.
Chaque dnode d’un bloc de dnodes. Quand un bloc de dnodes est visité, chacun de ses dnodes est confié à ceci.
traverse_dnode
Extrait de module/zfs/dmu_traverse.c, lignes
534–550, OpenZFS zfs-2.4.4.
Voir sur GitHub
if (td->td_flags & TRAVERSE_PRE) {
SET_BOOKMARK(&czb, objset, object, ZB_DNODE_LEVEL,
ZB_DNODE_BLKID);
err = td->td_func(td->td_spa, NULL, bp, &czb, dnp,
td->td_arg);
if (err == TRAVERSE_VISIT_NO_CHILDREN)
return (0);
if (err != 0)
return (err);
}
for (j = 0; j < dnp->dn_nblkptr; j++) {
SET_BOOKMARK(&czb, objset, object, dnp->dn_nlevels - 1, j);
err = traverse_visitbp(td, dnp, &dnp->dn_blkptr[j], &czb);
if (err != 0)
break;
}-
L534–L543
Le rappel est appelé pour le dnode lui-même. Aucune naissance n’est testée ici : chaque dnode d’un bloc de dnodes visité arrive à
send_cb, modifié ou non. -
L545–L550
Puis chacun des pointeurs de bloc du dnode passe par
traverse_visitbp, et par son test.
Voilà pourquoi, en envoyant @s4 depuis @s3, le flux contient des OBJECT pour a et
b, dont les données n’ont pas changé : la suppression de c a réécrit le bloc de dnodes qu’ils partagent avec
lui. Leurs blocs de données restent sautés, un niveau plus bas.
Des blocs visités aux enregistrements. Le rappel décide de ce que produit chaque visite.
send_cb
Extrait de module/zfs/dmu_send.c, lignes
1098–1120, OpenZFS zfs-2.4.4.
Voir sur GitHub
if (zb->zb_level == ZB_DNODE_LEVEL) {
if (zb->zb_object == DMU_META_DNODE_OBJECT)
return (0);
record = range_alloc(OBJECT, zb->zb_object, 0, 0, B_FALSE);
record->sru.object.bp = *bp;
size_t size = sizeof (*dnp) * (dnp->dn_extra_slots + 1);
record->sru.object.dnp = kmem_alloc(size, KM_SLEEP);
memcpy(record->sru.object.dnp, dnp, size);
bqueue_enqueue(&sta->q, record, sizeof (*record));
return (0);
}
if (zb->zb_level == 0 && zb->zb_object == DMU_META_DNODE_OBJECT &&
!BP_IS_HOLE(bp)) {
record = range_alloc(OBJECT_RANGE, 0, zb->zb_blkid,
zb->zb_blkid + 1, B_FALSE);
record->sru.object_range.bp = *bp;
bqueue_enqueue(&sta->q, record, sizeof (*record));
return (0);
}
if (zb->zb_level < 0 || (zb->zb_level > 0 && !BP_IS_HOLE(bp)))
return (0);
if (zb->zb_object == DMU_META_DNODE_OBJECT && !BP_IS_HOLE(bp))
return (0);- L1098–L1108 Un dnode devient une plage OBJECT, sauf le meta-dnode, l’objet 0.
- L1109–L1116 Un bloc de dnodes devient un OBJECT_RANGE, que seul un flux brut écrit.
- L1117–L1120 Le bloc objset et chaque bloc indirect qui n’est pas un trou ne produisent aucun enregistrement : ils ne sont lus que pour atteindre ce qui est en dessous.
send_cb
Extrait de module/zfs/dmu_send.c, lignes
1130–1146, OpenZFS zfs-2.4.4.
Voir sur GitHub
if (!overflow_multiply(span, zb->zb_blkid, &start) || (!(zb->zb_blkid ==
DMU_SPILL_BLKID || DMU_OT_IS_METADATA(dnp->dn_type)) &&
span * zb->zb_blkid > dnp->dn_maxblkid)) {
ASSERT(BP_IS_HOLE(bp));
return (0);
}
if (zb->zb_blkid == DMU_SPILL_BLKID)
ASSERT3U(BP_GET_TYPE(bp), ==, DMU_OT_SA);
enum type record_type = DATA;
if (BP_IS_HOLE(bp))
record_type = HOLE;
else if (BP_IS_REDACTED(bp))
record_type = REDACT;
else
record_type = DATA;- L1130–L1135 Rien au-delà de la fin de l’objet.
- L1140–L1146 Il reste un bloc de données (un WRITE) ou un trou (un FREE de sa plage ; un trou parmi les blocs de dnodes libère des objets). REDACT relève des envois expurgés.
Une plage OBJECT va à dump_dnode. Un emplacement de dnode sans objet
devient FREEOBJECTS ; un fichier devient OBJECT, avec son tampon bonus en charge utile,
suivi aussitôt d’un FREE de la fin du fichier jusqu’à DMU_OBJECT_END, pour
qu’un récepteur dont la copie était plus longue la coupe. dump_free garde un
FREE en attente et y fusionne le suivant quand il le prolonge, d’où un seul FREE pour les deux trous de b ; un
FREE jusqu’à la fin est écrit aussitôt, et tout autre enregistrement
écrit d’abord celui en attente. Les FREEOBJECTS sont fusionnés
de même, et les objets au-delà du dernier bloc de dnodes sont abandonnés. Un flux
complet porte aussi des libérations, pour pouvoir être reçu comme un clone.
La somme de contrôle qui court le long du flux.
dump_record
Extrait de module/zfs/dmu_send.c, lignes
257–283, OpenZFS zfs-2.4.4.
Voir sur GitHub
/*
* For all record types except BEGIN, fill in the checksum (overlaid in
* drr_u.drr_checksum.drr_checksum). The checksum verifies everything
* up to the start of the checksum itself.
*/
static int
dump_record(dmu_send_cookie_t *dscp, void *payload, int payload_len)
{
dmu_send_outparams_t *dso = dscp->dsc_dso;
ASSERT3U(offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum),
==, sizeof (dmu_replay_record_t) - sizeof (zio_cksum_t));
(void) fletcher_4_incremental_native(dscp->dsc_drr,
offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum),
&dscp->dsc_zc);
if (dscp->dsc_drr->drr_type == DRR_BEGIN) {
dscp->dsc_sent_begin = B_TRUE;
} else {
ASSERT(ZIO_CHECKSUM_IS_ZERO(&dscp->dsc_drr->drr_u.
drr_checksum.drr_checksum));
dscp->dsc_drr->drr_u.drr_checksum.drr_checksum = dscp->dsc_zc;
}
if (dscp->dsc_drr->drr_type == DRR_END) {
dscp->dsc_sent_end = B_TRUE;
}
(void) fletcher_4_incremental_native(&dscp->dsc_drr->
drr_u.drr_checksum.drr_checksum,
sizeof (zio_cksum_t), &dscp->dsc_zc);- L268–L270 La somme fletcher-4 courante est prolongée sur l’enregistrement, jusqu’à ses 32 derniers octets.
-
L271–L277
Sauf pour BEGIN, cette valeur va dans ces 32 octets,
drr_checksum, superposé à tous les types d’enregistrement. -
L281–L283
Puis les 32 octets sont sommés, et la charge utile après eux. Le
drr_checksumde chaque enregistrement est donc le fletcher-4 de tous les octets du flux qui le précèdent, y compris son propre en-tête jusqu’à la somme. La figure montre les 32 bits de poids faible de son dernier mot.
END en porte un de plus : drr_end.drr_checksum, la somme de tout ce qui le
précède.
Le côté réception vérifie avant d’appliquer.
receive_read_payload_and_next_header
Extrait de module/zfs/dmu_recv.c, lignes
2843–2868, OpenZFS zfs-2.4.4.
Voir sur GitHub
/*
* Note: checksum is of everything up to but not including the
* checksum itself.
*/
ASSERT3U(offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum),
==, sizeof (dmu_replay_record_t) - sizeof (zio_cksum_t));
receive_cksum(drc,
offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum),
&drc->drc_next_rrd->header);
zio_cksum_t cksum_orig =
drc->drc_next_rrd->header.drr_u.drr_checksum.drr_checksum;
zio_cksum_t *cksump =
&drc->drc_next_rrd->header.drr_u.drr_checksum.drr_checksum;
if (drc->drc_byteswap)
byteswap_record(&drc->drc_next_rrd->header);
if ((!ZIO_CHECKSUM_IS_ZERO(cksump)) &&
!ZIO_CHECKSUM_EQUAL(drc->drc_cksum, *cksump)) {
kmem_free(drc->drc_next_rrd, sizeof (*drc->drc_next_rrd));
drc->drc_next_rrd = NULL;
return (SET_ERROR(ECKSUM));
}
receive_cksum(drc, sizeof (cksum_orig), &cksum_orig);- L2843–L2851 Le récepteur tient sa propre somme : elle commence sur tout l’enregistrement BEGIN, se prolonge sur chaque charge utile, et ici sur l’en-tête suivant, jusqu’à sa somme de contrôle.
-
L2861–L2866
Un en-tête dont le
drr_checksumdiffère arrête la réception :ECKSUM. Une somme entièrement nulle n’est pas vérifiée. - L2868 La valeur stockée est ensuite sommée, comme l’a fait l’émetteur.
Cela se passe quand la charge utile d’un enregistrement et l’en-tête suivant ont été lus, et c’est seulement
alors que l’enregistrement est mis en file pour l’écrivain : un enregistrement dont les
octets ont été abîmés n’est jamais appliqué. END est vérifié une fois de plus, son
drr_end.drr_checksum contre la somme qui le précède. En cas d’erreur,
ce que la réception a créé est détruit : le récepteur détient toujours l’instantané de
départ. Avant tout cela, le BEGIN d’un flux incrémental nomme l’instantané de départ par son
GUID, et le récepteur le refuse (ENODEV) s’il ne détient pas d’instantané à
ce GUID.
Des enregistrements répartis par type. Le fil d’écriture sort chaque enregistrement de la file et le confie ici.
receive_process_record
Extrait de module/zfs/dmu_recv.c, lignes
3188–3213, OpenZFS zfs-2.4.4.
Voir sur GitHub
switch (rrd->header.drr_type) {
case DRR_OBJECT:
{
struct drr_object *drro = &rrd->header.drr_u.drr_object;
err = receive_object(rwa, drro, rrd->payload);
vmem_free(rrd->payload, rrd->payload_size);
rrd->payload = NULL;
break;
}
case DRR_FREEOBJECTS:
{
struct drr_freeobjects *drrfo =
&rrd->header.drr_u.drr_freeobjects;
err = receive_freeobjects(rwa, drrfo);
break;
}
case DRR_WRITE:
{
err = receive_process_write_record(rwa, rrd);
if (rwa->heal) {
/*
* If healing - always free the abd after processing
*/
abd_free(rrd->abd);
rrd->abd = NULL;
} else if (err != EAGAIN) {-
L3189–L3196
OBJECT va à
receive_object, qui crée l’objet, ou garde celui qui existe. -
L3197–L3203
FREEOBJECTS va à
receive_freeobjects, qui libère chaque objet existant de la plage : c disparaît. -
L3204–L3206
WRITE va à
receive_process_write_record. FREE, plus bas, va àreceive_free, qui libère la plage dans l’objet.
Ce que le récepteur obtient. Parti des fichiers de l’instantané de départ, il retrouve par
les enregistrements du flux exactement @s4 : la figure compare les deux à la fin, pour chaque
instantané de départ, et ses tests le vérifient contre les enregistrements de chaque flux établis à la main.
Ce que la figure laisse de côté
La figure est petite : des fichiers de quatre blocs de 512 octets sous un bloc indirect, des blocs de quatre
dnodes (un vrai bloc de dnodes de 16 Kio en contient 32), un meta-dnode dessiné avec ses deux blocs de dnodes,
pas de tampon bonus. L’envoi s’y déroule d’une traite là où le code relie plusieurs fils (parcours, fusion,
lecture, fil principal) par des files qui gardent l’ordre, et le récepteur démarre une fois le flux complet, là où
le code reçoit par un tube au fil de l’écriture. Les en-têtes suivent la disposition de
dmu_replay_record_t, seuls les champs utilisés par la figure étant remplis. Les flux expurgés, bruts
(chiffrés), compressés et reprenables, les données embarquées, WRITE_BYREF et les blocs spill ne sont pas montrés ;
pas plus que des débits, qui dépendent des disques et du réseau, pas de ce code.