Continuous Data Protection For GNU/Linux

GNU/Linux Magazine n° 196 | septembre 2016 | Olivier Delhomme
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Continuous Data Protection For GNU/Linux (CDPFGL) est un ensemble de logiciels libres qui permettent de sauvegarder vos fichiers. La principale caractéristique différenciatrice des autres (nombreux) logiciels de sauvegarde existants est que cet ensemble permet la sauvegarde des fichiers en continu pendant qu’ils sont créés et modifiés. Cet article vous présentera les principes et la mise en œuvre des logiciels de ce projet (qui est le mien).
Note

Après avoir exposé les idées et les principes qui régissent le projet, j’aborderai rapidement le principe de la déduplication et expliquerai sa mise en œuvre pour le stockage des données du côté serveur. Nous verrons comment télécharger, compiler et installer les dépendances du projet ainsi que le projet lui-même. Enfin bien que d’après moi le projet ne soit pas mûr pour être mis en production nous verrons comment mettre en œuvre les trois programmes qui le composent.

L’idée de départ provient d’une discussion ayant eu lieu dans une liste d’administrateurs système et réseau. La problématique était le temps nécessaire à une commande rsync pour terminer la liste des fichiers à sauvegarder avant de lancer effectivement cette sauvegarde. Le nombre de fichiers étant de plusieurs dizaines de millions, le temps en question se comptait en heures. S’est posée pour moi la question de savoir si on ne pouvait pas constituer la liste au fil de l’eau et quitte à constituer cette liste autant faire le travail au fur et à mesure en transmettant les fichiers en question ! Ainsi est né le projet de sauvegarde en continu.

Après avoir testé ZMQ et décidé que je n’avais pas besoin d’une telle complexité (et complétude) j’ai choisi HTTP comme méthode de communication entre les clients et le serveur. J’ai décidé d’imposer quelques principes pour borner le fonctionnement du serveur. J’ai choisi d’écrire une interface REST sans état (les grands disent «stateless») achevant la déduplication en ligne, à la source, tout en essayant de préserver la bande passante au maximum. Initialement j’avais dans l’idée d’associer à ce serveur un stockage basé sur Ceph. Finalement, le premier stockage codé est un stockage de fichiers à plat dans une arborescence à plusieurs niveaux. Toutefois le code a été prévu pour qu’il soit possible d’utiliser simplement d’autres systèmes de stockage pour le serveur. De son côté, le client doit être résilient aux connexions / déconnexions et continuer dans la mesure du possible son travail même lorsqu’il ne parvient plus à joindre le serveur.

Il découle de tout cela que le serveur a une empreinte mémoire minimale (le plus possible). En tout état de cause cette empreinte mémoire ne dépend en aucune manière de la volumétrie ou du nombre de blocs « dédupliqués » transmis au serveur (et pas non plus du temps qui passe – du moins j’essaie d’y faire attention).

Le projet est constitué d’un serveur (cdpfglserver) et deux programmes clients, un pour réaliser la sauvegarde des fichiers vers le serveur (cdpfglclient) et l’autre pour faire la restauration des fichiers depuis ce serveur (cdpfglrestore).

À la date de rédaction de l’article, les fonctionnalités de base sont implémentées : sauvegarde en continu (pourvu que l’interface FANOTIFY soit compilée dans le noyau) et restauration d’un fichier avec une sélection possible sur le nom et la date.

1. Comment savoir si l’interface FANOTIFY est disponible sur votre machine ?

Il s’agit de trouver les options qui ont été utilisées pour compiler le noyau Linux de votre distribution. Pour ce faire nous devons partir à la recherche d’un fichier texte contenant ces options. Il y a plusieurs façons de procéder car toutes les distributions n’utilisent pas nécessairement les mêmes mécanismes.

Pour trouver la version de votre noyau, utilisez la commande uname -r. Elle vous donnera un résultat de la sorte : 3.16.0-4-amd64. Regardez alors si vous trouvez un fichier nommé config-3.16.0-4-amd64 dans le dossier /boot.

Si vous ne possédez pas ce fichier, il est possible que votre noyau ait été compilé de sorte que le module configs se charge de garder les options utilisées. Vous pouvez alors exécuter la commande modprobe configs qui chargera le module et donnera accès au fichier /proc/config.gz qui contient, dans un format compressé, les informations de configuration du noyau.

Une fois que vous avez trouvé votre fichier, recherchez-y les informations relatives à FANOTIFY comme suit (remplacez grep par zgrep pour le fichier compressé) :

# grep FANOTIFY /boot/config-3.16.0-4-amd64

CONFIG_FANOTIFY=y

# CONFIG_FANOTIFY_ACCESS_PERMISSIONS is not set

Dans ce cas l’option est à y, c’est-à-dire que le noyau possède l’interface FANOTIFY.

# grep FANOTIFY /boot/config-3.16.0-4-amd64

# CONFIG_FANOTIFY is not set

Dans ce cas l’option n’est pas renseignée et le noyau ne possède pas l’interface FANOTIFY. Il faudra alors compiler un nouveau noyau (non abordé dans cet article) en spécifiant bien y lors du choix de l’activation de cette interface.

2. Le stockage des données par le serveur

2.1 La déduplication

La déduplication est une méthode qui permet de ne garder que les parties non dupliquées d’un fichier, d’un ensemble de fichiers ou d’un ensemble de blocs. Sur un unique fichier, il s’agit de ne garder que les parties « uniques » ou « originales » de ce fichier. Pour ce faire le fichier est généralement découpé en tronçons de taille fixe (des blocs) pour lesquels sont calculés des hashs. À deux hashs identiques correspondent deux blocs très probablement identiques (la probabilité d’identité est si proche de 1 que l’on commet souvent l’abus de langage de dire qu’ils sont identiques). Un fichier peut être représenté par la liste des hashs correspondants aux blocs qui le composent. En ne stockant les blocs aux hashs identiques qu’une seule fois, nous diminuons ainsi la place occupée sur le disque.

Note

Un hash d’un bloc peut être vu comme un résumé de ce bloc. Il est calculé par un algorithme de hachage à partir des données du bloc. Les algorithmes de hachage les plus connus et qu’il ne faut plus utiliser sont MD4, MD5 et SHA1. Aujourd’hui on utilisera plutôt SHA2 [0] et SHA3 [1].

Cette technique est particulièrement intéressante pour la sauvegarde. En effet la déduplication permet d’éviter la copie des blocs qui n’ont pas changé entre deux sauvegardes ou que l’on connaît déjà par ailleurs (sauvegarde de multiples systèmes identiques, fichiers en double, etc.). Dans ce projet elle est réalisée à la source par une communication entre le client et le serveur : le client envoie au serveur la liste des hashs (l’algorithme utilisé est SHA256) des blocs d’un fichier et le serveur lui répond la liste des hashs des blocs dont il a besoin (il s’agit des hashs qu’il ne connaît pas, c’est à dire des blocs inconnus donc originaux). Le client envoie alors les blocs réclamés correspondants.

Pour le moment, il n’y a aucun mécanisme qui permette de détecter un hash identique pour deux blocs aux contenus différents. Il serait intéressant d’avoir un tel mécanisme de détection. En effet la sécurité de SHA256 est réputée être de 2128 ainsi avec 2128+1 blocs nous serions certains d’avoir au moins une collision. C’est-à-dire qu’en terme de stockage, même avec les plus petits blocs de 512 octets, cette certitude représenterait tout de même 144 x 1015 yotta octets ! Toutefois il n’est pas impossible que cette sécurité soit moindre ou que le hasard nous fasse tomber sur une collision. Un tel mécanisme pourrait garantir que l’on sauvegarde bien le bon bloc. Il reste à le concevoir et à l’écrire !

Dans ce projet, cette déduplication réalisée par blocs existe en deux modes : soit la taille des blocs est identique quel que soit le fichier traité (16384 octets par défaut mais vous pouvez choisir votre propre valeur) soit la taille des blocs varie en fonction de la taille du fichier traité (de 512 octets pour les très petits fichiers à 256 Kio pour les fichiers de plus de 130 Mio). En effet, lors de mes essais, il est apparu que le taux d’intra-déduplication (entre les fichiers d’un même système) est plus important lorsque la taille des blocs est petite. Avec cette taille variable, on cherche à augmenter ce taux sans pour autant trop faire exploser le nombre de blocs sauvegardés côté serveur. En effet un fichier de 1 Gio génère 4096 blocs de 256 Kio ou 65536 blocs de 16384 octets selon le mode choisi.

2.2 Mise en cache des modifications

Lorsque le serveur n’est plus joignable, le client enregistre dans sa base de données interne (sqlite) l’ensemble des données et métadonnées des fichiers créés et modifiés. Dès que la connexion est à nouveau disponible, la base de données est vidée petit à petit en parallèle des autres actions.

2.3 Stockage des blocs à plat par le serveur

Le système de stockage des blocs reçus par le serveur est effectué dans des fichiers plats où chaque bloc est inscrit dans un fichier. Pour éviter d’avoir une quantité phénoménale de fichiers dans un même dossier, le serveur initialise une arborescence de dossiers (par défaut sur deux niveaux). Pour chaque dossier de chaque niveau, on crée 256 dossiers de 00 à FF. Par défaut il y a 65536 dossiers au total de 0000 à FFFF. Sur un système de fichiers ext4 rien que cette structure à deux niveaux représente 256 Mio. Par la suite on range les blocs dans le dossier dont l’arborescence correspond aux premiers octets du hash : ainsi le bloc dont le hash est beef3db9718bb771f293a9337181f212ebba690999309fce700e6d0072396324 sera stocké dans le dossier be/ef/ avec une structure à deux niveaux et be/ef/3d/b9 avec 4 niveaux. Le fichier ainsi créé a pour nom son hash au complet.

Un inconvénient d’une telle structure est qu’avec un niveau « élevé » de 4, le temps de construction de la structure est très loin d’être négligeable : un test de création du niveau 3 (64 Gio pour les seuls dossiers vides) sur un SSD s’est déroulé en une heure ! Le logiciel ne permet pas pour le moment d’utiliser plus de 4 niveaux (leur création n’a jamais été testée – avis aux amateurs (prévoyez de la place disque !)).

L’avantage de cette façon de faire c’est que l’on retrouve facilement les blocs, qu’ils sont bien répartis sur l’ensemble de l’arborescence et qu’elle est commune à l’ensemble des clients. Ainsi lorsque des clients ont les mêmes fichiers, leurs blocs ne se trouvent stockés qu’une seule fois sur le serveur.

Les métadonnées des fichiers sont enregistrées dans des fichiers textes à plat. Un fichier est créé par nom de machine. L’avantage de cette méthode est qu’il est facile de vérifier quels sont les fichiers qui ont été sauvegardés sur le serveur. L’inconvénient c’est que pour s’assurer de la cohérence de ce fichier le serveur utilise un thread unique pour écrire dedans. Même si la taille des métadonnées est relativement faible ce thread unique est un goulot d’étranglement.

Le principal inconvénient de cette méthode de stockage est qu’elle ne passe sans doute pas à l’échelle sans avoir recours à des « bidouilles » sur le système de fichiers comme pour la partition des dossiers sur plusieurs stockages physiques séparés. De plus, ce goulot d’étranglement sur le serveur limite sans aucun doute le nombre maximal de clients possible. Cette limite n’a pas été testée et notamment on ne sait pas à partir de combien de clients les performances sont impactées.

3. Installation du projet

Si vous êtes sous Debian vous trouverez des paquets binaires tout prêts mais je vous les déconseille pour le moment (à moins que vous ne soyez un développeur Debian et ayez envie de prendre le projet sous votre aile afin d’aider l’auteur dans sa démarche de création de paquets). Nous allons installer les programmes du projet directement à partir des sources. Quelques dépendances peuvent être installées par le système de paquets de votre distribution car les versions minimales nécessaires ne sont pas récentes. C’est le cas de glib, libcurl, sqlite ou les outils de compilation comme les autotools, gcc, et make. En revanche pour les autres il convient de prendre une version récente soit parce qu’une fonctionnalité le nécessite soit parce qu’un bug a été corrigé et que cela améliore le programme. Voyons comment installer ces dépendances pour Debian 8 et Centos 7. Commençons par les dépendances pour lesquelles la distribution fournit des versions assez récentes :

- Sous Debian vous pouvez taper les commandes suivantes :

$ sudo apt-get install git autoconf automake libtool make libsqlite3-dev libglib2.0-dev libcurl4-gnutls-dev intltool

- Sous Centos la commande est très similaire :

$ sudo yum install git autoconf automake libtool make sqlite-devel glib2-devel libcurl-devel intltool

Il s’agit maintenant d’installer une version récente de deux librairies. Cette partie est normalement identique sous Debian et sous Centos. Installons jansson une librairie C pour JSON. Nous la récupérons avec git puis la compilons de manière classique en prenant soin de générer le script configure :

$ git clone git://github.com/akheron/jansson.git

$ (cd jansson && autoreconf -f -i && ./configure && make && sudo make install)

Nous procédons de même avec la librairie libmicrohttpd qui permet d’embarquer un serveur HTTP dans un code C :

$ wget --quiet -c http://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-0.9.46.tar.gz

$ (tar zxf libmicrohttpd-0.9.46.tar.gz && cd libmicrohttpd-0.9.46 && ./configure && make && sudo make install)

Il reste maintenant à compiler et installer les programmes cdpfglclient, cdpfglserver et cdpfglrestore. La procédure est très classique et parfaitement similaire aux deux précédentes :

$ git clone https://github.com/dupgit/sauvegarde.git

$ (cd sauvegarde && ./autogen.sh && ./configure && make && sudo make install)

Les programmes se sont installés dans /usr/local/bin et les fichiers de configuration se trouvent dans /usr/local/etc/cdpfgl.

Recommencez cette installation sur toutes les machines sur lesquelles vous désirez installer le client. Dans la suite on admet que le serveur est installé sur une machine nommée save et que les clients sont installés sur d’autres machines de ce réseau (qui ne filtre pas les requêtes HTTP).

4. Mise en œuvre du projet

4.1 Le serveur

Sur la machine qui fera fonctionner le serveur, dans le dossier de configuration /usr/local/etc/cdpfgl copiez le fichier server.conf en server.prod et éditez ce dernier (en cas de réinstallation vous ne perdrez pas votre configuration). Il convient d’indiquer où le serveur devra enregistrer les blocs et les métadonnées (sans indication, le chemin par défaut est /var/tmp/cdpfgl). Pour cela éditez la valeur de file-directory dans la section [File_Backend] et indiquez le chemin du dossier qui contiendra les données (par exemple /home/dup/cdpfgl). Prenez garde à ce que l’utilisateur sous lequel vous ferez fonctionner le serveur puisse écrire à cet endroit (le serveur n’a pas besoin des droits root). Si le dossier n’existe pas, il sera créé ainsi que la sous-arborescence pour stocker les blocs. En fonction de votre système de stockage, cela peut prendre plus ou moins de temps. Soyez patient, par exemple un niveau 2 est créé en un peu moins de 2 minutes sur un Bananapi M1. Les autres valeurs par défaut sont correctes pour la réalisation de tests. En production on veillera sans doute à désactiver le mode debug : debug-mode=false.

Le serveur se lance avec la commande cdpfglserver -c /usr/local/etc/cdpfgl/server.prod. Dans le cas où l’option debug-mode est toujours positionnée sur true, le serveur vous indique ce qu’il fait à tout moment. Au démarrage il devrait vous indiquer qu’il créé l’arborescence toutefois s’il n’indique rien c’est que cette arborescence a déjà été créée.

4.2 Le client

Maintenant que le serveur est installé et fonctionnel, intéressons-nous aux clients. Vous devez compiler le projet sur chacune des machines clientes. Dans le dossier de configuration /usr/local/etc/cdpfgl copiez les fichiers client.conf et restore.conf respectivement en client.prod et restore.prod et éditez-les.

Dans la section [Server] des deux fichiers indiquez l’adresse IP de la machine sur laquelle vous avez installé le serveur (à ce stade vous en avez fini avec la configuration du fichier restore.prod).

Dans la section [Client] du fichier client.prod, avec la clef directory-list indiquez les dossiers que vous souhaitez sauvegarder (séparés par des points-virgules). Pour le moment la liste doit être explicite. Les expressions rationnelles ne sont pas encore autorisées dans cette liste. La recherche s’effectuant par préfixe, tout sous-dossier d’un dossier indiqué dans cette liste sera automatiquement sauvegardé. Indiquez dans la clef exclude-list les dossiers ou fichiers que vous souhaitez exclure de la sauvegarde (là encore séparés par des points-virgules). Les expressions rationnelles simples fonctionnent sur cette liste. Par exemple, pour exclure tous les fichiers dont l’extension est mp3 on pourra écrire .mp3$.

La clef cache-directory permet d’indiquer un dossier où seront stockées les métadonnées et les données en cache (en cas de perte de connexion au serveur). Indiquez ici un dossier dans un système de fichier où vous avez de la place et où vous ne risquez pas de bloquer le système. Pour quelques menus tests la valeur par défaut peut convenir mais pour des tests plus poussés ou de la production il faudra préférer une partition non système où il y a suffisamment de place libre.

Par défaut le mode de calcul des blocs des fichiers est adaptatif (adaptive=true). Pour pouvoir utiliser une taille fixe, passez cette clef à false et indiquez une taille (en octets) dans la clef blocksize. Vous n’avez probablement pas besoin de changer les autres valeurs par défaut. Sachez toutefois qu’en mode adaptatif la taille du buffer de communication qui correspond à la clef buffersize est également adaptative en partie et fixée à 2 Mio pour les fichiers de moins de 128 Mio et de 4 Mio au-delà.

Pour pouvoir utiliser l’interface FANOTIFY le client à besoin d’être lancé sous l’utilisateur « root ». De toute manière, si l’on souhaite sauvegarder un système complet il faut bien pouvoir traverser tous les fichiers et tous les dossiers de chacun des utilisateurs, qu’ils soient utilisateurs normaux ou système. Lancez le client en utilisant la commande cdpfglclient -c /usr/local/etc/cdpfgl/client.prod. Le client scanne immédiatement les dossiers et sous-dossiers que vous lui avez demandé de sauvegarder. Lorsqu’ils n’ont pas déjà été sauvegardés, il initie un dialogue avec le serveur pour réaliser cette sauvegarde. Il est aussi prêt à sauvegarder tout fichier qui viendrait à être créé ou modifié dans les dossiers indiqués. Par défaut, en mode debug, il indique tout ce qu’il fait.

4.3 La restauration d’un fichier

Le programme cdpfglrestorequi a en charge la restauration souffre de limitations qui devraient disparaître dans les prochaines versions. En effet, la roadmap prévoit de nouvelles fonctionnalités pour ce programme dans les versions 0.0.9 et 0.0.10 (à la date où ces lignes sont écrites la version 0.0.8 est pratiquement dans les cartons). Malgré ses limitations, le programme permet la restauration d’un fichier. Utilisez l’option --help qui vous donnera l’ensemble des options utilisables par le programme. L’invocation du programme nécessite un serveur en fonctionnement. L’option -l truc permet de lister les fichiers ou dossiers dont le nom contient truc. Vous pouvez limiter la liste ainsi obtenue en indiquant l’option -a ladate pour obtenir uniquement les fichiers ayant une date postérieure à ladate et -b uneautredate pour obtenir uniquement ceux dont la date est antérieure à uneautredate. Si vous remplacez l’option -l truc par -r truc vous restaurerez le dernier fichier de la liste précédemment obtenue. Si vous souhaitez restaurer ce fichier à un endroit précis indiquez le dossier avec l’option -w /nom/du/dossier. Une session de restauration pourrait ressembler à :

$ cdpfglrestore -c /usr/local/etc/cdpfgl/restore.prod -l options.c

[FILE] 2014-07-16 15:43:38 +0200 /home/dup/Dossiers_Perso/projets/projets_externes/fio/goptions.c

[FILE] 2015-04-06 21:31:34 +0200 /home/dup/Dossiers_Perso/projets/projets_externes/fio/options.c

[FILE] 2015-11-07 22:11:13 +0100 /home/dup/Dossiers_Perso/projets/sauvegarde/monitor/options.c

[FILE] 2015-11-21 21:09:04 +0100 /home/dup/Dossiers_Perso/projets/sauvegarde/monitor/options.c

[FILE] 2015-10-25 22:35:12 +0100 /home/dup/Dossiers_Perso/projets/sauvegarde/restaure/options.c

[FILE] 2015-10-25 22:35:12 +0100 /home/dup/Dossiers_Perso/projets/sauvegarde/serveur/options.c

[FILE] 2015-11-07 22:11:13 +0100 /home/dup/tmp/sauvegarde-0.0.6/monitor/options.c

[FILE] 2015-10-25 22:35:12 +0100 /home/dup/tmp/sauvegarde-0.0.6/restaure/options.c

[FILE] 2015-10-25 22:35:12 +0100 /home/dup/tmp/sauvegarde-0.0.6/serveur/options.c

$ cdpfglrestore -c /usr/local/etc/cdpfgl/restore.prod -l options.c -a 2015-10-26 -b 2015-11-20

[FILE] 2015-11-07 22:11:13 +0100 /home/dup/Dossiers_Perso/projets/sauvegarde/monitor/options.c

[FILE] 2015-11-07 22:11:13 +0100 /home/dup/tmp/sauvegarde-0.0.6/monitor/options.c

$ cdpfglrestore -c /usr/local/etc/cdpfgl/restore.prod -r /home/dup/tmp/sauvegarde-0.0.6/monitor/options.c -d 0 -a 2015-10-26 -b 2015-11-20 -w /tmp

[files.c, 505] Erreur ou avertissement pour le fichier (/home/dup/tmp/sauvegarde-0.0.6/monitor/options.c) : La définition de l’attribut standard::type n’est pas prise en charge

$ ls -ls /tmp/options*

20 -rw-r--r-- 1 dup dup 16926 nov.   7 22:11 /tmp/options.c

$ stat /tmp/options.c

  Fichier : « /tmp/options.c »

   Taille : 16926       Blocs : 40         Blocs d’E/S : 4096   fichier

Périphérique : fe00h/65024d     Inœud : 135505      Liens : 1

Accès : (0644/-rw-r--r--)  UID : ( 1000/     dup)   GID : ( 1000/     dup)

 Accès : 2015-11-18 20:48:54.563022000 +0100

Modif. : 2015-11-07 22:11:13.207030000 +0100

Changt : 2016-04-26 23:10:29.207030693 +0200

  Créé : -

L’avertissement que l’on note lors de la restauration est « normal » : il s’agit d’un avertissement de la librairie glib qui renseigne sur l’impossibilité d’indiquer la valeur de l’attribut standard::type car le système de fichiers ne le prend pas en compte. Pas d’inquiétude, le fichier est néanmoins bien restauré avec les bons attributs comme les commandes ls et stat l’indiquent.

Conclusion

Ce projet est une formidable opportunité pour apprendre et tenter de mettre en fonctionnement des tas de « nouveautés » pour moi. Vous l’aurez compris sans peine à la lecture de l’article, il reste encore beaucoup à faire avant d’obtenir un ensemble de logiciels satisfaisant qui puisse sauvegarder en continu puis restaurer vos données rapidement et en toute confiance.

Bien entendu, s’agissant de logiciels libres, si le projet vous intéresse, vous êtes particulièrement encouragés à participer de quelque manière que ce soit – en codant dans le noyau Linux pour ajouter les événements manquants à l’interface FANOTIFY, en ajoutant au client l’interface de notification de votre système préféré, en ajoutant une traduction ou en chiffrant les communications entre les clients et le serveur (qui a dit HTTPS ?), ...

N’ayant rien d’autre à offrir que la liberté de mon projet, je prends garde à ce que les contributions soient rendues à leurs auteurs respectifs. À la racine du projet, vous trouverez un fichier AUTHORS à jour contenant l’ensemble des auteurs ayant déjà contribué. Par ailleurs, j’indique également les noms des contributeurs à une version donnée dans le fichier NEWS (également à la racine) qui me sert à écrire ce que je souhaite raconter sur les différentes versions lors de leur sortie.

Références

[0] SHA2 : https://en.wikipedia.org/wiki/SHA-2

[1] SHA3 : https://en.wikipedia.org/wiki/SHA-3

Remerciements

Je tiens à remercier toutes les personnes qui œuvrent pour le logiciel libre, toutes celles qui contribuent directement ou indirectement à un logiciel libre et l’ensemble des logiciels libres que j’utilise pour mon projet (sqlite, glib, jansson, curllibmicrohttpd, autotools, pkg-config, ...). Parmi ceux-là, je tiens à remercier plus particulièrement Christian Grothoff du projet libmicrohttpd pour sa réactivité à corriger un bug sur lequel j’étais tombé par mon utilisation intensive et répétitive de requêtes POST. Si tous les mainteneurs de logiciels libres pouvaient prendre exemple il y aurait certainement plus de contributeurs. Encore merci !

Tags : sauvegarde