Gestion des dossiers et des fichiers en C avec la GLib

Julian Ibarz

Nous apercevons dans beaucoup de forums des questions dont la plus posée est : « Comment explorer un dossier ? » La GLib propose une solution simple au moyen de quelques fonctions, profitons-en ! ♪

- Titre : Gestion des dossiers et des fichiers en C avec la GLib

- Auteur : Julian Ibarz

- Parution : 30 juin 2008

- Licence : Copyright ® 2008 Julian Ibarz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Exploration d'un dossier

- II. Gestion de la portabilité

- III. Gestion multiutilisateur

- IV. Gestion des erreurs

- V. Remerciements

- VI. Références

I - Exploration d'un dossier

Avant toute chose, il faut commencer par obtenir un descripteur sur le dossier, à la manière d'un fichier, grâce à la fonction :

GDir* g_dir_open(const gchar *path, guint flags, GError **error);

Si le dernier paramètre vous horrifie, allez voir l'article sur la gestion des erreurs en C avec la GLib.

- path est la chaîne représentant le chemin. Par exemple sous Windows : « c:\\windows ».

- flags n'est pour l'instant pas utilisé par la GLib. Celle-ci préconise de le mettre à 0.

- error vous permet de récupérer les erreurs de runtime.

Pour fermer le dossier après son exploration, on utilisera :

void g_dir_close (GDir* dir);

Pour connaître les fichiers ou dossiers contenus dans notre dossier, on utilise :

G_CONST_RETURN gchar* g_dir_read_name(GDir* dir);

Ceci renvoie une chaîne de caractères représentant un fichier contenu dans le dossier. Par exemple votre dossier c:\moi\cours contient un fichier fichier.txt et bien si vous faites :

GDir* monDossier=g_dir_open("c:\\moi\\cours", 0, NULL);
G_CONST_RETURN gchar* file=g_dir_read_name(monDossier);

file sera bien sûr égal à « fichier.txt ». Si vous rappelez la fonction, elle vous renverra un autre fichier contenu dans le dossier.

Notez que « . » et « .. » qui représentent respectivement le dossier actuel et le dossier parent sont supprimés par la GLib.

L'ordre dans lequel les fichiers sont listés dépend du système.

Cette fonction nous dit quand s'arrêter, car lorsqu'elle nous a renvoyé tous les fichiers contenus dans le dossier, elle renvoie NULL. Pour connaître tous les fichiers d'un dossier, il vous suffit donc de faire une boucle et de mettre comme condition d'arrêt le retour NULL :

GDir* monDossier=g_dir_open("c:\\moi\\cours", 0, NULL);
G_CONST_RETURN gchar* file g_dir_read_name(monDossier);

// Affiche tous les fichiers et dossiers contenus dans monDossier sur la sortie standard
while(file != NULL)
{
printf("%s\n",file);
file = g_dir_read_name(monDossier);
}

Si ça vous prend de vouloir recommencer l'exploration d'un dossier depuis le début, utilisez :

g_dir_rewind(monDossier);

Le prototype de la fonction est bien sûr (vous l'avez deviné) :

void g_dir_rewind(GDir *dir);

Maintenant, imaginons que vous vouliez explorer un dossier et ses sous-dossiers. Il vous faudra utiliser cette fonction :

gboolean            g_file_test                         (const gchar *filename,
GFileTest test);

- filename l'adresse absolue ou relative (par rapport au dossier courant) du fichier à tester.

- Teste le test à effectuer.

Vous mettrez pour test la valeur G_FILE_TEST_IS_DIR. Ainsi si cette fonction renvoie TRUE, vous saurez que le fichier est en fait un dossier et vous n'aurez qu'à rappeler récursivement votre fonction d'exploration de dossier pour explorer ce dossier. Si vous voulez connaître d'autres valeurs possibles pour cet argument, je vous invite à lire la documentation officielle.

Pour connaître le dossier courant, rien de plus simple :

gchar* g_get_current_dir(void);

Ne pas oublier de désallouer la chaîne renvoyée. De manière générale (sauf exception contraire), tout return de type gchar* doit être désalloué avec g_free. Par contre les return de type G_CONST_RETURN ne doivent surtout pas être désalloués.

Ainsi si g_get_current_dir me dit que le dossier courant est c:\moi et que vous voulez savoir si c:\moi\cours\fichier.txt est un dossier. Vous aurez plusieurs choix possibles :

g_file_test("cours\\fichier.txt",G_FILE_TEST_IS_DIR); // Chemin relatif
g_file_test("c:\\moi\\cours\\fichier.txt",G_FILE_TEST_IS_DIR); // Chemin absolu

II - Gestion de la portabilité

La GLib fournit deux macros pour un peu plus de compatibilité. Ceux qui s'intéressent à cela savent que sous Windows les séparations entre les dossiers constituant un chemin est le caractère '\', par exemple c:\windows\system. Sous Linux c'est le caractère '/', par exemple /usr/lib. Lorsque vous constituez des chemins, utilisez les macros G_SEARCHPATH_SEPARATOR (sous Windows, le caractère '\') et G_SEARCHPATH_SEPARATOR_S (sous Windows, la chaîne « \ »).

Notez bien la notation standard que j'utilise : un caractère (de type C char) est entouré de '' tandis qu'une chaîne (de type C char*) est entourée de "" ; ce qui n'est vraiment pas pareil. Si vous ne comprenez pas la nuance, je vous suggère de revoir votre cours préféré de C ou d'aller voir l'article sur les chaînes de caractères.

Il y a aussi des fonctions qu'on cherche partout et qui sont en fait sous notre nez (dans une autre rubrique de la GLib intitulée Miscellaneous Utility Functions…) comme celle-ci :

gchar* g_build_filename(const gchar* first_element,...);

Ainsi, si l'on tape :

gchar* file=g_build_filename("c:","temp","bou.txt",NULL); // le NULL pour dire : "Ca y est, on arrête !". Sinon madame la GLib continue...
g_free(file); // Et on n'oublie pas de désallouer !

Et notre belle fonction produit le résultat suivant : « c:\\temp\\bou.txt » qui s'affiche comme ceci :

c:\temp\bou.txt

Ce qui est bien le résultat escompté… Magnifique non ?

Et si on veut contrôler le séparateur (le caractère '\\'), on utilise la fonction suivante :

gchar* g_build_path(const gchar *separator, const gchar *first_element, ...);

Un exemple pour comprendre :

g_build_path("#","c:","temp","bou.txt",NULL);

Ce qui produira :

c:#temp#bou.txt

Et la fonction est gentille en plus parce que si l'on tape :

g_build_filename("c:","","temp","bou.txt",NULL);

La fonction ne produira pas c:\\\temp\bou.txt, mais le joli résultat de tout à l'heure ou si l'on a oublié que la GLib mettait les séparateurs automatiquement, par exemple si on écrit :

g_build_path("#","c:","#","temp","#","bou.txt",NULL);

Et bien notre fonction ne génèrera pas :

c:###temp###bou.txt

Mais reconnaîtra qu'on a inséré la chaîne de séparation par inadvertance et les supprimera ; on aura donc comme résultat :

c:#temp#bou.txt

C'est sympa tout de même.

Une autre fonction utile :

gchar* g_path_get_basename(const gchar *file_name);

On donne un chemin absolu et on nous renvoie le nom du fichier. Par exemple si on envoie « c:\\temp\\bou.txt », et bien on nous renvoie « bou.txt ». Pratique… Ne pas oublier de désallouer la chaîne qu'on nous renvoie !

Ensuite pour savoir si l'on a un chemin absolu ou non :

gboolean g_path_is_absolute(const gchar *file_name);

Si on envoie « c:\\temp\\bou.txt », on nous répond TRUE, si c'est « bou.txt », c'est FALSE.

Et le petit gadget de la semaine :

G_CONST_RETURN gchar* g_path_skip_root(const gchar *file_name);

On envoie « c:\\temp\\bou.txt » et on nous renvoie « temp\\bou.txt ». Bref on supprime la racine. Sous un UNIX la fonction supprimera le premier slash.

III - Gestion multiutilisateur

Il peut arriver qu'on veuille stocker des fichiers dans le dossier Mes Documents ou si l'on est Linuxien home qui sous Windows est pour moi : c:\Document And Settings\Julian ; et qui sous Linux est /home/julian. Et bien pour savoir :

G_CONST_RETURN gchar* g_get_home_dir(void);

Cela permet de configurer votre programme différemment pour chaque utilisateur sans passer par une boîte de dialogue pour choisir sa configuration.

Et si on veut stocker des fichiers temporaires dans le dossier des fichiers temporaires (ce qui est légitime ;-) :

G_CONST_RETURN gchar* g_get_tmp_dir(void);

Allez, si vous voulez savoir comment vous vous appelez (on ne sait jamais, ça peut servir ;) :

G_CONST_RETURN gchar* g_get_user_name(void);

Il me renvoie Julian. Ensuite pour savoir le login de l'utilisateur courant :

G_CONST_RETURN gchar* g_get_real_name(void);

Il me renvoie aussi Julian, mais sous un UNIX la première fonction m'aurait renvoyé Julian Ibarz et la dernière julian…

IV - Gestion des erreurs

Le domaine d'erreur est G_FILE_ERROR, voici l'énumération :

typedef enum
{
G_FILE_ERROR_EXIST,
G_FILE_ERROR_ISDIR,
G_FILE_ERROR_ACCES,
G_FILE_ERROR_NAMETOOLONG,
G_FILE_ERROR_NOENT,
G_FILE_ERROR_NOTDIR,
G_FILE_ERROR_NXIO,
G_FILE_ERROR_NODEV,
G_FILE_ERROR_ROFS,
G_FILE_ERROR_TXTBSY,
G_FILE_ERROR_FAULT,
G_FILE_ERROR_LOOP,
G_FILE_ERROR_NOSPC,
G_FILE_ERROR_NOMEM,
G_FILE_ERROR_MFILE,
G_FILE_ERROR_NFILE,
G_FILE_ERROR_BADF,
G_FILE_ERROR_INVAL,
G_FILE_ERROR_PIPE,
G_FILE_ERROR_AGAIN,
G_FILE_ERROR_INTR,
G_FILE_ERROR_IO,
G_FILE_ERROR_PERM,
G_FILE_ERROR_FAILED
} GFileError;

Et la documentation...

- G_FILE_ERROR_EXIST Opération non permise : seulement le propriétaire du fichier (ou une autre personne) ou un processus avec les droits appropriés peut effectuer l'opération.

- G_FILE_ERROR_ISDIR Le fichier est un dossier ; vous ne pouvez ouvrir un fichier en mode écriture, ou créer ou effacer des liens (comprenez des fichiers : un dossier est un ensemble de « pointeurs », à comprendre comme les pointeurs en C, qui contiennent l'adresse des fichiers dans le disque dur).

- G_FILE_ERROR_ACCES Permission refusée ; les attributs du fichier ne permettent pas cette opération.

- G_FILE_ERROR_NAMETOOLONG Nom de fichier trop long.

- G_FILE_ERROR_NOENT Impossible de trouver le fichier ou le dossier. Ceci est une erreur de fichier introuvable « This is a »file doesn't exist" error for ordinary files that are referenced in contexts where they are expected to already exist.

- G_FILE_ERROR_NOTDIR Un fichier qui n'est pas un dossier a été donné quand un dossier est attendu.

- G_FILE_ERROR_NXIO Matériel ou adresse introuvable. Le système a essayé d'utiliser le matériel représenté par un fichier que vous avez donné et il ne l'a pas trouvé (pour les non-Unixens, dans le monde Unix, le driver d'un matériel dialogue avec le système à travers un banal fichier). Cela peut dire que le fichier du matériel a été mal installé, ou que le matériel physique est en train d'utiliser le fichier ou est mal connecté à l'ordinateur.

- G_FILE_ERROR_NODEV Ce fichier est sur un système de fichier qui ne supporte pas le mapping.

- G_FILE_ERROR_ROFS Le dossier contenant le nouveau lien ne peut pas être modifié, car il est sur un système de fichier en lecture seule.

- G_FILE_ERROR_TXTBSY Fichier texte occupé.

- G_FILE_ERROR_FAULT Vous pointez vers une mauvaise zone mémoire. La GLib ne sera plus stable si ceci est retourné.

- G_FILE_ERROR_LOOP Trop de niveaux de liens symboliques ont été rencontrés lors de la lecture du nom de fichier. Cela arrive quand il y a un cycle dans les liens symboliques.

- G_FILE_ERROR_NOSPC Plus d'espace disque ; l'opération d'écriture dans un fichier a échoué parce que le disque est plein.

- G_FILE_ERROR_NOMEM Pas assez de mémoire. Le système ne peut plus allouer assez de mémoire virtuelle parce que le disque dur est plein (la mémoire virtuelle a le même rôle qu'une mémoire vive, mais son lieu de stockage est le disque dur ; d'où son adjectif virtuelle).

- G_FILE_ERROR_MFILE Le processus courant a trop de fichiers ouverts et ne peut plus en ouvrir d'autres. Duplicate descriptors do count toward this limit.

- G_FILE_ERROR_NFILE Il y a trop de fichiers ouverts en même temps dans le système.

- G_FILE_ERROR_BADF Mauvais descripteur de fichier ; par exemple, écriture ou lecture sur un descripteur qui a été fermé ou lecture sur un descripteur qui est ouvert seulement pour de l'écriture (ou vice versa).

- G_FILE_ERROR_INVAL Argument invalide. Cette erreur est utilisée pour indiquer différents problèmes qui ont pour origine le passage d'un argument incorrect à une fonction de la librairie.

- G_FILE_ERROR_PIPE Erreur UNIX seulement qui arrive si on utilise des tubes de communication. Tube de communication cassé ; il n'y a aucun processus en lecture sur ce tube. Toutes les fonctions de la librairie qui retournent cette erreur génèrent aussi un signal SIGPIPE ; ce signal termine le programme si ce dernier n'est pas bloqué ou récupéré. Ainsi, votre programme ne verra jamais ce code d'erreur s'il ne bloque pas ou ne récupère pas le signal SIGPIPE.

- G_FILE_ERROR_AGAIN Ressource temporairement inaccessible ; l'appel devrait marchait si vous essayez encore.

- G_FILE_ERROR_INTR Appel de fonction interrompu ; un signal asynchrone est arrivé et a arrêté l'appel de la fonction. Quand cela arrive, vous devriez simplement rappeler la fonction.

- G_FILE_ERROR_IO Erreur de Lecture/Écriture ; généralement utilisé pour des erreurs de lecture ou d'écriture physiques c'est-à-dire que le disque ou un autre matériel physique a rencontré une erreur.

- G_FILE_ERROR_PERM Opération non permise : seulement le propriétaire du fichier (ou une autre personne) ou un processus avec les droits appropriés peut effectuer l'opération.

- G_FILE_ERROR_FAILED Ne correspond pas à un code d'erreur UNIX : ceci est le code d'erreur standard pour une « erreur de cause inconnue » présent dans toutes les énumérations de code d'erreur d'un GError. Utilisé si aucun autre code ne peut être appliqué.

Notez que l'on peut recevoir deux codes d'erreur différents pour une même erreur dans deux systèmes d'exploitation différents. En effet, pour les systèmes UNIX, la GLib se base sur les codes d'erreurs de la GNU C Library et à ceux de Microsoft sous Windows.

V - Remerciements

Je voudrais remercier Gege2061 pour ses conseils et relectures.

VI - Références

Voici différents liens qui pourraient vous être utiles :

- Documentation officielle de la GLib sur les dossiers

- Documentation officielle de la GLib sur les fonctions utilitaires

- Gestion des erreurs en C avec la GLib

- FAQ GLib sur la gestion des fichiers

- Cours sur les chaînes de caractères

- Cours sur la récursivité