Sala 11
send / receive
Mantener una copia de un sistema de archivos en otra máquina parece sencillo: comparar los dos lados y copiar lo que difiere. Pero comparar significa leer. Para encontrar los pocos bloques que cambiaron desde ayer, una herramienta que trabaja archivo por archivo lee, o como mínimo enumera, todos los archivos de ambos lados, cada vez, y solo puede ver un cambio a través del contenido que compara o de las fechas y tamaños en los que confía.
ZFS no compara. Cada puntero de bloque guarda el txg en el que nació su bloque, y una instantánea tomada en el txg F no contiene ningún bloque nacido después de F. Así que la diferencia entre dos instantáneas son los bloques de la más reciente nacidos después del txg de la más antigua. La copia en escritura reescribe todos los bloques por encima de uno modificado, así que un subárbol con una raíz antigua no ha cambiado por debajo: una sola comparación en su cima lo salta entero, sin leerlo. Lo que encuentra el recorrido se convierte en un flujo de registros, cada uno salvo el primero con una suma de comprobación de todo lo anterior, que el lado receptor comprueba registro a registro, antes de aplicar cada uno.
1 Enviar @s4, desde la instantánea que elijas
tank/fs tiene cuatro instantáneas, una por cada txg de su historia. La figura envía la última,
@s4: su árbol, cada puntero de bloque con su txg de nacimiento lógico. Elige la instantánea de
origen que el receptor ya tiene, o ninguna para un flujo completo, y sigue el envío paso a paso: en cada
puntero de bloque se compara el nacimiento con F, el txg de la instantánea de origen, los registros caen en el
flujo con sus sumas de comprobación, y el receptor los comprueba y los aplica hasta que sus archivos son los de
@s4. «Invertir un bit» daña el flujo por el camino.
Flujo incremental de @s2 a @s4 (F = 20): visitados 12 punteros de bloque de 19, 15 registros, 6728 bytes.
Historia de tank/fs
- txg 10 crea los archivos a, b y c (objetos 1 a 3), de cuatro bloques de 512 bytes cada uno; instantánea
@s1 - txg 20 reescribe el bloque 2 de a; instantánea
@s2 - txg 30 abre un hueco en los bloques 1 y 2 de b, crea d (objeto 4); instantánea
@s3 - txg 40 elimina c; instantánea
@s4
Lado emisor: el árbol de @s4
12 punteros de bloque visitados3 omitidos4 por debajo, nunca leídos15 registros6728 bytes
- objset40> 20
- bloque de dnodes 040> 200meta-dnode1a2b3libre
- a: L120≤ 20a010a110a2′20a310
- b: L130> 20b010≤ 20hueco30> 20hueco30> 20b310≤ 20
- bloque de dnodes 130> 204d5libre6libre7libre
- d: L130> 20d030> 20d130> 20d230> 20d330> 20
El flujo
- 0BEGIN@s2 → @s4 (comprobado y aplicado)sin suma
- 1OBJECTobjeto 1, a (comprobado y aplicado)50788f0f
- 2FREEa 2048–final (comprobado y aplicado)43af0187
- 3OBJECTobjeto 2, b (comprobado y aplicado)abe05a34
- 4FREEb 2048–final (comprobado y aplicado)e8b7bb43
- 5FREEb 512–1536 (comprobado y aplicado)8c224c03
- 6FREEOBJECTSobjeto 3 (comprobado y aplicado)356ecbe7
- 7OBJECTobjeto 4, d (comprobado y aplicado)21559e25
- 8FREEd 2048–final (comprobado y aplicado)7331f09e
- 9WRITEd @0 «d0» (comprobado y aplicado)753d4dba
- 10WRITEd @512 «d1» (comprobado y aplicado)c439bd12
- 11WRITEd @1024 «d2» (comprobado y aplicado)a695a488
- 12WRITEd @1536 «d3» (comprobado y aplicado)cc0bcf92
- 13FREEOBJECTSobjetos 5–7 (comprobado y aplicado)d3567f4c
- 14ENDsuma 58c87268 (comprobado y aplicado)b31db7bb
Lado receptor
El receptor tiene @s2 (txg 20).
- aobjeto 1a0a1a2′a3
- bobjeto 2b0huecohuecob3
- dobjeto 4d0d1d2d3
END comprobado (58c87268). Los archivos recibidos son los de @s4.
| Desde | F | Visitados | Omitidos + debajo | Registros | Bytes |
|---|---|---|---|---|---|
| ninguna (completo) | 0 | 19 / 19 | 0 + 0 | 21 | 11 672 |
| @s1 | 10 | 14 / 19 | 5 + 0 | 16 | 7552 |
| @s2 | 20 | 12 / 19 | 3 + 4 | 15 | 6728 |
| @s3 | 30 | 3 / 19 | 3 + 13 | 7 | 2184 |
Versión en texto de esta figura
Flujo incremental de @s2 a @s4 (F = 20): visitados 12 punteros de bloque de 19, 15 registros, 6728 bytes.
El recorrido
- objset, nacido en el txg 40: 40 ≤ 20 es falso, visitado. send_cb no produce ningún registro; el bloque se lee para llegar a lo que hay por debajo.
- L1 del meta-dnode, nacido en el txg 40: 40 ≤ 20 es falso, visitado. send_cb no produce ningún registro; el bloque se lee para llegar a lo que hay por debajo.
- bloque de dnodes 0, nacido en el txg 40: 40 ≤ 20 es falso, visitado. send_cb produce un OBJECT_RANGE, que solo se escribe en un flujo raw.
- Dnode 0, el número del meta-dnode: send_cb no produce nada.
- Dnode 1, archivo a: send_cb produce OBJECT; dump_dnode lo escribe, y luego un FREE desde el final del archivo. Escritos: OBJECT 1 (a), FREE a desde 2048 hasta el final.
- a: L1, nacido en el txg 20: 20 ≤ 20, omitido. Nunca leído, ni lo que hay por debajo (4 punteros de bloque).
- Dnode 2, archivo b: send_cb produce OBJECT; dump_dnode lo escribe, y luego un FREE desde el final del archivo. Escritos: OBJECT 2 (b), FREE b desde 2048 hasta el final.
- b: L1, nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb no produce ningún registro; el bloque se lee para llegar a lo que hay por debajo.
- b, bloque 0, nacido en el txg 10: 10 ≤ 20, omitido. Nunca leído.
- b, bloque 1 (hueco), nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb produce HOLE: un FREE de su intervalo. Retenido en espera: dump_free y dump_freeobjects fusionan en él lo que sigue.
- b, bloque 2 (hueco), nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb produce HOLE: un FREE de su intervalo. Retenido en espera: dump_free y dump_freeobjects fusionan en él lo que sigue.
- b, bloque 3, nacido en el txg 10: 10 ≤ 20, omitido. Nunca leído.
- Dnode 3, libre: send_cb produce OBJECT; dump_dnode convierte un dnode vacío en FREEOBJECTS. Escrito: FREE b desde 512, 1024 bytes.
- bloque de dnodes 1, nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb produce un OBJECT_RANGE, que solo se escribe en un flujo raw.
- Dnode 4, archivo d: send_cb produce OBJECT; dump_dnode lo escribe, y luego un FREE desde el final del archivo. Escritos: FREEOBJECTS 3, OBJECT 4 (d), FREE d desde 2048 hasta el final.
- d: L1, nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb no produce ningún registro; el bloque se lee para llegar a lo que hay por debajo.
- d, bloque 0, nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb produce DATA: un WRITE con los 512 bytes del bloque. Escrito: WRITE d en 0 (d0).
- d, bloque 1, nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb produce DATA: un WRITE con los 512 bytes del bloque. Escrito: WRITE d en 512 (d1).
- d, bloque 2, nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb produce DATA: un WRITE con los 512 bytes del bloque. Escrito: WRITE d en 1024 (d2).
- d, bloque 3, nacido en el txg 30: 30 ≤ 20 es falso, visitado. send_cb produce DATA: un WRITE con los 512 bytes del bloque. Escrito: WRITE d en 1536 (d3).
- Dnode 5, libre: send_cb produce OBJECT; dump_dnode convierte un dnode vacío en FREEOBJECTS. Retenido en espera: dump_free y dump_freeobjects fusionan en él lo que sigue.
- Dnode 6, libre: send_cb produce OBJECT; dump_dnode convierte un dnode vacío en FREEOBJECTS. Retenido en espera: dump_free y dump_freeobjects fusionan en él lo que sigue.
- Dnode 7, libre: send_cb produce OBJECT; dump_dnode convierte un dnode vacío en FREEOBJECTS. Retenido en espera: dump_free y dump_freeobjects fusionan en él lo que sigue.
Los registros
- BEGIN
- OBJECT 1 (a), drr_checksum 50788f0f
- FREE a desde 2048 hasta el final, drr_checksum 43af0187
- OBJECT 2 (b), drr_checksum abe05a34
- FREE b desde 2048 hasta el final, drr_checksum e8b7bb43
- FREE b desde 512, 1024 bytes, drr_checksum 8c224c03
- FREEOBJECTS 3, drr_checksum 356ecbe7
- OBJECT 4 (d), drr_checksum 21559e25
- FREE d desde 2048 hasta el final, drr_checksum 7331f09e
- WRITE d en 0 (d0), drr_checksum 753d4dba
- WRITE d en 512 (d1), drr_checksum c439bd12
- WRITE d en 1024 (d2), drr_checksum a695a488
- WRITE d en 1536 (d3), drr_checksum cc0bcf92
- FREEOBJECTS 5 a 7, drr_checksum d3567f4c
- END, drr_checksum b31db7bb
END comprobado (58c87268). Los archivos recibidos son los de @s4.
La idea, a tres profundidades
En una frase
Un envío incremental recorre el árbol de la instantánea más reciente y se salta cada puntero de bloque nacido en el txg de la instantánea más antigua o antes, con todo lo que tiene por debajo, y escribe lo que queda como un flujo de registros cuya suma de comprobación acumulada verifica el receptor antes de reproducirlos.
En un párrafo
zfs send -i @from @to recorre el árbol de @to con una cota inferior, F, el txg en el que
se creó @from. Un puntero de bloque nacido en F o antes ya estaba en @from, y también
todo el subárbol que tiene debajo, ya que la copia en escritura da un
nacimiento nuevo a todos los bloques por encima de un cambio: el recorrido vuelve sin leerlo. Todo lo que sí
visita se convierte en registros: un OBJECT por cada dnode que encuentra, un WRITE por cada bloque de datos, un
FREE por cada hueco, FREEOBJECTS por cada ranura de dnode vacía. El flujo empieza con BEGIN, que nombra las dos
instantáneas por su GUID, y termina con END. Cada registro salvo BEGIN lleva la suma fletcher-4 de todos los
bytes del flujo hasta esa suma; el receptor lleva la misma suma y rechaza el flujo en la primera cabecera que no
coincide, antes de aplicar el registro al que sigue. Sin instantánea de origen, F vale 0, se visitan todos los
bloques, y el flujo reconstruye el sistema de archivos entero.
En detalle
Un flujo de registros. Un flujo de send es una secuencia de dmu_replay_record_t,
cada uno una cabecera de 312 bytes con uno de once tipos, a veces seguida de una carga
útil: los datos de un WRITE siguen a su cabecera. BEGIN lleva el número mágico 0x2F5bacbacULL,
y el GUID de la instantánea enviada y el de aquella desde la que parte.
La cota. El recorrido que alimenta un envío empieza aquí.
send_traverse_thread
Extracto de module/zfs/dmu_send.c, líneas
1203–1221, OpenZFS zfs-2.4.4.
Ver en 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
El dataset se recorre desde
fromtxg, el txg de creación de la instantánea de origen, 0 para un flujo completo.TRAVERSE_LOGICALse suma a los indicadores del envío, entre ellosTRAVERSE_PRE: la función de retorno de un bloque se ejecuta antes que sus hijos. -
L1217–L1218
Tras el último intervalo va una marca de fin de flujo. Los intervalos que
send_cbpone en cola llegan al hilo principal a través de más colas, en orden, y allí se convierten en registros.
La prueba que salta un subárbol. Todos los punteros de bloque de la instantánea pasan por aquí.
traverse_visitbp
Extracto de module/zfs/dmu_traverse.c, líneas
249–279, OpenZFS zfs-2.4.4.
Ver en 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 hueco con fecha de nacimiento 0 nunca se escribió, o se creó antes de la funcionalidad
hole_birth. Estas líneas lo saltarían en algunos casos, pero solo cuandosend_holes_without_birth_timevale 0, y vale 1 por defecto: esos huecos se visitan. La figura no tiene ninguno; un hueco abierto en b lleva el txg de la liberación, y pasa por la prueba de abajo. -
L277–L278
La regla.
td_min_txges la cota, F. Si nació en F o antes, el bloque está en la instantánea de origen: la función vuelve antes de leerlo o de llamar a la función de retorno, así que no se alcanza nada de lo que tiene debajo. En la figura la ficha pasa a borde discontinuo y su subárbol se atenúa.
El nacimiento lógico. get_birth_time devuelve el nacimiento lógico
cuando TRAVERSE_LOGICAL está activado, como en un envío. Un puntero de bloque tiene dos
nacimientos, y el comentario que los define dice que el físico puede ser menor que
el lógico con la deduplicación y la clonación de bloques, y mayor con los bloques reasignados y reescritos, y que
el lógico es el que sirve para la replicación incremental. Send compara el lógico, el txg
en el que el usuario cambió los datos: un bloque deduplicado o clonado sobre una copia más antigua se envía igual,
ya que el usuario lo escribió después de F, y un bloque que solo se reescribió o se reasignó en disco no se
vuelve a enviar.
Cada dnode de un bloque de dnodes. Cuando se visita un bloque de dnodes, cada uno de sus dnodes se entrega a esta función.
traverse_dnode
Extracto de module/zfs/dmu_traverse.c, líneas
534–550, OpenZFS zfs-2.4.4.
Ver en 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
La función de retorno se ejecuta para el propio dnode. Aquí no se prueba ningún nacimiento: todos los dnodes de un bloque de dnodes visitado llegan a
send_cb, hayan cambiado o no. -
L545–L550
Después cada uno de los punteros de bloque del dnode pasa por
traverse_visitbp, y por su prueba.
Por eso, al enviar @s4 desde @s3, el flujo contiene registros OBJECT para a y b, cuyos
datos no cambiaron: borrar c reescribió el bloque de dnodes que comparten con él. Sus bloques de datos se
siguen saltando, un nivel más abajo.
De los bloques visitados a los registros. La función de retorno decide lo que produce cada visita.
send_cb
Extracto de module/zfs/dmu_send.c, líneas
1098–1120, OpenZFS zfs-2.4.4.
Ver en 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 se convierte en un intervalo OBJECT, salvo el meta-dnode, el objeto 0.
- L1109–L1116 Un bloque de dnodes se convierte en un OBJECT_RANGE, que solo escribe un flujo raw.
- L1117–L1120 El bloque del objset y cada bloque indirecto que no es un hueco no producen ningún registro: solo se leen para llegar a lo que tienen debajo.
send_cb
Extracto de module/zfs/dmu_send.c, líneas
1130–1146, OpenZFS zfs-2.4.4.
Ver en 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 Nada más allá del final del objeto.
- L1140–L1146 Lo que queda es un bloque de datos (un WRITE) o un hueco (un FREE de su intervalo; un hueco entre los bloques de dnodes libera objetos). REDACT pertenece a los envíos redactados.
Un intervalo OBJECT va a dump_dnode. Una ranura de dnode sin ningún objeto
se convierte en FREEOBJECTS; un archivo se convierte en OBJECT, con su búfer bonus como
carga útil, seguido de inmediato por un FREE desde el final del
archivo hasta DMU_OBJECT_END, para que un receptor cuya copia era más larga la recorte.
dump_free mantiene un FREE pendiente y fusiona en él el siguiente cuando lo
continúa, y por eso los dos huecos de b viajan como un solo FREE; un FREE hasta el final
se escribe de inmediato, y cualquier otro registro escribe
primero el pendiente. Los FREEOBJECTS se fusionan del mismo modo, y los objetos
más allá del último bloque de dnodes se descartan. Un flujo completo también lleva
liberaciones, para poder recibirse como un clon.
La suma de comprobación que recorre el flujo.
dump_record
Extracto de module/zfs/dmu_send.c, líneas
257–283, OpenZFS zfs-2.4.4.
Ver en 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 suma fletcher-4 acumulada continúa sobre el registro, hasta sus últimos 32 bytes.
-
L271–L277
Salvo para BEGIN, ese valor va en esos 32 bytes,
drr_checksum, superpuestos a todos los tipos de registro. -
L281–L283
Después se suman los 32 bytes, y la carga útil que los sigue. Así, el
drr_checksumde cada registro es la fletcher-4 de todos los bytes del flujo anteriores a él, incluida su propia cabecera hasta la suma. La figura muestra los 32 bits bajos de su última palabra.
END lleva una más: drr_end.drr_checksum, la suma de comprobación de todo lo
anterior.
El lado receptor comprueba antes de aplicar.
receive_read_payload_and_next_header
Extracto de module/zfs/dmu_recv.c, líneas
2843–2868, OpenZFS zfs-2.4.4.
Ver en 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 El receptor lleva su propia suma: empieza sobre todo el registro BEGIN, continúa sobre cada carga útil, y aquí sobre la cabecera siguiente, hasta su suma.
-
L2861–L2866
Una cabecera cuyo
drr_checksumno coincide detiene la recepción:ECKSUM. Una suma de comprobación formada solo por ceros no se comprueba. - L2868 Después se suma el valor almacenado, como hizo el emisor.
Esto se ejecuta cuando se han leído la carga útil de un registro y la cabecera que lo sigue, y solo entonces el
registro se pone en la cola del escritor: un registro cuyos bytes se dañaron nunca se
aplica. END se comprueba una vez más, su drr_end.drr_checksum contra la suma
anterior a él. Ante un error, lo que la recepción creó se destruye: el receptor
sigue teniendo la instantánea de origen. Antes de todo esto, el BEGIN de un flujo incremental
nombra la instantánea de origen por su GUID, y el receptor lo rechaza
(ENODEV) a menos que tenga una instantánea con ese GUID.
Registros despachados por tipo. El hilo escritor saca cada registro de la cola y lo entrega aquí.
receive_process_record
Extracto de module/zfs/dmu_recv.c, líneas
3188–3213, OpenZFS zfs-2.4.4.
Ver en 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 a
receive_object, que crea el objeto, o conserva uno que ya existe. -
L3197–L3203
FREEOBJECTS va a
receive_freeobjects, que libera cada objeto del intervalo que existe: c desaparece. -
L3204–L3206
WRITE va a
receive_process_write_record. FREE, más abajo, va areceive_free, que libera el intervalo en el objeto.
Con qué termina el receptor. Partiendo de los archivos de la instantánea de origen, los
registros del flujo devuelven exactamente @s4: la figura compara los dos al final, para cada
instantánea de origen, y sus pruebas lo contrastan con los registros calculados a mano de cada flujo.
Lo que la figura deja fuera
La figura es pequeña: archivos de cuatro bloques de 512 bytes bajo un solo bloque indirecto, bloques de dnodes de
cuatro dnodes (un bloque de dnodes real de 16 KiB contiene 32), un meta-dnode dibujado con sus dos bloques de
dnodes, sin búfer bonus. El envío se ejecuta como una sola secuencia, mientras que el código une varios hilos
(recorrido, fusión, lector, el hilo principal) mediante colas que conservan el orden, y el receptor empieza una vez
completo el flujo, mientras que el código recibe a través de una tubería a medida que se escribe el flujo. Las
cabeceras de los registros siguen la estructura de dmu_replay_record_t, con solo los campos que usa la
figura rellenados. No se muestran los flujos redactados, raw (cifrados), comprimidos y reanudables, los datos
integrados, WRITE_BYREF ni los bloques spill; tampoco cifras de rendimiento, que dependen de los discos y de la red,
no de este código.