Skip to content

Linux Attitude

Le libre est un état d'esprit

Archive

Tag: Disque

Niveau : Star Star Empty Empty Empty
Résumé : table des partitions

Un disque dur est en général divisé en partitions. Je dis en général, car ce n'est pas obligatoire, une disquette ne l'est quasiment jamais.

Sur un PC, la table est en général au format DOS (nommé aussi MBR), je dis en général car ce n'est pas le seul format possible, même si c'est le plus courant.

Nous allons donc se restreindre au cas d'un disque dur partitionné au format DOS.

Premier secteur

Les informations de partitionnement se situent sur le premier secteur (512 octets) d'un disque. Mais il n'y a pas que ça. On y trouve des nombres magiques, et du code pour démarre la machine. La table des partitions n'est constituée que de 64 octets (de 446 à 510). On appelle ce secteur le master boot record (MBR).

mbr.png

Pour lire les données de votre MBR :

$ dd if=/dev/hda of=/tmp/mbr bs=512 count=1
$ xxd /tmp/mbr

La table contient 4 entrées de 16 octets, qui forment les fameuses 4 partitions primaires, limite originelle du nombre de partitions.

mbrtable.png

Chaque entrée décrit le début, la longueur et quelques attributs d'une partition. Historiquement, on utilisait la notion de cylindre, tête et secteur mais cette notation est obsolète, inefficace en terme de place et ne correspond plus à la réalité. On utilise donc un comptage sur 4 octets du nombre de secteurs (512 octets) sur le disque, qu'on appelle LBA. Comptez, cela fait 2To maximum, vous aurez peut-être un problème de partitionnement avec votre prochain disque, il faudra passer à un format un peu plus moderne ... patientez pour un prochain article.

Maintenant, je suis sûr que vous voulez savoir à quoi ressemble votre table :

# Contenu quasiment brut
$ sfdisk -d /dev/hda
# le même un peu traduit
$ sfdisk -l -uS /dev/hda

Et là vous vous dites : mais il y a plus que 4 partitions !

Les partitions logiques et étendues

Hé oui, des gens ont voulu pouvoir avoir bien plus que 4 partitions, c'est comme ça qu'on a inventé les partitions logiques. Question vocabulaire, une partition étendue est une partition primaire qui va contenir d'autres partitions qu'on appelle les partitions logiques.

Donc si vous lisez bien votre table, il est probable qu'elle contienne un hda2 (étendue, non visible sous linux) englobant la totalité du disque à l'exception de la première partition. Les partitions hda5 et hda6 (logiques) sont alors dans cette dernière.

Le format de partition étendue est un peu consommateur (512 octets) puisqu'on réécrit un nouveau secteur pour chaque partition au même format que le MBR. On n'y met cette fois que la partition logique et la description de la prochaine partition (ce qui forme une liste chaînée). Cela veut dire qu'on peut avoir autant de partitions étendues qu'on veut (par défaut linux n'en peut gérer que 64).

Espace perdu

Comme dit un peu plus haut, historiquement on utilisait la notation C/H/S (cylinder, head, sector) pour décrire les positions des partitions. Cette notation impliquait qu'on mette la première partition dans la 2e piste puisque le MBR se trouvait dans la première. On a gardé cette convention même si elle n'a rien d'obligatoire. Ce qui laisse de l'espace libre dans la première piste.

Une piste est constituée des secteurs d'un cylindre sur une tête de lecture. Ce qui nous donne :

  • Taille Secteur = 512 (défaut sur quasiment tous les disques)
  • Taille Piste = 512 * nombre de secteurs par piste (S)
  • Taille Cylindre = 512 * nombre de secteurs par piste (S) * nombre de têtes (H)
  • Taille du disque = 512 * nombre de secteurs par piste (S) * nombre de têtes (H) * nombre de cylindre (C)

Elle a en général une taille de 512*63 soit 32256 octets inutilisés sur un disque (et autant pour chaque partition logique si vous avez bien suivi). C'est ici que certains outils anti-piratage mettaient leurs données à l'époque où windows existait encore. Maintenant un bootloader comme grub va profiter de cet espace pour s'y loger et éviter d'être dérangé par les systèmes.

Grub premier du nom va y mettre son stage1.5 et grub2 est capable d'y rentrer en entier.

Niveau : Star Star Star Star Empty
Résumé : lilo ; grub

Même si la plupart des distributions sont passées à grub, on s'est longtemps posé la question d'utiliser lilo ou grub. Il servent tous les deux à charger linux (le noyau), voyons comment ça marche.

Bios

Avant de nous intéresser au bootloader, étudions le processus de démarrage d'une machine (x86 type intel).

  1. Appui sur le bouton
  2. Allumage de l'alimentation, électricité dans la carte mère
  3. Allumage du processeur dans un état connu
  4. Passage du contrôle au bios
  5. Lecture d'un disque, chargement du secteur de boot nommé MBR (généralement un bootloader)
  6. Chargement du noyau et d'éventuels modules, passage du contrôle au noyau
  7. Montage d'un système de fichier et passage du contrôle au processus d'init (sur les unix)

Bon, le début, vous connaissez.

L'état connu du processeur c'est : mode réel, 16bits, exécution pointant sur les derniers octets du premier Mo (FFFF:0000). Ici se trouve mappé statiquement le bios qui va se lancer et initialiser le matériel qu'il connaît. Il laisse disponible quelques fonctions via des interruptions, par exemple pour accéder à la carte vidéo ou au disque dur. Et il termine en chargeant le premier secteur (512 octets) du premier périphérique (disque, dur, clé usb ...) bootable qu'il trouve et lui passe la main (à l'adresse 0000:7C00).

C'est ici qu'on trouve lilo, grub, syslinux et même parfois linux lui-même sur de vieilles versions. Comme vous le constatez, 512 octets c'est très petit pour coder un bootloader (surtout que les octets ne sont pas tous disponibles à cause de la table des partitions). C'est pourquoi les bootloaders ont tous une architecture un peu bizarre avec plusieurs étapes.

Lilo

Lilo est un bootloader spécifique à linux (LInux LOader). Il fonctionne en 2 temps, un secteur de boot qui va charger un fichier un peu plus gros dont il connaît les secteurs sur le disque. Ce dernier peut éventuellement proposer un menu puis charge le bon noyau et lui passe la main.

Lilo utilise un fichier de configuration /etc/lilo.conf. À chaque modification de ce fichier, il faut lancer la commande lilo. Cette commande va lire le fichier et réécrire le secteur de boot à la racine du disque. Il va aussi écrire un fichier map qui contient la liste des secteurs sur le disque où trouver les étapes suivantes de chargement ainsi qu'une version compilée du fichier de configuration. En pratique cette deuxième étape se trouve à l'intérieur du binaire lilo lui-même (depuis la version 22.x x<4).

lilo.png

Grub

Grub est un bootloader qui se veut générique (la version 2 commence à être utilisable sur d'autres architectures). Il est capable de booter de nombreux formats de noyaux.

Son secteur de boot (stage1) est statique et est installé une fois pour toutes (ou presque). Celui-ci se contente de charger un autre fichier (stage2) qui sait lire les systèmes de fichiers et donc peut lire sa propre configuration directement. Cela lui permet de ne pas nécessiter de manipulation après chaque modification de sa configuration. Il est aussi beaucoup plus flexible puisque cette deuxième étape contient un shell qui permet de choisir soi-même ce qui se passe pendant le boot (pratique pour les récupérations d'urgence).

La limitation de cette technique est qu'il faut encore réinstaller grub dans le secteur de boot lorsque le shell (stage2) a été modifié. Mais on peut faire mieux, il existe la possibilité d'ajouter une étape, le stage1.5, qui sait lire un système de fichier. Ce code est suffisamment petit pour qu'on puisse l'installer dans un espace vide du disque juste derrière la table des partitions. Il est donc dans un endroit qui ne bougera jamais et il saura lire le shell où qu'il soit et aucune réinstallation ne sera plus nécessaire.



Il reste tout de même un cas où il faut le réinstaller : quand on change le type de système de fichier ou la partition qui contient le shell grub.

grub.png

Travaux pratiques

Nous allons nous exercer sur un loopback (un disque virtuel) pour éviter les larmes en cas d'erreur (ayez toujours un liveCD sur vous, on ne sait jamais). Installez aussi qemu, ça vous permettra de tester ce que vous faites (c'est pratique même sur des vrais disque pour ne pas rebooter).

Préparation du disque virtuel pour nos tests :

# 50Mo c'est déjà beaucoup pour un disque :-)
$ dd if=/dev/zero of=/tmp/test bs=1M count=50
# Le disque entier
$ losetup /dev/loop0 /tmp/test
# On le partitionne (une partition, tout l'espace)
$ cfdisk /dev/loop0
# On repère le premier secteur de la partition
$ fdisk -l -u /dev/loop0 # -> start, en général 63
# On transforme cette partition en device 
$ losetup -o $((63*512)) /dev/loop1 /tmp/test
# On crée un système de fichier sur cette partition
$ mke2fs /dev/loop1
# On le monte 
$ mount /dev/loop1 /mnt
# On y place un noyau pour faire joli
$ cp /boot/vmlinuz-* /mnt
# on fait un test avec qemu pour vérifier que ça ne marche pas
$ sync # pour ne pas avoir à démonter la partition
$ qemu /tmp/test

Vous aurez aussi besoin de grub et lilo, mais votre système ne vous permettra pas d'installer les deux en même temps. Alors vous pouvez soit les recompiler, soit lire les récupérer sous forme binaire, soit sauter la partie correspondante :-). Pour les debianistes :

$ aptitude download lilo
$ dpkg -x lilo*.deb ~/tmp/

Lilo

Créons nous un fichier de configuration pour notre noyau (/mnt/etc/lilo.conf) :

# définition de la correspondance bios <-> linux
disk=/dev/loop0
        bios=0x80
        sectors=63
        cylinders=12
        heads=255
        partition=/dev/loop1
                start=63

# où mettre le secteur de boot
boot=/dev/loop0
# disque dur moderne
lba32
# on demande a l'utilisateur son avis
prompt
# avec un menu c'est plus joli
install=menu
# on stocke la liste des secteurs quelque part sur la partition
map=/mnt/lilo/map
# on ne seuvegarde pas le secteur de boot
backup=/dev/null

# une seule entrée dans notre menu
image=/mnt/vmlinuz-2.6.25
        label=Mon beau noyau, roi des os ...

Vous aurez remarqué le premier paragraphe. Il n'est nécessaire que parce que nous travaillons sur des loopbacks. Il s'agit d'expliquer à lilo que lors du boot, le bios (et donc lilo) verra ce qui est actuellement /dev/loop0 comme le premier disque (0x80). On lui indique aussi comment il est partitionné puisqu'il ne peut pas deviner tout seul que /dev/loop1 est sur le disque /dev/loop0. Et enfin on lui indique la géométrie du disque telle que nous l'a donnée la commande fdisk un peu plus haut car un loopback n'a pas de géométrie.

Le reste du fichier est le minimum pour avoir quelque chose de correct qui ne casse pas votre machine et qui propose une entrée de menu. Pour plus de fioritures, vous pouvez regarder la documentation vraiment bien détaillée. Vous pouvez vous amuser à mettre des images, ou même un boot animé, il y a même un boot casse briques.

Et maintenant installons lilo :

# on a défini ce répertoire dans lilo.conf (d'habitude on met /boot)
$ mkdir /mnt/lilo
# on met à jour le bootloader
$ lilo -c /mnt/etc/lilo.conf
# et on teste pour faire joli
$ sync && qemu /tmp/test

Wahoo, c'est beau ! Maintenant, faisons de même avec ...

Grub

Nous pourrions jouer la facilité et lancer grub-install. Mais non, ce n'est pas notre genre. Il faut comprendre comment ça marche avant de tout casser. Tout comme il faut savoir calculer avant de se servir d'une calculatrice.

Commençons par l'indispensable, un fichier de configuration :

# une entrée, c'est déjà bien assez
title Mon Bô noyau, que j'aime ta verdure 
kernel (hd0,0)/vmlinuz-2.6.25

Vous remarquerez deux choses. Tout d'abord grub utilise sa propre notation pour les disques et partitions (hé oui linux ne sera plus là quand il bootera). (hd0) est le premier disque vu par le bios, (hd1) le 2e, etc. De même pour les partitions, (hd0,0) est la première partition du premier disque, (hd0,1) la 2e etc. Ensuite, le chemin est relatif à la partition telle qu'elle sera lue au boot et non pas à l'arborescence actuelle (on ne met pas /mnt/vmlinuz-2.6.25).

Le stage2 (le shell) doit être disponible sur le nouveau disque pour que le boot fonctionne.

$ mkdir /mnt/grub
$ cp /boot/grub/stage2 /mnt/grub/

Créons un fichier device.map pour faire correspondre les loopback aux disques qui seront vus par le bios (même problème que pour lilo) :

(hd0) /dev/loop0
(hd1) /dev/hda

Le hd1 est bien utilecar on va lire des fichiers qui ne sont pas stockés sur le loopback (on pourrait aussi les y copier :-).

Ensuite lançons un shell grub pour faire l'installation :

$ grub --device-map=/tmp/device.map
# on met un stage1.5 pour être complet
# il se trouve sur /dev/hda1 dans /boot/grub et on le met sur /dev/loop0 
$ embed (hd1,0)/boot/grub/e2fs_stage1_5 (hd0)
> 15 sectors are embedded.
# on va installer le stage1 et paramétrer tout ça :
#   --stage2=... parce que la partition est montée, il faut écrire dessus en utilisant le noyau
#   (hd1,0)/boot/grub/stage1 : on va chercher le stage1 où il est stocké actuellement
#   (hd0) : on installe sur le mbr de /dev/loop0
#   (hd0)1+15 : on lui dit d'utiliser le stage1.5 installé précédemment (mbr+1, 15 secteurs) pour lire le stage2
#   p (hd0,0)/grub/stage2 : on dit au stage1.5 où trouver le stage2
#   /grub/menu.lst : on dit à stage2 où aller chercher sa configuration
$ install --stage2=/mnt/grub/stage2 (hd1,0)/boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/grub/stage2 /grub/menu.lst
$ quit

Remarquez le embed, il met le stage 1.5 dans l'espace disponible derrière la table des partitions comme nous l'avons déjà vu.

Si vous vouliez sauter le stage1.5 cela aurait donné :

$ grub --device-map=/tmp/device.map
$ install --stage2=/mnt/grub/stage2 (hd1,0)/boot/grub/stage1 (hd0) (hd0,0)/grub/stage2 /grub/menu.lst
$ quit

Et comme je suis gentil, je vous montre comment le faire sans s'embêter (et sans hd1) :

$ cp -r /boot/grub /mnt
$ grub --device-map=/mnt/grub/device.map
$ setup --stage2=/mnt/grub/stage2 (hd0) (hd0,0)
$ quit

Et si vous voulez installer grub pour le système en cours d'exécution il y a encore plus simple :

$ grub-install /dev/hda

Test :

$ sync && qemu /tmp/test

Ça y est ! La gloire est à nos portes !

Choix

Et maintenant, lequel choisir ? Cela dépend de votre situation et de vos besoins. Faisons un petit résumé des avantages de chacun.

Grub:

  • peut lancer plus de systèmes
  • dispose d'un shell au boot pour les réparations ou les changements de paramètres
  • résiste aux modifications du système de fichier

Lilo :

  • plus léger
  • sait contourner les bugs d'un grand nombre de vieux bios

C'est ce qui fait que grub est de plus en plus souvent choisi comme bootloader face à lilo.

Niveau : Star Star Star Star Empty
Résumé : mysnapshot

Maintenant que vous savez comment fonctionne device-mapper, il est possible de compenser un certain manque de lvm à la main. Nous allons faire des snapshot de snapshot (merci à glandium pour la suggestion).

Voici 2 scripts permettant de faire cela. Ils s'utilisent de la manière suivante :

# En supposant que vous ayez déjà fait un premier snapshot avec lvm
# snapshot2 sera un snapshot de snapshot1
$ mysnapshot '-L 1G' vg0 snapshot1 snapshot2

# Pour supprimer le snapshot précédemment créé
$ mysnapshotrm vg0 snapshot2

Prenez ces scripts avec quelques pincettes puisque bien que testés sur mes machines, ils n'est pas garanti qu'ils fassent tout ce que vous vouliez. Par exemple, il permet d'enchaîner les snapshot, mais tel quel il va vous poser quelques problèmes pour faire des arbres de snapshot (plusieurs fois un snapshot d'un même snapshot), dans ce cas ... à vous de bosser.

Attention : contrairement à lvm, ces snapshots ne sont pas automatiquement mis en place au boot.

Un petit schéma du principe de fonctionnement des snapshots, peut-être un peu plus clair que le précédent pour ceux qui connaissent device-mapper (attention, les noms ne correspondent pas totalement à ceux du script).

lvm_snapshot2.png

Ce schéma permet aussi d'expliquer quelque chose que j'ai oublié de dire lors de l'article précédent : bien qu'il n'y ait pas de différence fondamentale entre l'orginal et le snapshot, le snapshot est le périphérique qui aura les meilleures performances en écriture.

mysnapshot :

#!/bin/sh 

# gestion des paramètres
if [ -z "$4" ]
then
        echo "usage $0 'lvmParameters' volumeGroup origin snapshot"
        exit 1
fi

lvmparams=$1
vg=$2
origin=$3
snapshot=$4

min=0
# lecture du snapshot existant pour déterminer la taille du dev final
idh=$(stat -c "%T" /dev/mapper/$vg-$origin)
id=$((0x$idh))
max=$(cat /sys/devices/virtual/block/dm-$id/size)

# on crée avec lvm l'espace pour stocker le snapshot
lvcreate -n $snapshot-cow $lvmparams  $vg

# on suspend le snapshot pour que l'activité en cours dessus ne le casse pas
dmsetup suspend $vg-$origin

# on recopie le snapshot d'origine dans un un nouveau dev (pour y accéder directement et pour le retour en arrière)
dmsetup table $vg-$origin | dmsetup create $vg-$origin-real

# on remplace le snapshot d'origine par un device qui va générer des évènements de modif (pour le 2e snapshot)
echo $min $max snapshot-origin /dev/mapper/$vg-$origin-real | dmsetup reload $vg-$origin

# on crée le nouveau snapshot qui pointe vers l'espace de stockage et vers le snapshot d'origine
echo $min $max snapshot /dev/mapper/$vg-$origin-real /dev/mapper/$vg-$snapshot--cow P 8 | dmsetup create $vg-$snapshot

# on réactive le snapshot d'origine qui est maintenant snapshotté
dmsetup resume $vg-$origin

mysnapshotrm :

#!/bin/sh

# gestion des paramètres
if [ -z "$2" ]
then
        echo "usage $0 volumeGroup snapshot"
        exit 1
fi

vg=$1
snapshot=$2

# lecture du dev du snapshot pour découvrir qui était l'original
originmm=$(dmsetup table $vg-$snapshot | cut -d " " -f 4 | sed 's/:/,[[:space:]]+/')
originvg=$(ls -l /dev/mapper | egrep "$originmm" | sed 's/.* \([^ ]*\)$/\1/')
origin=$(echo $originvg | sed "s/$vg-\(.*\)-real/\1/")

# on suspend le snapshot d'origine pour éviter de le casser
dmsetup suspend $vg-$origin

# on le restaure avec la version d'origine
dmsetup table $vg-$origin-real | dmsetup reload $vg-$origin

# et on peut le réactiver
dmsetup resume $vg-$origin

# on vire le snapshot créé
dmsetup remove $vg-$snapshot

# on vire la copie du snapshot d'origine
dmsetup remove $vg-$origin-real

# et on supprime l'espace de stockage de notre snapshot
lvremove -f /dev/$vg/$snapshot-cow

Et si vous êtes développeur lvm, si vous pouviez y intégrer l'équivalent, ça serait sympa.

Niveau : Star Star Empty Empty Empty
Résumé : dm-crypt ; cryptsetup

Maintenant que vous savez tout sur device mapper, il y a une dernière fonctionnalité fournie en standard que vous allez vouloir utiliser: le chiffrement des partitions. Comme vous l'avez compris, il est possible de tout faire avec dmsetup, mais il y a mieux et c'est moins cher.

Chiffrement avec LUKS

Attention, ce que nous faisons ici écrit sur la partition, si vous ne voulez pas perdre vos données, regardez le dernier paragraphe.

La commande cryptsetup s'occupe de tout pour vous. Pour chiffrer une partition :

# pour préserver la résistance de votre partition à la cryptanalyse (des données apparemment chiffrées déjà sur le disque)
# on efface la partition avec des données aléatoires
$ dd if=/dev/urandom of=/dev/sda1
# il vous demandera simplement une passphrase
$ cryptsetup luksFormat /dev/sda1

Et maintenant pour l'utiliser :

# il vous demandera simplement une passphrase
$ cryptsetup luksOpen /dev/sda1 partoche

Et voila, votre partition est disponible dans /dev/mapper/partoche. Vous pouvez en faire ce que vous voulez, mettre une système de fichier dessus ou le redécouper avec lvm ou écrire directement dessus ...

Et si vous ne voulez plus utiliser la partition, fermez-là (la partition) pour ne pas qu'un autre tente d'en lire le contenu, on ne sait jamais :

$ cryptsetup luksClose partoche

Un peu plus loin

Cryptsetup permet d'autres formats de disque que LUKS, mais il n'est pas intéressant de les utiliser car luks est disponible un peu partout (même sous windows) et bien pensé

Luks permet entre autre quelques fantaisies comme la possibilité d'utiliser plusieurs clés, ce qui veut dire que vous pouvez partager un disque dur chiffré entre plusieurs personnes et que chacun ait sa clé pour lire et écrire dessus.

# ajoute une nouvelle clé (nouvel utilisateur ?)
$ cryptsetup luksAddKey /dev/sda1

Et donc vous pouvez révoquer une clé pour interdire à un des participants d'y revenir :

# liste les clés
$ cryptsetup luksDump /dev/sda1
# on en révoque une 
$ cryptsetup luksDelKey /dev/sda1 1

Attention si un des utilisateurs a eu accès aux méta-données de la partition, il a très bien pu les enregistrer, et comme il a du garder sa propre clé, il est dans ce cas toujours capable d'utiliser le disque.

Un autre usage des clés multiples est d'avoir une clé d'usage et une clé de secours imprimée et stockée dans un coffre pour usage en cas de perte de la première.

Types de chiffrement

Vous pouvez choisir le type de chiffrement utilisé au moment de la création de la partition. Par exemple si vous ne faites pas confiance au NIST, vous pourriez préférer la méthode twofish.

# connaître la liste des chiffrements disponibles (attention, tous ne sont pas des chiffrement)
$ cat /proc/crypto
# charger un type manquant
$ modprobe twofish
# utiliser le bon type
$ cryptsetup luksFormat --cipher twofish /dev/sda1

Encore plus loin

Il est même possible de chiffrer une partition sur elle même sans casser ce qui se trouve dessus. Seul problème, il faut qu'elle dispose d'un peu de place pour les en-têtes luks. Cette taille est variable, en fonction de la taille choisie pour la clé. Mais elle est de l'ordre de 500 ko avec les essiv, si besoin faites un test préalable.

Supposons qu'il y a un système de fichier (fort probable :)

# on démonte
$ umount /dev/sda1
# on réduit de 1Mo (ça devrait suffire dans la plupart des cas)
$ resize2fs /dev/sda1 9999M

On stocke dans un coin l'endroit ou sera notre en-tête

# 1Mo aussi 
$ dd if=/dev/sda1 of=/tmp/first bs=1M count=1

On fait notre partition chiffrée

# on prépare la partition
$ cryptsetup luksFormat /dev/sda1
# on la met en place
$ cryptsetup luksOpen /dev/sda1 crypto1

Et là, il y a plusieurs subtilités, on va recopier /dev/sda1 dans /dev/mapper/tmp. Ça fonctionne parce que ce qu'on va lire va être lu avant qu'on écrive au même endroit. Cet argument n'est pas tout à fait valable puisqu'on va recopier un peu plus loin qu'on ne va lire (header oblige). Donc la deuxième subtilité sera de recopier en commençant par la fin pour ne pas écraser les données qu'on va lire à la prochaine itération.

# on récupère le décalage a effectuer (unité le bloc (512o)
$ cryptsetup luksDump /dev/sda1 | grep Payload
> Payload offset: 1032
$ offset=$((1032*512))
# on récupère la taille de la partition : 
$ blockdev --getsize64 /dev/sa1
> 67108864
$ max=67108864

# et on fait la copie morceaux par morceaux en partant de la fin
$ i=$(($max/$offset))
$ while [ $i -gt 0 ]
 do
     dd if=/dev/sda1 of=/dev/mapper/crypto1 bs=$offset count=1 seek=$i skip=$i
     i=$(($i-1))
 done
# et on n'oublie pas notre premier morceau
$ dd if=/tmp/first of=/dev/mapper/crypto1

Ça y est, notre partition chiffrée /dev/mapper/crypto1 est prête à être utilisée comme avant

$ mount /dev/mapper/crypto1 /srv

Encore une victoire de canard !

Et pour plus de détails, le site sur dm-crypt et sur luks vous expliquera tout.

Niveau : Star Star Star Star Empty
Résumé : dmsetup

Maintenant que vous savez tout sur lvm, regardons sous le capot, pour voir comment ça tourne.

lvm

Lvm n'est en réalité qu'une surcouche à un système bien intégré dans linux et qui se nomme device-mapper ou dm en abrégé. Dm ne fait qu'une chose et il le fait bien : mapper (cartographier) un ou plusieurs périphériques de bloc sur un autre périphérique. Donc ce que fait lvm quand vous déclarez un nouveau lv, c'est uniquement déclarer à device mapper qu'un nouveau périphérique de bloc (le lv) correspond à telle et telle portion d'un périphérique physique (les pv).

Bon lvm fait un peu plus que ça puisqu'il sait faire tout ça tout seul au démarrage sans rien vous demander, il stocke ses informations au bon endroit sur les disques et tout marche de façon transparente. Mais supposons que nous voulions le faire nous-même à la main. C'est parfaitement possible avec la commande dmsetup :

# on crée un device nommé monlv, qui fait 10000 blocs et qui est mappé sur /dev/hda à partir du 1234e bloc
$ echo 0 10000 linear /dev/hda 1234 | dmsetup create monlv

Si vous voulez savoir comment sont fait vos lv, il suffit simplement de le demander à dmsetup :

$ dmsetup table /dev/mapper/vg0-lv0
>0 1028160 linear /dev/hda 0
>1028160 3903762 linear /dev/hdb 0

dm

Maintenant, voyons plus loin que lvm, dm vous permet de faire beaucoup de choses (mais à la main). En fait la liste des choses que vous pouvez faire est disponible avec la commande suivante :

$ dmsetup targets
>crypt            v1.5.0
>snapshot-origin  v1.6.0
>snapshot         v1.6.0
>mirror           v1.0.20
>multipath        v1.0.5
>striped          v1.1.0
>linear           v1.0.2
>error            v1.0.1

Cela liste les différentes formes de mapping disponibles dans dm. Vous y reconnaissez des fonctionnalités de lvm, mais aussi d'autres comme error (qui retourne des erreur, pratique surtout pour le développement) ou comme multipath (pour des périphériques auxquels ont peux accéder par des méthodes différentes (redondance)).

Une cible intéressante est crypt qui chiffre à la volée les périphériques de façon transparente. C'est l'utilitaire cryptsetup qui va vous aider à le configurer (hé oui, c'est rigolo à la main, mais c'est pas très pratique). Cet utilitaire vous permettra aussi de gérer différents formats de métadonnées (luks, cryptoloop ...).

Plus rigolo, avec device-mapper vous pouvez tout mélanger et tout superposer. On peut donc imaginer du chiffrement sur seulement la moitié du disque, superposer plusieurs chiffrements, un disque où on laisse des trous, etc.

Et enfin, imaginez que vous puissiez développer votre propre mapper, lequel feriez-vous ? Ne pensez pas que c'est très difficile, il suffit d'écrire 5 fonctions dans le noyau (dont une seule qui fera le mapping). Il est donc possible que si vous avez une bonne idée elle puisse être en développée rapidement. Je vous propose quelques suggestions :

  • Un overlay en ram (lecture sur disque, écriture en ram), pratique pour transformer un fs en lecture seule en lecture/écriture temporaire (déja faisable avec des snapshot et des ramdisk, a la main)
  • Support des qcow et autres format de disque des machines virtuelles
  • Un périphérique avec des checkpoint permettent d'annuler ou de commiter les modifications depuis le dernier checkpoint (plus ou moins jouable avec les snapshot et lvmerge)
  • Un périphérique avec une légère redondance pour récupérer les erreurs du medium (par exemple avec par2)
  • Un système de statistiques sur le périphérique

Et notez une dernière mauvaise idée : faire de la compression. Faire de la compression au niveau périphérique est extrêmement compliqué car cela implique d'avoir des blocs de taille variable et des périphériques de taille variable, ceci n'est actuellement géré ni au niveau des périphériques ni au niveau des systèmes de fichier. La compression est donc bien mieux gérée au niveau du système de fichier.

Niveau : Star Star Star Empty Empty
Résumé : lvm raid

Aujourd'hui la suite tant attendue d'une série sur lvm.

Figurez-vous qu'il est possible de faire du raid avec LVM. Hé oui, vous avez déjà remarqué que vous pouviez mettre plusieurs disque dans un vg. Pour l'instant lvm se contente de les mettre bout à bout, comme le ferait un raid de type linear.

Striping

Le raid0 aussi appelé striping découpe un disque en petits morceaux et les alterne pour en faire un disque plus gros. Le but est d'avoir un disque plus gros, mais aussi d'augmenter les performances. En effet, dès qu'on va lire ou écrire un fichier un peu plus gros que ces morceaux, on va le faire sur 2 disques simultanément et donc augmenter la bande passante.

Lorsqu'on répartit les données linéairement sur le disque on ne gagne pas en performances, par contre en cas de crash d'un des deux disques, il y a moyen de récupérer presque la moitié des données, alors que dans le cas du striping, vous êtes sur de ne rien pouvoir récupérer.

Pour faire du striping avec lvm, rien de plus simple. Lors de la création de votre LV, il suffit de préciser le nombre de "stripes" qu'on désire :

# 2 etant le nombre de partition sur lesquels découper le volume
$ lvcreate -L 1G -i 2 lv0 vg0

Notez que si vous voulez forcer le striping sur certaines partitions, il est possible de donner en paramètre à lvcreate la liste des pv sur lesquels vous vous qu'il soit.

Et pour vérifier que c'est bien ce que vous vouliez :

$ lvdisplay -m /dev/vg0/lv0

Miroir

Le raid1 aussi appelé "mirroring" recopie intégralement un disque sur un autre pour permettre une récupération sans soucis lorsqu'un des deux disques grille, au coût d'un disque supplémentaire.

Alors lvm permet aussi de faire ce genre de chose tout aussi simplement :

# 1 correspond au nombre de copies du volume
$ lvcreate -m 1 -L 1G lv0 vg0

Les miroirs lvm diffèrent du raid1 du fait que lvm demande un périphérique supplémentaire pour stocker les logs du miroir. C'est grâce à ces logs qu'il sait quel est l'endroit qui pose problème lorsqu'il y en a un. C'est donc un peu plus exigeant qu'un raid1.

Et pour vérifier que c'est bien ce que vous vouliez :

$ lvdisplay -m /dev/vg0/lv0

lvm_raid.png

Niveau : Star Star Star Empty Empty
Résumé : lvcreate -s

Avançons encore un peu dans notre exploration de lvm. LVM permet beaucoup de choses, et entre autre la création de snapshots. Un snapshot c'est une prise de vue instantanée, qui dans le cas de lvm se fait au niveau du disque. Ce qui veut dire que le contenu du snapshot peut ne pas être cohérent, par exemple si le système est en train d'écrire sur le disque en plein milieu du snapshot.

On peut utiliser les snapshot pour beaucoup de choses, par exemple, Apple l'utilise pour faire sa machine à remonter dans le temps, on peut l'utiliser pour faire des backup cohérents mais aussi pour faire des expérimentations avec des retours arrière rapide.

Un snapshot

Partons d'un lv existant avec un vg sur lequel il reste de la place (hé oui, il faudra bien stocker nos snapshots. Faisons un snapshot de notre lv :

$ lvcreate -L 50M -s -n snap /dev/vg0/original

Et voila, c'est tout, fin de l'article !

Bon, pas encore. Déjà, pourquoi donner une taille au snapshot ? Tout simplement parce que celui-ci est intelligent, donc il ne va pas copier l'intégralité du lv original. Au contraire, il ne va stocker que les différences. C'est pourquoi il est instantané et commence avec une occupation taille nulle. Par contre, il faut lui allouer une taille dans le vg, donc 50Mo sera la quantité maximum de différence qu'il pourra stocker. Au delà de cette taille, le snapshot sera cassé et il ne pourra plus fonctionner correctement (les données ne sont plus valides, laissez tomber).

lvm_snapshot.png

C'est pourquoi il faut faire attention à bien dimensionner ses snapshots et les gérer correctement. Après quelque quelque temps, vous verrez l'occupation du lv augmenter avec la commande lvdisplay :

$ lvdisplay /dev/vg0/snap
...
  Allocated to snapshot  26,27% 
...

Votre snapshot est occupé à 26%, vous avez encore un peu de marge. C'est à vous de monitorer cette valeur et de prendre une décision, soit supprimer le snapshot (lvremove) ou d'augmenter la place allouée au snapshot (lvextend).

Une fois le snapshot cassé, vous avez ce genre d'information :

$ lvdisplay /dev/vg0/snap
...
  LV snapshot status     source of
                         /dev/vg0/original [INACTIVE]
...

Backup

Donc la technique pour faire un backup est simple, seul problème, si la machine a besoin du système de fichier pendant le snapshot. La meilleure méthode serait de faire "umount;lvcreate -s;mount", mais on ne peut pas. On va donc avoir de préférence un système de fichier qui résiste au crash (ext3 par exemple) et limiter la casse avec sync :

$ sync
# on espère qu'il y aura le moins d'écriture possible entre ces 2 commandes
$ lvcreate -s -L 500M -s -n backup /dev/vg0/original

# et maintenant on a une durée de 500Mo d'écriture pour faire tranquillement notre backup
$ mount /dev/vg0/backup /mnt
$ tar cfz /backups/today.tgz /mnt # c'est vous qui voyez
$ umount /mnt
$ lvremove /dev/vg0/backup

Annulation

Maintenant supposons que nous voulions nous amuser avec notre système de fichier (par exemple pour tester un passage à la prochaine debian sans pour autant avoir peur de tout perdre. Sur / ça risque d'être un peu difficile. Pour ne pas vous perturber nous allons simplifier en utilisant le / d'une machine virtuelle (ou d'un chroot).

$ sync
# choisissez une grande taille, voire la même taille que la partition originale pour éviter tout problème
$ lvcreate -L 5G -s -n backup /dev/vg0/root
# pas besoin de monter le backup
# on bidouille
$ vi /etc/apt/sources.list && apt-get update && apt-get dist-upgrade

Et là paf c'est cassé, réparons (avec un noyau <= 2.6.27):

# on va le casser
$ umount /dev/vg0/root

# on récupère le backup dans un espace temporaire
$ dd if=/dev/vg0/bakup of=/srv/temp.dd

# et on le restaure sur le lv d'origine
$ lvremove /dev/vg0/bakup
$ dd if=/srv/temp.dd of=/dev/vg0/root

# fin
$ rm /srv/temp.dd
$ mount /dev/vg0/root

Le problème de cette technique est qu'elle nécessite un espace aussi gros que la partition disponible pour la restauration. Un petit gzip bien placé peut vous faire gagner en place, mais c'est pas top. Mais ... un patch est en cours pour permettre de faire tout ça en une seule commande et sans nécessiter d'espace intermédiaire. Il ne devrait malheureusement pas être disponible dans le noyau avant la version 2.6.28

# noyau > 2.6.27
$ lvconvert -M bakcup --nameorigin

Lire l'annonce pour plus de détails.

Deux snapshots

Remarquez que de même, il n'est pas possible de faire le snapshot d'un snapshot. C'est bien dommage car cela empêche de faire des tests sur un arbre de test et de revenir à l'endroit ou l'on veut. Ce genre de fonctionnalité n'est pas encore prévue. Il va donc falloir le faire soi-même, ou alors atendre la sortie de btrfs qui devrait permettre ce genre de chose directement sur le système de fichiers..

Donc en attendant, les commandes qui permettent de faire ça en utilisant des copies complètes :

# on snapshotte l'original
$ lvcreate -L 50M -s -n v1x /dev/vg0/v0

# on copie le snapshot (vérifier la taille)
$ lvcreate -L 5G -n v1 vg0
$ dd if=/dev/vg0/v1x of=/dev/vg0/v1
$ lvremove /dev/vg0/v1x

# et on peut resnapshotter
$ vcreate -L 500M -s -n v2 /dev/vg0/v1