Les fichiers temporaires, ces red shirts de la programmation

enlist-red-shirt2Dans la série télévisée Star Trek, les agents de sécurité sont reconnaissables à leur gilet rouges, c’est pour cela qu’on les appelle couramment les red shirts. Le souci avec ces personnages, c’est qu’ils ont une fâcheuse tendance à mourir dès leurs premières minutes à l’écran (pour une raison différente à chaque épisode), ainsi, les personnages principaux (récurrents) se rendent compte du danger imminent sans subir eux-mêmes de dommages (pratique, scénaristiquement, non ?). Il s’agit souvent de leur unique rôle dans l’épisode, et on ne connaît parfois même pas leur nom.

De manière assez inattendue, on peut trouver des similitudes entre un redshirt de Star Trek et un fichier temporaire en programmation : on en a besoin dans un contexte particulier, son nom ne nous intéresse parfois même pas, et il se doit d’être détruit à la fin, lorsqu’on en a plus besoin.

Cette fois, je vas parler des fichiers temporaires, d’une astuce et de quelques fonctions pour les gérer facilement en C. Notez bien: on est en environnement Linux. Il n’est pas garanti que ça marche (voire même c’est sûr, certaines choses ne marcheront pas) sur Windows ou ailleurs.

« Dead File Walking »

Une première technique consiste à se servir de l’appel système unlink : l’astuce ici est d’ouvrir le fichier temporaire, puis d’appeler unlink juste après. Cet appel système, en temps normal, sert à supprimer un fichier du disque dur. Mais, le système est intelligent, il garde une table des fichiers ouverts. Et il va vite se rendre compte que ce fichier est encore ouvert par le programme. Ainsi, il ne le supprime pas tout de suite, on peut se servir du fichier normalement, mais dès qu’on ferme le fichier ou que le programme se ferme (dans ce cas, il ferme le descripteur de fichier de toute façon), le fichier disparaîtra: un vrai fichier temporaire.

On peut faire un petit test vite fait :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

int main(void)
{
int fd = open("redshirt.tmp", O_WRONLY|O_CREAT, 0644);

printf("%d\n", fd);
sleep(5);
printf("%d\n", unlink("redshirt.tmp")); // la destruction du fichier en tant que telle
printf("%lu\n", write(fd, "pew", 3));
printf("%lu\n", write(fd, "pew", 3));
sleep(5);
printf("%lu\n", write(fd, "pew", 3));
close(fd);
return 0;
}

j’ai inclus des sleep pour les besoins de la démonstration, mais en gros, ce qu’il faut retenir, c’est qu’au premier sleep, le fichier apparaît encore sur le système de fichiers, mais que dès l’appel à unlink, il disparaît (virtuellement). Il n’est plus dans l’arborescence, mais écrire dedans reste possible car Linux détecte qu’un file descriptor ( « fd » ) utilisant ce fichier est toujours ouvert. La libération des données s’effectue alors à la sortie du programme (et ce, quelle que soit l’issue: normale, plantage, etc.).

La bibliothèque standard traite-t-elle ce problème ?

redstorm

Comme un vrai redshirt, un fichier temporaire devrait disparaître quoi qu’il arrive !

Avec un problème aussi universel, il aurait été bien étonnant que la bibliothèque standard du C ne propose pas quelque chose.

La solution primaire se présente sous la forme de la fonction mkstemp : on lui passe un « patron » de nom de fichier, dont les six derniers caractères doivent être des X (« XXXXXX »), car ce sont ces X qui seront remplacés pour former un nom de fichier unique, pour ouvrir le fichier temporaire. La fonction retourne ensuite le fd du fichier déjà ouvert, prêt à l’utilisation (ouvert par défaut en lecture/écriture).

Notez que cette fonction se décline en plusieurs versions: on dénote par exemple mkostemp qui permet de spécifier soi-même les flags de configuration que la fonction doit passer à l’appel système open interne, ou mkstemps qui permet de créer des fichiers temporaires dont la partie variable (les X) se situe non pas à la fin du nom, mais au milieu (le patron de nom comporte alors un « préfixe » et un « suffixe »).

Cette méthode, bien qu’un peu plus standard, a cependant quelques défauts:

  • on n’a aucun moyen de savoir quel est le nom du fichier choisi (ce n’est pas trop grave, dépendant de la situation)
  • le fichier n’est pas automatiquement détruit à la fin du programme. En partie car cette fonction part du principe que le programmeur va vouloir ouvrir le fichier dans le dossier temporaire du système (/tmp sur Linux), qui est de toute façon nettoyé à chaque redémarrage
  • un défaut de pur confort d’utilisation pour le programmeur: comme la chaîne de caractères patron passée en argument est modifiée par la fonction, on ne peut pas lui passer une chaîne constante (qui, pour ceux qui l’ignorent, sont stockées dans la partie « read-only » d’un binaire : elles sont immuables, tenter de les modifier fait exploser le truc à tous les coups) comme dans le cas, par exemple, de open. Il faut passer à cette fonction une chaîne de caractères allouée ou un tableau.

Si on met à jour notre exemple pour mkstemp, ça donne :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <unistd.h>

int main(void)
{
char template[] = "redshirt.XXXXXX";
int fd = mkstemp(template); // bien

// int fd = mkstemp("redshirt.XXXXXX"); <-  PAS BIEN !!

printf("%d\n", fd);
sleep(5);
printf("%lu\n", write(fd, "pew", 3));
printf("%lu\n", write(fd, "pew", 3));
sleep(5);
printf("%lu\n", write(fd, "pew", 3));
close(fd);
return 0;
}

 

L’abstraction absolue: tmpfile()

post-6101-This-is-going-to-be-a-bloodbat-8TP2

À part la fonction mkstemp qui comme nous l’avons vu a quelques défauts, la bibliothèque standard nous propose une autre fonction beaucoup plus « haut niveau » : tmpfile (la bien-nommée).

Ici, c’est encore plus simple: pas de paramètre, pas de nom. Elle se charge entièrement pour nous du choix de nom de fichier, où le mettre, et elle renvoie juste un pointeur sur un descripteur de flux (FILE *), prêt à l’utilisation en lecture/écriture. C’est un peu différent d’un file descriptor, mais ça marche globalement pareil, voire mieux (mais ce n’est pas le thème de l’article).
On ne s’intéresse donc plus du tout au nom du fichier. Mais un des avantages de tmpfile par rapport à sa cousine mkstemp est qu’elle gère la suppression automatique du fichier à la fermeture. Pratique !

L’exemple encore mis à jour:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main(void)
{
FILE * tmp = tmpfile();

sleep(5);
fprintf(tmp, "%s\n", "pew");
fprintf(tmp, "%s\n", "pew");
sleep(5);
fprintf(tmp, "%s\n", "pew");
fclose(tmp);
return 0;
}

 

Les fichiers temporaires sont un peu les grands héros oubliés de la programmation : ils disparaissent à chaque arrêt (normalement), ce qui fait qu’on les remarque rarement.

Mais tout comme les redshirts dans Star Trek, leur rôle n’en est pas moins primordial ! C’est pour cela qu’il est important de savoir quelles sont les « bonnes pratiques » pour les manipuler.

On pourrait continuer pendant longtemps (par exemple, on a vu ici l’exemple du C, mais comment ça se passe dans d’autres langages ?), mais je vais m’arrêter là pour le moment. 🙂

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.