quarta-feira, 15 de junho de 2016

Assinaturas

Quando formatamos um dispositivo de armazenamento, colocamos um sistema arquivos numa região delimitada. Às ferramentas que os criam, precisamos dizer qual área compreenderá essa região. Tradicionalmente, especificamos um dispositivo de bloco como /dev/sdc1.

Imagine um disco de 2 MiB (2097152 bytes) com setores de 512 bytes (4096 setores), particionamento MBR (MS-DOS) e uma única partição primária.

    Disco
    /dev/sdc
    +----------------------------------------------------------------------------+
    |                   Espaço não                                               |
    |                   particionado                                             |
    |   +--------------+  /      \  +----------------------------------------+   |
    |   | MBR, tabela  |            | Partição 1                             |   |
    |   | de partições |            | /dev/sdc1                              |   |
    |   |              |            |                                        |   |
    |   +--------------+            +----------------------------------------+   |
LBA |   0              1           / 2048                               4095 /   |
    |                             /                                         /    |
    +----------------------------/-----------------------------------------/-----+
                                /                                         /
                                +----------------------------------------+
                                |                                        |
                                |                                        |
                                |                                        |
                                +----------------------------------------+
                                0                                      2047

(fora de escala)

O kernel interpreta a tabela de partições do disco /dev/sdc e cria o dispositivo /dev/sdc1 [1], que corresponde à área entre os setores 2048 e 4095 neste exemplo. Quando um programa acessar o setor 0 de /dev/sdc1, estará na verdade acessando o setor 2048 do disco. Da mesma forma, o último setor de /dev/sdc1 (2047) equivale ao setor 4095 do disco. Essa tradução é feita automaticamente pelo kernel.

Portanto, ao rodarmos mkfs.ext4 /dev/sdc1, dizemos ao programa para criar um sistema de arquivos EXT4 entre os setores 2048 e 4095 do disco. O resto fica intocado.

Cada sistema de arquivos possui alguns bytes em posições específicas (assinaturas) que os identificam. À medida que sobre uma mesma área criamos diferentes sistemas de arquivos, é prudente, a cada formatação, apagar assinaturas anteriores eventualmente presentes. Ter mais de uma assinatura numa mesma área é roleta russa, pois o volume pode, se por azar o offset conferir, ser montado com o driver de outro sistema de arquivos (colisão), que por sua vez pode considerá-lo inconsistente e na pior das hipóteses tentar consertá-lo sobrescrevendo setores e causando perda de dados no sistema arquivos verdadeiro. Idem com as ferramentas de verificação (fsck).

Na libblkid, parte da suíte util-linux, há funções de detecção e apagamento de assinaturas, e um extenso banco de dados contendo várias delas, que podem ser usadas por outros programas. O wipefs, da própria suíte, é um consumidor dessas interfaces. Idealmente, todos os mkfs deveriam linkar a libblkid e usá-la com essa finalidade, mas nem todos o fazem.

Áreas não particionadas podem ter qualquer coisa gravada. E ferramentas de particionamento que não automatizem a criação de sistemas de arquivos em partições recém criadas também não importam-se com o que está presente entre início e fim das mesmas. Sua obrigação é entregar a estrutura que as delimita. Consequentemente, é recomendável, antes de criar o sistema de arquivos, rodar wipefs -af <partição>. Talvez o mkfs em questão faça isso por nós, porém não custa prevenir.

O mesmo aplica-se ao disco em si, pois é perfeitamente possível criar um sistema de arquivos diretamente em /dev/sdc no nosso exemplo. Vamos supor que /dev/sdc nunca tenha sido particionado e tenha um sistema de arquivos EXT4 em toda sua área (entre os setores 0 e 4095). Ao decidir particioná-lo em MBR, não basta apenas criar a tabela de partições no setor 0. Precisamos garantir que a assinatura EXT4 anterior, gravada no terceiro setor (o superbloco dos EXT começa a partir de 1 KiB [2] — estaria em LBA 2), seja apagada. Ou seja, apagar assinaturas considerando a área total do dispositivo, entre LBA 0 e 4095 no esquema acima. Abstraia por um momento que o disco inteiro passou a ser uma partição de outro disco imaginário. A libblkid é capaz de apagar não só assinaturas de sistema de arquivos: as que identificam particionamento também desaparecerão.

Minha recomendação é fazer assim ao começar um particionamento do zero num disco usado anteriormente [3]:

          wipefs -a <dispositivo>
                    |
                    v
               particionar
                    |
                    v
        wipefs -af /dev/<partição 1>
        wipefs -af /dev/<partição 2>
                   etc.
                    |
                    v
         criar sistema de arquivos

As ferramentas fdisk e sfdisk ganharam na versão 2.28 da suíte util-linux habilidade de limpar automaticamente as assinaturas do disco (opção --wipe):

fdisk: add --wipe
sfdisk: add --wipe

Assim a primeira invocação de wipefs torna-se desnecessária.

Na versão 2.29, ainda não lançada enquanto escrevo, poderemos instruí-las a fazer o mesmo nas áreas que englobam partições (opção --wipe-partitions) [4]:

fdisk: add --wipe-partitions=auto|never|default
sfdisk: add --wipe-partitions=auto|never|default

O que nos permite remover a segunda invocação de wipefs.

Já o cfdisk permite apagar assinaturas presentes no disco, porém não faz o mesmo com partições por enquanto:

cfdisk: wipe device if create a new label

Outra alternativa é escrever zeros do início ao fim do disco. Em SSDs é um processo fácil e rápido via secure erase.

[Atualização - 28/09/2016] Adicionada opção -f ao wipefs, pois do contrário tabelas de partições dentro de partições não são apagadas.
[Atualização - 30/09/2016] wipefs no disco inteiro sem -f, assim o kernel é informado se houver mudança na tabela de partições.


[1] Antigamente, era tarefa do udevd. Hoje, o daemon apenas ajusta permissões e cria links simbólicos de conveniência. O kernel é responsável por manter os nós de dispositivos no diretório /dev (devtmpfs).

[2] A posição em relação ao início (e ao fim em alguns casos) do dispositivo varia de acordo com o sistema de arquivos, volume RAID, etc. Identificá-la fica por conta da libblkid. O Btrfs, por exemplo, tem até três superblocos (64 KiB, 64 MiB e 256 GiB), cada um com sua assinatura.

[3] O GParted tem tudo isso automatizado. Infelizmente, por precisar manter-se compatível com distribuições obsoletas, que carregam pacotes util-linux ultrapassados demais, precisa reinventar a roda implementando as rotinas de apagamento por conta, sem usar a libblkid.

[4] Petr Uzel da SUSE implementou funcionalidade equivalente no Parted. Patch ainda não tornado upstream, provavelmente porque depende de uma recente correção na libblkid.

Nenhum comentário:

Postar um comentário