cClaude.rocks ☕ Le blog

[Nouvelles technologies, sciences et coups de gueule…]

Menu

J’ai beaucoup de sauvegarde d’images SD, cela permet de faire des migrations de mes PIs de manière assez sure. Pour cela j’utilise la commande dd qui à l’avantage de sauvegarder toutes les partitions d’un coup. Le problème c’est que la taille de l’image est aussi grosse que la taille de la SD. Or, je prévois mes SD de telle sorte que l’espace utilisé ne dépasse pas les 60 % de l’espace total ; sur certaines machines, c’est largement moins.

Comment réduire la taille de ces images, la compression ? C’est très long et pas forcément très efficace.

L’idée est donc d’utiliser les commandes gparted, fdisk et truncate.

  • On va utiliser gparted pour redimensionner les partitions contenues dans le fichier .img,
  • Utiliser fdisk pour connaître la zone occupée sur le disque,
  • et enfin tronquer le fichier avec truncate pour ne garder que la partie significative.

Procédure détaillée

Je vous conseille de vous positionner dans le dossier du fichier image à traiter, par exemple :

cd ~/Documents/raspbian-images/backups/

Sachant que GParted opère sur des périphériques, et non sur de simples fichiers comme les images, il est d’abord nécessaire de créer un périphérique pour l’image. Nous le faisons en utilisant la fonctionnalité de bouclage (loop) de Linux.

Il est probable que loop soit déjà actif, mais si ce n’est pas le cas vous pouvez le faire à l’aide de :

sudo modprobe loop # Si besoin uniquement

Demandons un nouveau périphérique de bouclage (libre) :

sudo losetup --find

La commande renvoie le chemin vers un périphérique de bouclage libre, par exemple :

/dev/loop18

Créons un périphérique de l’image :

sudo losetup /dev/loop18 2022-03-17.09-00.framboiseXXXXXX.img

ou

Vous pouvez faire cela en une fois comme suit :

sudo losetup --find --partscan 2022-03-17.09-00.framboiseXXXXXX.img

Puis utilisez lsblk pour voir quels périphériques ont été associés à cette image.

lsblk

Mais vous pouvez également utiliser losetup :

losetup --json # ou --list

Pour continuer, vous devez avoir identifié le périphérique /dev/loopN correspondant à votre image…

Maintenant, il faut demander au noyau de charger le fichier :

sudo partprobe /dev/loop18

Dans l’exemple, on a le périphérique /dev/loop18 qui pointe vers le fichier image 2022-03-17.09-00.framboiseXXXXXX.img.

Maintenant, il est possible de lancer GParted comme suit :

sudo gparted /dev/loop18

Dans GParted, vous pouvez alors redimensionner les partitions comme pour un disque standard. L’idée étant de redimensionner la ou les partitions de manière à ce qu’elle s’adapte à son contenu.

Déplacer vers la gauche autant que possible les partitions.

N’oubliez pas de finaliser l’opération en appliquant les modifications.

Maintenant le périphérique de bouclage n’est plus nécessaire, alors déchargez-le :

sudo losetup -d /dev/loop18

L’étape suivante consiste à tronquer le fichier en supprimant la dernière partie maintenant inutile ; l’opération précédente consistant justement à mettre les données importantes au début de l’image.

Nous devons savoir où se termine notre partition et où commence la partie non allouée. Pour ce faire, nous utiliserons fdisk :

fdisk -l 2022-03-17.09-00.framboiseXXXXXX.img
Disk 2022-03-17.09-00.framboiseXXXXXX.img: 29,74 GiB, 31914983424 bytes, 62333952 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
Disklabel type: dos
Disk identifier: 0x5d05d223

Device                                Boot  Start      End  Sectors  Size Id Type
2022-03-17.09-00.framboiseXXXXXX.img1        8192   532479   524288  256M  c W95 FAT32 (LBA)
2022-03-17.09-00.framboiseXXXXXX.img2      532480 14868479 14336000  6,9G 83 Linux

Notez deux informations importantes dans le résultat affiché :

  • La partition se termine sur le bloc 14868479 (indiqué sous « End »).
  • La taille du bloc est de 512 octets (indiquée sous forme de secteurs de « 1 * 512 » — comprendre : 1 × 512).

Nous utiliserons ces chiffres dans le reste de l’exemple. La taille du bloc (512) est la valeur que vous retrouverez le plus souvent, mais le bloc de fin (14868479) sera différent pour vous.

Ces chiffres signifient que la partition se termine sur l’octet 14 868 479 × 512 du fichier. Après cet octet vient la partie non allouée. Seuls les 14 868 479 × 512 premiers octets seront utiles pour notre image.

Pour réduire le fichier image à une taille qui peut juste contenir la partition nous utiliserons la commande truncate.

La commande truncate nécessite de fournir la taille du fichier en octets.

Le dernier bloc était 14868479 et les numéros de bloc commencent à 0. Cela signifie que nous avons besoin de (14 868 479 + 1) × 512 octets. C’est important, sinon la partition ne s’adaptera pas à l’image.

Il est possible de faire la calcul directement en bash comme suit:

echo $(( (14868479+1)*512 ))
7612661760

Il est possible truncate avec les calculs, comme suit :

truncate --size=$(( (14868479+1)*512 )) 2022-03-17.09-00.framboiseXXXXXX.img


Un petit script pour aider…

  • gparted-dd-img-and-resize.sh

    Script sans garantie à utiliser sur une sauvegarde, évitez d’utilisé l’image originale.

    #!/bin/bash
    #
    # gparted-dd-img-and-resize.sh
    #
    
    function mount_dd_image {
      local -r img_file_realpath="$1"
    
      sudo losetup --find --partscan "${img_file_realpath}" >&2 || return $?
    
      local __json__=
      __json__="$(
        set -x
        losetup --json | jq --arg image "${img_file_realpath}" '.loopdevices[] | select( .["back-file"] == $image )'
        )" || return $?
    
      jq . <<<"${__json__}" >&2
    
      local __device__=
      __device__="$( jq -r .name <<<"${__json__}" )" || return $?
    
      echo "Device: ${__device__}" >&2
    
      sudo partprobe "${__device__}" >&2 || return $?
      echo "${__device__}"
    }
    
    function open_gparted_dd_image {
      local -r img_file="$1"
    
      local img_file_realpath=
      img_file_realpath="$( realpath "${img_file}" )" || return $?
    
      if [ ! -f "${img_file_realpath}" ] ; then
        echo "File not found: '${img_file}'"
        return 1
      fi
    
      local __device__=
      __device__="$( mount_dd_image "${img_file_realpath}" )" || return $?
    
      echo "Start gparted on '${__device__}'"
      sudo gparted "${__device__}" || return $?
    
      echo "Free device '${__device__}'"
      sudo losetup -d "${__device__}" || return $?
    }
    
    function dd_image_auto_resize {
      local -r img_file="$1"
    
      fdisk -l "${img_file}" || return $?
    
      local -i size=0
      size="$(
        fdisk -l "${img_file}" | grep 'Units: ' | sed -E 's,(.*)=[[:blank:]]*(.*)[[:blank:]]*bytes,\2,g'
        )" || return $?
    
      if [ "${size}" -eq 0 ] ; then
        echo 'Units size error'
        return 1
      fi
    
      local -i end=0
      end="$(
        fdisk -l "${img_file}" |
          grep "^${img_file}" |
          sed -E 's,^(.*)[[:blank:]]+([0-9]+)[[:blank:]]+([0-9]+)[[:blank:]]+([0-9]+)[[:blank:]]+(.*),\3,g'|
          sort -n -r |
          head -n 1
        )" || return $?
      if [ "${end}" -eq 0 ] ; then
        echo 'End position error'
        return 1
      fi
    
      local -i new_size=0
      new_size=$(( ( end + 1 ) * size ))
    
      if [ "${new_size}" -eq 0 ] ; then
        echo 'New size error'
        return 1
      fi
    
      (
        set -x
        truncate --size=${new_size} "${img_file}"
      ) || return $?
    }
    
    function gparted_dd_img_and_resize {
      local -r img_file="$1"
    
      open_gparted_dd_image "${img_file}" || return $?
      dd_image_auto_resize "${img_file}" || return $?
    }
    
    function main {
      gparted_dd_img_and_resize "$1" || return $?
    }
    
    main "$@" || exit $?
    


Références

ᦿ


ℹ 2006 - 2022 | 🏠 Accueil du domaine | 🏡 Accueil du blog