Compression
Introduction
Alors que les ordinateurs disposent de plus en plus d’espace de stockage ou de bande passante vers le réseau, il reste encore préférable dans de nombreux cas de compresser les données, par exemple pour manipuler des fichiers plus petits, comme c’est déjà le cas avec les images ou la vidéo (formats JPEG, PNG, MPEG, DivX...). Pour une compression sans perte, le programmeur C se tournera principalement vers deux bibliothèques qui implémentent deux algorithmes de compression différents, à savoir l’ancienne libz et la plus récente libbzip2. La compression sans perte garantit qu’un fichier compressé, lorsqu’il est décompressé, est identique au fichier original. Elle diffère de la compression avec perte, comme implémentée par le format MP3 (MPEG1 couche 3), généralement acceptable car l’oreille n’est pas souvent apte à remarquer les pertes.
Avant de vouloir tout compresser, il faut savoir que la compression fonctionne très bien avec certains types de données et plutôt mal avec d’autres. Ainsi, les résultats obtenus sur des fichiers déjà compressés seront décevants, ce qui peut se comprendre. Parmi les fichiers déjà compressés, nous pouvons citer certaines images, certains fichiers sonores...
Lire un fichier compressé
Problème
Vous voulez lire un fichier compressé au format gz ou bz2.
Solution
S’il s’agit d’un fichier au format gz, utilisez gzopen() pour ouvrir le fichier, puis au choix gzread() ou gzgets() pour le lire, et gzclose() pour le fermer.
S’il s’agit d’un fichier au format bz2, utilisez fopen() suivi de BZ2_bzReadOpen() pour ouvrir le fichier, puis BZ2_bzRead() pour le lire, et BZ2_bzReadClose() suivi de fclose() pour le fermer.
Discussion
La lecture d’un fichier compressé ne diffère de la lecture d’un fichier normal que par les fonctions (de prototypes fortement inspirés de ceux de fopen()) appelées pour le lire. La recette "Lire un fichier" (chapitre "Contenu des fichiers") est donc tout à fait applicable, même pour un fichier compressé, à condition d’utiliser les fonctions correspondantes de zlib pour ouvrir, lire et fermer le fichier. La fonction de l’exemple ci-dessous affiche le contenu du fichier tel que le ferait le programme zcat.
int
zcat (const char *filename)
{
gzFile gzfh = NULL;
char buffer[BUF_SIZE];
int n;
int r = 0;
if (NULL == (gzfh = gzopen (filename, "rb")))
{
fprintf (stderr, "Impossible d'ouvrir '%s'\n", filename);
return (-1);
}
while (0 < (n = gzread (gzfh, buffer, BUF_SIZE - 1)))
{
int l;
buffer[n] = '\0';
/* Remplace les octets nuls par des espaces pour l'affichage */
while (n < (l = strlen (buffer)))
buffer[l] = ' ';
printf ("%s", buffer);
}
printf ("\n");
if (!gzeof (gzfh))
{
printf ("Problème de lecture\n");
r = -1;
}
gzclose (gzfh);
return (r);
}
Il est important de savoir qu’un fichier non compressé est considéré...
Écrire un fichier compressé
Problème
Vous souhaitez écrire dans un fichier des données à compresser au format gz ou bz2.
Solution
Si vous voulez compresser un fichier au format gz, utilisez gzopen() pour ouvrir le fichier, puis au choix gzwrite(), gzprintf() ou gzputs() pour écrire et gzclose() pour le fermer.
Si vous voulez compresser un fichier au format bz2, utilisez fopen() suivi de BZ2_bzWriteOpen() pour ouvrir le fichier, puis BZ2_bzWrite() pour écrire et BZ2_bzWriteClose() suivi de fclose() pour le fermer.
Discussion
L’écriture d’un fichier compressé ressemble de près à celle d’un fichier normal. Elle suit le même ordre d’idées que la lecture d’un fichier compressé vu dans la recette précédente. Seuls les appels à gzopen() et à BZ2_bzWriteOpen() ont quelques particularités dans le cas d’une ouverture pour écriture.
Ainsi, le second argument de gzopen() est la chaîne de caractères "wbX", qui est la même que pour fopen(), avec en plus la possibilité de mettre un chiffre entre zéro et neuf à la place du X pour indiquer le taux de compression. Zéro permet d’avoir un taux de compression nul, ce qui revient à ne pas compresser. Ce taux nul est intéressant pour déboguer. Inversement, neuf est le meilleur taux de compression.
Dans le cas de BZ2_bzWriteOpen(), les arguments sont les suivants. Le premier est un pointeur vers un entier qui contiendra un code d’erreur au retour de la fonction. Voyez la recette précédente pour ces codes d’erreur qui sont les mêmes que ceux de BZ2_bzReadOpen(). Le deuxième argument est le descripteur de flux renvoyé...
Compresser des données en mémoire
Problème
Vous disposez de données en mémoire que vous souhaitez compresser.
Solution
Utilisez compress2() si votre préférence va à zlib, ou BZ2_bzBuffToBuffCompress si vous préférez libbzip2.
Discussion
Si vous utilisez zlib, la fonction à utiliser est compress2() dont les arguments sont un espace mémoire pré-alloué pour y placer le résultat, un pointeur vers un entier qui contiendra la taille des données compressées, puis un pointeur vers les données à compresser suivi de leur taille, et en cinquième argument, le niveau de compression, entre 0 (pas de compression) et 9 (compression la meilleure possible). La taille du tampon à pré-allouer doit être d’une taille un peu supérieure à la taille des données initiales, même si après compression, la taille des données sera moindre. La formule de calcul de la taille du tampon à pré-allouer est la suivante : <longueur de la source> + <longueur de la source> * 0.1% + 12. Prenons un exemple :
int
gz_alloc_and_compress (char **result, int *result_len, char *string)
{
int len = strlen (string);
int err;
char *buffer;
if (NULL ==
(buffer = malloc ((len * 1001 / 1000 + 12) * sizeof *buffer)))
return (-1);
err = compress2...
Décompresser des données en mémoire
Problème
Vous souhaitez lire des données qui sont compressées et en mémoire.
Solution
Avec zlib, utilisez uncompress(). Avec libbzip2, utilisez BZ2_bzBuffToBuffDecompress().
Discussion
La fonction uncompress() fonctionne comme son opposée compress() avec ses quatre arguments.
La fonction BZ2_bzBuffToBuffDecompress() prend six arguments. Les quatre premiers sont les mêmes que ceux de uncompress(). Les deux suivants sont les mêmes que les deux derniers arguments de BZ2_bzReadOpen(), vue dans la première recette de ce chapitre. Voici en exemple les fonctions inverses à celles de la recette précédente :
char *
gz_alloc_and_uncompress (char *data, int len)
{
int err;
int result_len = len * 10;
int real_result_len;
char *result;
if (NULL == (result = malloc (result_len * sizeof *result)))
return (NULL);
err = uncompress (result, &real_result_len, data, len);
while (Z_BUF_ERROR == err)
{
result_len += len;
if (NULL ==
(result == realloc (result, result_len * sizeof *result)))
return (NULL);
err = uncompress (result, &real_result_len, data, len);
}
if (err != Z_OK)
{
free (result);
return (NULL);
}
return (realloc (result, ((real_result_len) * sizeof *result)));
} ...
Décompresser un fichier tar.gz ou tar.bz2
Problème
Vous souhaitez décompresser un fichier tar compressé au format gz ou bz2, fichier dont l’extension est généralement .tar.gz ou .tar.bz2.
Solution
Exécutez tar via une commande exec, avec les options adéquates.
Discussion
Une archive .tar.bz2 ou une archive .tar.gz n’est en fait qu’un archivage en un seul fichier d’un ensemble de fichiers, avec la commande tar, fichier archive qui est lui-même ensuite compressé avec gzip ou bzip2. Aujourd’hui, la commande tar supporte deux extensions qui permettent de réaliser l’archivage et la compression en une étape. Ces extensions sont appelées via l’option -j pour bzip2 et -z pour gzip.
Les recettes "Lire un fichier compressé" et "Compresser des données en mémoire" permettent de réaliser la première étape de décompression. Avec la bibliothèque libtar nous pourrions effectuer le reste. Cette bibliothèque, qui est une excellente initiative, souffre d’un défaut dont la responsabilité est plutôt à attribuer aux auteurs de tar : ce dernier est tellement simple à utiliser par rapport à la bibliothèque libtar qu’il est préférable d’exécuter tar. De plus, le coût de la création d’un nouveau processus est négligeable à moins d’effectuer l’opération dans une grande boucle. Or cela n’est généralement pas le cas : nous n’avons pas tant de fichiers archives à décompresser à l’aide d’une boucle.
Pour extraire les fichiers d’une archive .tar.gz ou .tar.bz2, voici comment cela se passe :
typedef enum
{
COMPRESSION_METHOD_DIRECT,
COMPRESSION_METHOD_GZIP,
COMPRESSION_METHOD_BZIP2
} compression_method_e;
int
tar_extract (char *gnutar, char *filename, char *output_dir,
compression_method_e...