Copia de particiones aisladas con "dd", sobre una imagen de disco

Estoy trabajando en un proyecto sobre Raspberry PI que requiere regenerar de vez en cuando la tarjeta de memoria SD, con el fin de ser clonada a otros dispositivos. En la documentación del proyecto se detallan todos los pasos con sumo cuidado, para que puedan llevarse a cabo por personal no especializado.

Pero un paso crucial es disponer de acceso físico a la tarjeta de memoria SD de la Raspberry PI, y parar el servicio durante algo más de una hora, algo inconveniente en esta fase del proyecto. Hay momentos en los que incluso no es posible. ¿Podemos hacerlo de otra manera?.

Sí, hay otra forma. Pero primero veamos una peculiaridad este proyecto:

root@Heimdallr:~# fdisk -l /dev/mmcblk0

Disk /dev/mmcblk0: 8035 MB, 8035237888 bytes
4 heads, 16 sectors/track, 245216 cylinders, total 15693824 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00047c7a

        Device Boot      Start         End      Blocks   Id  System
/dev/mmcblk0p1            8192      122879       57344    c  W95 FAT32 (LBA)
/dev/mmcblk0p2          122880     6414335     3145728   83  Linux
/dev/mmcblk0p3         6414336    12705791     3145728   83  Linux
/dev/mmcblk0p4        12705792    14811929     1053069    f  W95 Ext'd (LBA)
/dev/mmcblk0p5        12705800    14802943     1048572   83  Linux

La tarjeta consta de 5 particiones. La primera partición contiene el sistema de arranque, lo que es el boot de un Linux. Las siguientes dos particiones, de 3 GB exactos, contienen el sistema operativo, aplicaciones y configuraciones generales, que son iguales para todos los dispositivos.

¿Por qué dos particiones de sistema?. Para poder actualizar el sistema operativo de forma remota y segura (ante cortes de comunicaciones, fallos eléctricos, cuelgues, etc), solo se usa una de las particiones en un momento dado. Cuando hay una actualización, ésta se almacena en la otra partición. Una vez que nos hemos asegurado de que la actualización está completa y es correcta, entonces reiniciamos el dispositivo indicando que la partición de arranque es la nueva, actualizada. De esta forma las particiones se van alternando a medida que van entrando actualizaciones.

Por último, la cuarta partición primaria sólo sirve para contener a su vez la quinta partición, esta vez lógica. Esta partición contiene todas las configuraciones personales exclusivas de ese dispositivo.

La creación inicial de la imagen SD consta de una serie de pasos simples pero numerosos, ya que el proyecto requiere tener en cuenta muchos detalles que no son relevantes para este post. Lo importante es que, al final de todo el procedimiento, tenemos lo siguiente:

  • Una imagen física de la tarjeta, lista para ser grabada en una nueva tarjeta de memoria SD.
  • Una imagen de exactamente 3GB de tamaño, conteniendo un volcado de la partición de sistema operativo, aplicaciones y configuraciones más actualizada, precedida por 16793600 (1025*16384) bytes de un árbol Merkle.

En actualizaciones futuras, mientras no se cambie el particionado de la imagen SD, solo necesitamos actualizar la partición de arranque y la partición del sistema. Es decir, no necesitamos recrear la imagen desde cero. Podemos aprovecharnos de la versión anterior:

  • El primer paso consiste en realizar un volcado de la partición de boot. Desde la Raspberry PI hacemos:

    $ sudo bash
    $ dd if=/dev/zero of=/boot/z-fill bs=65536
    dd: writing '/boot/z-fill': No space left on device
    598+0 records in
    597+0 records out
    39149568 bytes (39 MB) copied, 0.720115 s, 54.4 MB/s
    $ sync
    $ rm /boot/z-fill
    $ umount /boot
    $ fsck /boot
    fsck from util-linux 2.20.1
    dosfsck 3.0.13, 30 Jun 2012, FAT32, LFN
    /dev/mmcblk0p1: 13 files, 9490/28607 clusters
    $ dd if=/dev/mmcblk0p1 of=/tmp/boot.bin bs=65536
    896+0 records in
    896+0 records out
    58720256 bytes (59 MB) copied, 16.0051 s, 3.7 MB/s
    $ exit
    

    La velocidad de grabación indicada no es real. La tarjeta es mucho más lenta. Lo que se ve es la velocidad de grabación en memoria. Por eso hago un sync posterior, para asegurarme de que los datos están en la tarjeta de memoria antes de borrarlos.

    (Estoy llenando el espacio libre de la partición con ceros por si acaso se filtrase algún dato confidencial en los ficheros borrados con anterioridad)

  • Copiamos la imagen del boot en el lugar donde tengamos la imagen previa de la tarjeta SD.

  • Allí sobreescribimos el boot de la imagen de la tarjeta con la nueva versión. Si nos fijamos en la tabla de particiones que puse antes, la primera partición empieza en el offset 8192, con sectores de 512 bytes. Por tanto, hacemos:

    $ dd if=boot.bin of=20140325-total-dd_parcial.bin bs=512 seek=8192 conv=notrunc
    114688+0 records in
    114688+0 records out
    58720256 bytes (59 MB) copied, 1.27489 s, 46.1 MB/s
    

    Obsérvese el uso que hacemos de conv=notrunc para poder grabar en mitad de la imagen sin que el fichero se trunque.

  • A continuación grabamos los 3GB de la imagen de la partición de sistema, aplicaciones y configuraciones. Nos saltamos el árbol Merkle que tiene al principio:

    $ dd if=update.bin of=20140325-total-dd_parcial.bin bs=16384 seek=3840 skip=1025 conv=notrunc
    197794+0 records in
    197794+0 records out
    3240656896 bytes (3.2 GB) copied, 35.6389 s, 90.9 MB/s
    

    Aquí trabajamos con bloques de 16KB por rendimiento. Con bloques de 512 bytes la grabación es tres veces más lenta. Por supuesto, hay que ajustar seek y skip apropiadamente.

    skip nos indica cuántos bloques (en este caso, de 16Kbytes) hay que saltarse del fichero de entrada, mientras que seek, como antes, indica el punto de sobreescritura de la imagen.

    La partición que nos interesa empieza en el sector 122880 con sectores de 512 bytes, que es 3840 cuando los sectores son de 16384 bytes (16Kbytes).

Ya está. Tenemos una imagen SD nueva lista para ser distribuída. No tocamos la tabla de particiones ni el contenido del resto de la tarjeta.

Rápido. Simple. Sin parar el servicio.

Por supuesto, toda la gracia está en tener por ahí la imagen de 3 Gigabytes de la partición a actualizar. Esto, su árbol Merkle, y cómo y por qué tenemos esa información por ahí es el meollo del proyecto, que no puedo detallar aquí.

Hay casos en los que no es preciso actualizar el volcado de boot. Esto puede saberse calculando el hash de ambos volcados o bien, en mi proyecto, examinando los metadatos del sistema de actualización remota. Otro meollo que no puedo explicar.