Synchronisation de fichiers

Il arrive fréquemment qu'on ait plusieurs copies d'un fichier, voir d'un répertoire, sur des ordinateurs différents. Par exemple, on peut avoir ses documents de travail sur son portable et garder une copie sur clipper en cas de problème (c'est même fortement recommandé!). De temps en temps, il faut copier les documents de travail du portable vers clipper. Il s'agit donc de réaliser un miroir. Pour gagner du temps, on souhaite éviter de copier les documents non modifiés. Le problème est plus ardu quand on a modifié des fichiers sur les deux ordinateurs. Il faut alors faire des copies dans les deux sens pour unifier les deux répertoires. Parfois, ce n'est pas possible (exemple: un même fichier a été modifié de deux manières différentes sur les deux ordinateurs). Dans ce cas, il faut ne pas faire d'action inconsidérée mais plutôt prévenir l'utilisateur et le laisser régler le conflit à la main. C'est le problème de la synchronisation de fichiers.

On va dans ce TP essayer de comprendre comment peut fonctionner un tel système et l'implanter en Perl.

Exercices

Parcours d'arborescence

1) Faites un script Perl qui prend un nom de répertoire en argument et le parcours récursivement en affichant le nom de tous les fichiers qu'il contient (c.f. opendir, readdir et closedir, voir aussi glob). Attention aux répertoires spéciaux . et ..!

Corrections: version avec readdir: dir1.pl, version alternative qui n'échoue pas en cas de répertoire inaccessible: dir1bis.pl, version avec glob: dir1ter.pl.

Miroir

On suppose dans un premier temps qu'on ne fait que des communications à sens unique: d'un répertoire de travail B vers un répertoire archive A. Les fichiers de A ne sont jamais modifiés directement par l'utilisateur.

Une première idée pour détecter les fichiers modifiés est d'utiliser la date de modification (voir stat). Toutefois, ce n'est pas toujours très fiable. Une meilleur solution est de fabriquer avec le contenu du fichier une signature courte mais (on l'espère) unique. On propose ici d'utiliser comme signature la fonction de hachage SHA-1 (sans collision connue au début 2007). On pourra, par exemple, faire appel à la commande Unix sha1sum(1) via l'opérateur back-tick ` ` de Perl.

2) Modifiez le script précédent pour qu'il calcule la signature des fichiers de A et de B (en incluant comme avant tous les sous-répertoires). On suggère la construction d'une table de hachage %tableA qui associe à chaque fichier de A sa signature, et une table similaire pour les fichiers de B.

Faites également une procédure qui, en comparant les deux tables, détermine une séquence minimale de copies (de B vers A) et de suppressions (dans A) qui rend A identique à B.

Corrections: en utilisant des variables globales: mirroir2.pl, et en passant des références en argument: mirroir2bis.pl (voir perlref et perlsub).

3) On veut maintenant que le script effectue ces opérations si l'option --doit est passée en argument. Si --doit n'est pas passée en argument, alors le programme se contente d'afficher la liste des actions à faire sans les exécuter. Ceci permet à l'utilisateur de vérifier que le programme ne risque pas de faire de bêtise.

Une opération de copie robuste n'étant pas si triviale à programmer (ce sera le sujet d'un autre TP), vous pourrez utiliser une solution toute faite comme le module File::Copy ou le programme cp. L'effacement d'un fichier est par contre facile grâce à la commande Perl unlink. En cas d'échec d'une opération, il est souhaitable de continuer après avoir alerté l'utilisateur (warn).

Correction: mirroir3.pl.

4) En fait, il est inutile de recalculer à chaque fois la liste de signatures de A puisque ne change pas entre deux synchronisations. On appellera cette liste l'état de A. Il sera stocké sous forme de fichier texte entre deux invocations du script. Modifiez votre programme pour qu'il charge au démarrage l'état de A, si disponible, et le calcule sinon. Le programme devra aussi sauvegarder le nouvel état de A avant de quitter.

Correction: mirroir4.pl.

5) Modifiez votre programme pour qu'il mette à jour, en plus du contenu des fichiers, les méta-données: droits d'accès, propriétaires (utilisateur et groupe), date de dernière modification, etc. (voir stat). Gérez aussi le cas où seules les méta-données ont besoin d'être mises à jour (on éviter dans ce cas une copie inutile).

Sous quelles conditions est-il possible de respecter le propriétaire d'un fichier?

Correction: mirroir5.pl. Seul root peut changer arbitrairement le propriétaire d'un fichier. Un autre utilisateur, si il est propriétaire du fichier, peut changer le groupe propriétaire du fichier en un des groupes auquel lui-même appartient (voir groups(1) et chown(2)).

6) Vérifiez que votre programme se comporte bien en présence de fichiers spéciaux (liens symboliques et durs, fichiers device, pipes nommés, sockets). Une première approche consiste à suivre les liens et ignorer les autres types de fichiers spéciaux. Une deuxième, plus complexe à mettre en œuvre, consiste à créer un fichier spécial de même type et avec les même attributs (destination d'un lien, numéro de device, etc). La fonction lstat et la commande mknod(1) seront utiles ici. Dans tous les cas, il ne faut surtout pas essayer de copier ou de calculer la signature d'un fichier device, pipe nommé ou socket!

Unification

Cette fois A et B ont des rôles symétriques. On appelle EA et EB l'état de A et de B juste après la dernière synchronisation, et EA' et EB' l'état courant de A et de B. On a en particulier EA=EB.

7) Écrivez un script qui:

Correction: mirroir7.pl.

Réseau

On a supposé dans tout ce qui précède que notre programme a un accès local aux deux répertoires, en laissant de côté le problème de la synchronisation entre deux ordinateurs distants. Ceci nécessite en effet une architecture client / serveur et des communications par réseau qui ne sont pas le sujet de ce TP.

Un peu de culture

Le problème que nous venons d'étudier a fait l'objet de longues recherches. Il existe donc de nombreux logiciels de synchronisation de fichiers. Nous n'en citons ici que deux:

Les systèmes de gestion de versions exploitent également la synchronisation de fichiers pour permettre à plusieurs utilisateurs de travailler sur une même arborescence. Dans ce cas, il existe une version principale des fichiers, sur un serveur en lecture seule, tandis que chaque utilisateur travaille sur une copie locale personnelle. Quand un utilisateur est satisfait, il peut transmettre ses modifications au serveur et elles seront intégrées à la version principale. Les autres utilisateurs les recevront alors automatiquement lors de leur prochaine synchronisation. La synchronisation peut engendrer des conflits qui doivent alors être résolues localement par l'utilisateur concerné. De plus, le serveur sert d'archive: il stocke une histoire complète des modifications. Ceci permet de restaurer tout état antérieur de la version principale.

Parmi les gestionnaires de versions les plus utilisés, citons:




Antoine Miné