Empilement de périphériques en mode bloc

La démo est modifiée pour vous permettre de la rejouer sur votre machine en tant que root, sans utiliser de disque physique externe, clef USB, carte sd ou autre, uniquement des périphérique boucle.

Si vous utilisez WSL sous windows, il faut passer à WSL2 (ou mieux : profitez de l'occasion pour passer à virtualbox).

On part de deux périphériques loop qu'on va découper, coller, chiffrer, etc

On se place dans le répertoire temporaire /tmp :

# cd /tmp

On crée un fichier /tmp/disque1 rempli de zéros de taille 100 MiB. Pour cela, on utilise le fichier spécial /dev/zero qui est un périphérique caractère qui génère une infinité de zéros. On utilise la commande dd car il faut bien s'arrêter à un moment.

# dd if=/dev/zero of=disque1 bs=1MiB count=100
100+0 enregistrements lus
100+0 enregistrements écrits
104857600 octets (105 MB, 100 MiB) copiés, 0,0612908 s, 1,7 GB/s

Remarque : dans la démo originale, on avait d'abord testé :

# cp /dev/zero disque1

et on s'était rendu compte que le système de fichiers sur lequel était monté /tmp s'était rempli très vite, avec un fichier disque1 qui a pris toute la place avant de créer une erreur. Ne le faites pas sur les machines de TP (ou alors allez dans /dev/shm et n'oubliez pas d'effacer le fichier ensuite).

On peut voir que le fichier est plein de zéros :

# hexdump disque1 
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
6400000

On fait pareil avec le fichier /tmp/disque2, ou alors on recopie le contenu de disque1 dans disque2 :

# cp disque1 disque2

Ces fichiers sont des fichiers réguliers, on peut les promouvoir en périphériques en mode bloc :

# losetup -f disque1

On regarde quel périphérique boucle est associé à disque1 :

# losetup --associated disque1
/dev/loop0: [0031]:3565662 (/tmp/disque1)

On fait pareil pour disque2 :

# losetup -f disque2

On liste les périphériques boucles existants :

# losetup --list
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE    DIO LOG-SEC
/dev/loop1         0      0         0  0 /tmp/disque2   0     512
/dev/loop0         0      0         0  0 /tmp/disque1   0     512

On peut voir que deux périphériques bloc sont apparus :

# lsblk
NAME           MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
...
loop0            7:0    0   100M  0 loop  
loop1            7:1    0   100M  0 loop  

On crée une table de partitions de type msdos sur /dev/loop0 (man parted) :

# parted /dev/loop0 mklabel msdos
Information: You may need to update /etc/fstab.

On peut voir qu'une en-tête (en: header) a été écrite au début du fichier :

# hexdump disque1
0000000 b8fa 1000 d08e 00bc b8b0 0000 d88e c08e
0000010 befb 7c00 00bf b906 0200 a4f3 21ea 0006
0000020 be00 07be 0438 0b75 c683 8110 fefe 7507
0000030 ebf3 b416 b002 bb01 7c00 80b2 748a 8b01
0000040 024c 13cd 00ea 007c eb00 00fe 0000 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
*
00001b0 0000 0000 0000 0000 e571 5a09 0000 0000
00001c0 0000 0000 0000 0000 0000 0000 0000 0000
*
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200 0000 0000 0000 0000 0000 0000 0000 0000
*
6400000

On crée deux partitions primaires de taille 50 MiB :

# parted /dev/loop0 mkpart primary 0 50MiB
Warning: The resulting partition is not properly aligned for best performance.
Ignore/Cancel? C

Il est pas content, on annule (C) et on re-éssaye :

# parted /dev/loop0 mkpart primary 1MiB 50MiB
Information: You may need to update /etc/fstab.

# parted /dev/loop0 mkpart primary 50MiB 100%
Information: You may need to update /etc/fstab.

On liste ("ls") les périphériques en mode bloc ("blk") pour voir ou on en est :

# lsblk
NAME           MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
...
loop0            7:0    0   100M  0 loop
├─loop0p1      259:0    0    49M  0 part
└─loop0p2      259:1    0    50M  0 part
loop1          7:1      0   100M  0 loop

On peut aussi remarquer que le fait d'avoir créé deux partitions, n'a fait que modifier la table de partitions, mais par exemple, rien n'est écrit au milieu du disque (à la frontière entre les partitions) :

# hexdump disque1
0000000 b8fa 1000 d08e 00bc b8b0 0000 d88e c08e
0000010 befb 7c00 00bf b906 0200 a4f3 21ea 0006
0000020 be00 07be 0438 0b75 c683 8110 fefe 7507
0000030 ebf3 b416 b002 bb01 7c00 80b2 748a 8b01
0000040 024c 13cd 00ea 007c eb00 00fe 0000 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
*
00001b0 0000 0000 0000 0000 e571 5a09 0000 2000
00001c0 0021 5f83 0619 0800 0000 8800 0001 5f00
00001d0 061a be83 0c32 9000 0001 9000 0001 0000
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200 0000 0000 0000 0000 0000 0000 0000 0000
*
6400000

Bien sur, c'est le même contenu que /dev/loop0 :

# hexdump /dev/loop0
0000000 b8fa 1000 d08e 00bc b8b0 0000 d88e c08e
0000010 befb 7c00 00bf b906 0200 a4f3 21ea 0006
0000020 be00 07be 0438 0b75 c683 8110 fefe 7507
0000030 ebf3 b416 b002 bb01 7c00 80b2 748a 8b01
0000040 024c 13cd 00ea 007c eb00 00fe 0000 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
*
00001b0 0000 0000 0000 0000 e571 5a09 0000 2000
00001c0 0021 5f83 0619 0800 0000 8800 0001 5f00
00001d0 061a be83 0c32 9000 0001 9000 0001 0000
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200 0000 0000 0000 0000 0000 0000 0000 0000
*
6400000

On peut voir que l'espace occupé par les partitions n'a que des zéros :

# hexdump /dev/loop0p1
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
3100000

On va utiliser LVM pour recoller la première partition de /dev/loop0 (50 MiB) et /dev/loop1 (100 MiB), redécouper l'ensemble en deux périphériques de tailles 70 MiB chacun (voir le schéma des commandes LVM dans les slides du cours) :

On transforme les deux périphériques en volumes physiques LVM :

# pvcreate /dev/loop0p1
  Physical volume "/dev/loop0p1" successfully created.

On peut observer que ça a écrit des choses au début de la partition (notez les indications) :

# hexdump -C /dev/loop0p1 
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000200  4c 41 42 45 4c 4f 4e 45  01 00 00 00 00 00 00 00  |LABELONE........|
00000210  12 2c 0d 55 20 00 00 00  4c 56 4d 32 20 30 30 31  |.,.U ...LVM2 001|
00000220  33 4b 34 39 62 6a 37 63  32 30 75 31 45 65 76 39  |3K49bj7c20u1Eev9|
00000230  32 42 58 62 37 4c 46 39  4b 67 72 73 61 48 42 52  |2BXb7LF9KgrsaHBR|
00000240  00 00 10 03 00 00 00 00  00 00 10 00 00 00 00 00  |................|
00000250  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000260  00 00 00 00 00 00 00 00  00 10 00 00 00 00 00 00  |................|
00000270  00 f0 0f 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000280  00 00 00 00 00 00 00 00  02 00 00 00 00 00 00 00  |................|
00000290  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000  16 d6 8e db 20 4c 56 4d  32 20 78 5b 35 41 25 72  |.... LVM2 x[5A%r|
00001010  30 4e 2a 3e 01 00 00 00  00 10 00 00 00 00 00 00  |0N*>............|
00001020  00 f0 0f 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00001030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
03100000

On fait pareil pour /dev/loop1 :

# pvcreate /dev/loop1
  Physical volume "/dev/loop1" successfully created.

On les fusionne en un volume group LVM nommé vg_disque :

# vgcreate vg_disque /dev/loop0p1 /dev/loop1
  Volume group "vg_disque" successfully created

Si vous refaites un hexdump -C sur les deux périphériques bloc, vous verrez les indications sur le volume group (exercice).

On découpe le volume groupe LVM en deux volumes logiques de 70 MiB :

# lvcreate -L 70MiB vg_disque
  Rounding up size to full physical extent 72,00 MiB
  Logical volume "lvol0" created.

# lvcreate -L 80MiB vg_disque
  Volume group "vg_disque" has insufficient free space (18 extents): 20 required.

# lvcreate -L 70MiB vg_disque
  Rounding up size to full physical extent 72,00 MiB
  Logical volume "lvol1" created.

On jette un oeil, les deux volumes logiques sont bien des périphériques en mode bloc :

# lsblk
NAME                MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
...
loop0                 7:0    0   100M  0 loop
├─loop0p1           259:0    0    49M  0 part
│ └─vg_disque-lvol1 253:2    0    72M  0 lvm
└─loop0p2           259:1    0    50M  0 part
loop1                 7:1    0   100M  0 loop
├─vg_disque-lvol0   253:1    0    72M  0 lvm
└─vg_disque-lvol1   253:2    0    72M  0 lvm

On remarque que bien que lsblk nous montre la situation comme s'il s'agissait d'arbres, en fait vg_disque-lvol1 apparaît 2 fois, car ce périphérique bloc est à cheval entre les périphériques loop0p1 et loop1. Comme on l'a discuté en cours, la relation entre les périphériques en mode bloc est plutôt un DAG (directed acyclic graph, structure représentant une relation de dépendance).

Notez aussi que les volumes physiques LVM et le volume group LVM ne sont pas des périphériques bloc, mais des étapes intérmédiaires spécifiques à LVM (ils n'apparaîssent pas dans la sortie de la commande lsblk).

On chiffre le premier volume logique :

# cryptsetup luksFormat /dev/mapper/vg_disque-lvol0

WARNING!
========
Cette action écrasera définitivement les données sur /dev/mapper/vg_disque-lvol0.

Are you sure? (Type uppercase yes): YES
Saisissez la phrase secrète pour /dev/mapper/vg_disque-lvol0 :
Vérifiez la phrase secrète :

Remarque, si on fait un :

# hexdump -C /dev/mapper/vg_disque-lvol0 

on observe qu'un énorme header a été ajouté à la suite du header LVM : il commence par "SKUL" (qui est le renversé de "LUKS" : Linux Unified Key Setup), c'est le header de la nouvelle "couche de chiffrement" correspondant au prériphérique bloc chiffré. Ce header contient de nombreux octets apparement aléatoires, car il contient la clef de chiffrement du périphérique chiffré, cette clef est elle-même chiffrée avec la phrase de passe que nous venons d'entrer.

Après le chiffrement de ce périphérique en mode bloc, on ne voit pas de périphérique en mode bloc apparaître :

# lsblk
NAME                MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
loop0                 7:0    0   100M  0 loop
├─loop0p1           259:0    0    49M  0 part
│ └─vg_disque-lvol1 253:2    0    72M  0 lvm
└─loop0p2           259:1    0    50M  0 part
loop1                 7:1    0   100M  0 loop
├─vg_disque-lvol0   253:1    0    72M  0 lvm
└─vg_disque-lvol1   253:2    0    72M  0 lvm

C'est normal car, étant chiffré, on a un truc illisible, il faut d'abord permettre au système de le déchiffrer (en lui donnant la phrase de passe) pour que le système puisse le manipuler :

# cryptsetup open /dev/mapper/vg_disque-lvol0 crypt_disque
Saisissez la phrase secrète pour /dev/mapper/vg_disque-lvol0 :

On regarde qu'un périphérique en mode bloc est bien apparu :

# lsblk
NAME                MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
loop0                 7:0    0   100M  0 loop
├─loop0p1           259:0    0    49M  0 part
│ └─vg_disque-lvol1 253:2    0    72M  0 lvm
└─loop0p2           259:1    0    50M  0 part
loop1                 7:1    0   100M  0 loop
├─vg_disque-lvol0   253:1    0    72M  0 lvm
│ └─crypt_disque    253:3    0    56M  0 crypt
└─vg_disque-lvol1   253:2    0    72M  0 lvm

Remarque : les périphériques bloc de type LVM2 et LUKS sont numérotés /dev/dm-<n> mais sont accessibles par leur petit nom dans /dev/mapper/ via un lien symbolique :

# ls -l /dev/mapper/
total 0
crw------- 1 root root 10, 236 mars   2 15:05 control
lrwxrwxrwx 1 root root       7 mars   3 00:19 crypt_disque -> ../dm-2
lrwxrwxrwx 1 root root       7 mars   3 00:19 vg_disque-lvol0 -> ../dm-0
lrwxrwxrwx 1 root root       7 mars   3 00:22 vg_disque-lvol1 -> ../dm-1

À partir de là, on peut installer des systèmes de fichiers dans les périphériques en mode bloc qu'on a obtenu, au hasard ext4, xfs, vfat :

# mkfs.ext4 /dev/mapper/crypt_disque
mke2fs 1.44.5 (15-Dec-2018)
Creating filesystem with 57344 1k blocks and 14336 inodes
Filesystem UUID: 36386848-5f90-4ab7-b7d8-ad1be16bfcbb
Superblock backups stored on blocks:
    8193, 24577, 40961

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done


# mkfs.xfs /dev/mapper/vg_disque-lvol1
meta-data=/dev/mapper/vg_disque-lvol1 isize=512    agcount=4, agsize=4608 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=0
data     =                       bsize=4096   blocks=18432, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=855, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0

# mkfs.vfat /dev/loop0p2
mkfs.fat 4.1 (2017-01-24)

On peut monter ces systèmes de fichier sur des répertoires :

# mkdir m1 m2 m3

# mount /dev/mapper/crypt_disque m1
# mount /dev/mapper/vg_disque-lvol1 m2
# mount /dev/loop0p2 m3

# mount
[...]
/dev/mapper/crypt_disque on /tmp/m1 type ext4 (rw,relatime)
/dev/mapper/vg_disque-lvol1 on /tmp/m2 type xfs (rw,relatime,attr2,inode64,noquota)
/dev/loop0p2 on /tmp/m3 type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro)

Par contre, toutes ces encapsulations et systèmes de fichiers prennent de la place (tables des inoeuds, etc), de sorte qu'on a perdu par rapport aux périphériques en mode bloc initiaux :

# df -h
Sys. de fichiers            Taille Utilisé Dispo Uti% Monté sur
[...]
/dev/mapper/crypt_disque       51M    1,1M   46M   3% /tmp/m1
/dev/mapper/vg_disque-lvol1    69M    4,0M   65M   6% /tmp/m2
/dev/loop0p2                   50M       0   50M   0% /tmp/m3

On a bien un truc qui marche :

# echo hop > m1/plop
# ls m1/
lost+found  plop

Remarque : on peut obtenir des infos sur les périphériques bloc et les systèmes de fichiers avec d'autres outils :

On peut lister les périphériques bloc avec "fdisk -l" :

# fdisk -l
...
...
...

Disque /dev/loop0 : 100 MiB, 104857600 octets, 204800 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets
Type d'étiquette de disque : dos
Identifiant de disque : 0xf2c230bd

Périphérique Amorçage  Début    Fin Secteurs Taille Id Type
/dev/loop0p1            2048 102399   100352    49M 83 Linux
/dev/loop0p2          102400 204799   102400    50M 83 Linux


Disque /dev/loop1 : 100 MiB, 104857600 octets, 204800 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets


Disque /dev/mapper/vg_disque-lvol0 : 72 MiB, 75497472 octets, 147456 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets


Disque /dev/mapper/vg_disque-lvol1 : 72 MiB, 75497472 octets, 147456 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets


Disque /dev/mapper/crypt_disque : 56 MiB, 58720256 octets, 114688 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets

On peut directement regarder le fichier /proc/partitions :

# cat /proc/partitions
major minor  #blocks  name
...
...
...
   7        0     102400 loop0
 259        0      50176 loop0p1
 259        1      51200 loop0p2
   7        1     102400 loop1
 253        1      73728 dm-0
 253        2      73728 dm-1
 253        3      57344 dm-2

On peut regarder les systèmes de fichiers montés avec findmnt, qui est plus sympa à lire que ce que retourne mount :

# findmnt
TARGET                                SOURCE                      FSTYPE      OPTIONS
/                                     ......                      ......      .......
├─/...
├─/...
├─/...
└─/tmp                                tmpfs                       tmpfs       rw,relatime,size=8388608k
  ├─/tmp/m1                           /dev/mapper/crypt_disque    ext4        rw,relatime
  ├─/tmp/m2                           /dev/mapper/vg_disque-lvol1 xfs         rw,relatime,attr2,inode64,noquota
  └─/tmp/m3                           /dev/loop0p2                vfat        rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro

Si on veut tout ranger avant de quitter, on remonte le temps :

# umount m1 m2 m3

# cryptsetup close crypt_disque

# vgremove vg_disque
Do you really want to remove volume group "vg_disque" containing 2 logical volumes? [y/n]: y
Do you really want to remove active logical volume vg_disque/lvol0? [y/n]: y
  Logical volume "lvol0" successfully removed
Do you really want to remove active logical volume vg_disque/lvol1? [y/n]: y
  Logical volume "lvol1" successfully removed
  Volume group "vg_disque" successfully removed

# losetup -d /dev/loop0 /dev/loop1

Exercice : faites un dessin de la situation, avec les empilements de périphériques en mode bloc, les systèmes de fichiers, et leurs montages dans l'arborescence.