Existe-t-il un algorithme pour décider si un lien symbolique boucle?

16

Les systèmes Unix génèrent généralement des erreurs s'ils sont confrontés à un chemin qui contient une boucle de liens symboliques ou tout simplement trop de liens symboliques, car ils ont une limite au nombre de liens symboliques qu'ils traverseront dans une recherche de chemin. Mais existe-t-il un moyen de réellement décider si un chemin donné se résout en quelque chose ou contient une boucle, même s'il contient plus de liens qu'un Unix est prêt à suivre? Ou est-ce un problème formellement indécidable? Et si cela peut être décidé, peut-il être décidé dans une quantité raisonnable de temps / mémoire (par exemple sans avoir à visiter tous les fichiers sur un système de fichiers)?

Quelques exemples:

a/b/c/d
where a/b is a symlink to ../e
and e is a symlink to f
and f is a symlink to a/b

a/b/c/d
where a/b/c is a symlink to ../c

a/b/c/d
where a/b/c is a symlink to ../c/d

a/b/c/d
where a/b/c is a symlink to /a/b/e
where a/b/e is a symlink to /a/b/f
where a/b/f is a symlink to /a/b/g

Modifier :

Pour clarifier, je ne demande pas de trouver des boucles dans le système de fichiers, je demande un algorithme de décision qui décide d'un chemin donné s'il se résout en un fichier / répertoire défini ou s'il ne se résout pas du tout. Par exemple, dans le système suivant, il y a une boucle, mais le chemin donné se résout toujours bien:

/ -- a -- b
where b is a symlink to /a

Cette arborescence de répertoires a clairement un cycle, mais le chemin a/b/b/b/b/bse résout toujours bien /a.

JanKanis
la source
Que dit l'outil de ligne de commande readlink ...sur les situations ci-dessus?
slm
1
Demandez-vous si nous pouvons dire simplement à partir du chemin d'accès s'il y a des boucles? Ou pouvons-nous le faire dans un vrai système d'exploitation, en utilisant les outils standard et en vérifiant à quoi les différents composants du nom de chemin se résolvent?
Mike Diehn
@MikeDiehn Évidemment, on ne peut pas dire à partir d'un simple chemin s'il se résout sans faire d'opérations sur le système de fichiers. Mais également avec un environnement de système d'exploitation, il n'est pas simple de distinguer un chemin qui nécessite simplement de traverser un grand nombre de liens symboliques à résoudre d'un chemin qui ne résout pas du tout.
JanKanis

Réponses:

10

Je ne comprends pas bien ce que vous demandez. Si je ne savais pas mieux, je pense que vous demandiez s'il y avait un moyen de détecter cela pendant que vous traitez un fichier. Je ne pense pas que ce soit possible.

La seule méthode que je peux concevoir est de faire une recherche où vous commencez spécifiquement à parcourir une branche particulière de l'arborescence des répertoires.

Exemple

$ tree 
.
`-- a
    `-- b
        |-- c
        |   `-- d
        |       `-- e -> ../../../../a/b
        `-- e -> e

5 directories, 1 file

La findcommande détectera cette boucle mais ne vous en dira pas beaucoup à ce sujet.

$ find -L . -mindepth 15
find: File system loop detected; `./a/b/c/d/e' is part of the same file system loop as `./a/b'.
find: `./a/b/e': Too many levels of symbolic links

J'ai arbitrairement choisi 15 niveaux afin de bloquer toute sortie affichée par le find. Vous pouvez cependant supprimer ce commutateur ( -mindepth) si vous ne vous souciez pas de l'arborescence de répertoires affichée. La findcommande détecte toujours la boucle et s'arrête:

$ find -L . 
.
./a
./a/b
./a/b/c
./a/b/c/d
find: File system loop detected; `./a/b/c/d/e' is part of the same file system loop as `./a/b'.
find: `./a/b/e': Too many levels of symbolic links

Soit dit en passant , si vous souhaitez remplacer la valeur par défaut MAXSYMLINKSqui est apparemment 40 sur Linux (les versions 3.x plus récentes du noyau), vous pouvez voir cette Q&R U&L intitulée: Comment augmenter MAXSYMLINKS .

Utilisation de la commande de liens symboliques

Il existe un outil que les responsables du site FTP pourraient utiliser, appelé, symlinksqui aidera à exposer les problèmes liés aux arbres longs ou pendants qui ont été causés par des liens symboliques.

Dans certains cas, l' symlinksoutil peut également être utilisé pour supprimer les liens incriminés.

Exemple

$ symlinks -srv a
lengthy:  /home/saml/tst/99159/a/b/c/d/e -> ../../../../a/b
dangling: /home/saml/tst/99159/a/b/e -> e

La bibliothèque glibc

La bibliothèque glibc cherche à offrir quelques fonctions C autour de cela, mais je ne connais pas entièrement leur rôle ni comment les utiliser réellement. Je ne peux donc que vous les signaler.

La page de manuel man symlinkmontre la définition de la fonction d'une fonction appelée symlink(). La description va comme ceci:

symlink () crée un lien symbolique nommé newpath qui contient la chaîne oldpath.

L'une des erreurs indique que cette fonction renvoie:

ELOOP Trop de liens symboliques ont été rencontrés lors de la résolution de newpath.

Je vous dirigerai également vers la page de manuel, man path_resolutionqui explique comment Unix détermine les chemins d'accès aux éléments sur le disque. Plus précisément ce paragraphe.

If  the component is found and is a symbolic link (symlink), we first 
resolve this symbolic link (with the current lookup directory as starting 
lookup directory).  Upon error, that error is returned.  If the result is 
not a directory, an ENOTDIR error is returned.  If the resolution of the 
symlink is successful and returns a directory, we set the current lookup
directory to that directory, and go to the next component.  Note that the 
resolution process here involves recursion.  In order  to  protect  the 
kernel against stack overflow, and also to protect against denial of 
service, there are limits on the maximum recursion depth, and on the maximum 
number of symbolic links followed.  An ELOOP error is returned  when  the
maximum is exceeded ("Too many levels of symbolic links").
slm
la source
Si possible, je voudrais un moyen de détecter une boucle de lien symbolique quand on lui donne un seul chemin, et de résoudre les liens symboliques manuellement dans un programme au lieu de laisser le système d'exploitation le faire. Mais je me demande si cela est possible du tout. La solution de recherche semble intéressante, mais avez-vous une idée / comment / trouver détecte les boucles de lien symbolique, et si la méthode qu'elle utilise est complète (c.-à-d. Détecte toutes les boucles possibles et n'identifie pas mal les chemins sans boucle)?
JanKanis
@Somejan - voir mes mises à jour du A. Faites-moi savoir si cela a du sens.
slm
5

OK, après réflexion, je pense avoir une solution claire.

L'idée critique est que si chaque lien faisant partie d'un chemin se résout en quelque chose, alors le chemin entier se résout. Ou l'inverse, si un chemin ne se résout pas, il doit y avoir un lien symbolique spécifique qui nécessite une traversée qui ne se résout pas.

En pensant à ce problème auparavant, j'utilisais un algorithme qui traversait les éléments d'un chemin à partir de la racine, et lorsqu'il rencontrait un lien symbolique, il remplaçait cet élément de chemin par le contenu du lien symbolique, puis continuait à parcourir. Étant donné que cette approche ne se souvient pas du lien symbolique qu'elle est en train de résoudre, elle ne peut pas détecter lorsqu'elle se trouve dans une boucle non résolue.

Si l'algorithme garde la trace du lien symbolique qu'il est en train de résoudre (ou de quels liens symboliques en cas de liens récursifs), il peut détecter s'il tente de résoudre récursivement un lien qu'il est toujours en train de résoudre.

Algorithme:

initialize `location` to the current working directory
initialize `link_contents` to the path we want to resolve
initialize `active_symlinks` to the empty set

def resolve_symlink(location, link_contents, active_symlinks) :
    loop forever:
        next_location = location / [first element of link_contents]
        see if next_location is a symlink.
        if so:
            if next_location in active_symlinks: abort, we have a loop
            location = resolve_symlink(location, readlink(next_location), active_symlinks ∪ {next_location})
        else:
            location = next_location
        strip first element of link_contents
        if link_contents is empty: 
            return location

modifier :

J'ai une implémentation fonctionnelle de cela en python à https://bitbucket.org/JanKanis/python-inotify/src/853ed903e870cbfa283e6ce7a5e41aeffe16d4e7/inotify/pathresolver.py?at=pathwatcher .

JanKanis
la source
3

Python a une fonction appelée networkx.simple_cycles () qui peut être utilisée pour cela. Mais oui, il faudrait lire tous les fichiers du système.

>>> import networkx as nx
>>> G = nx.DiGraph()
>>> G.add_edge('A', 'B')
>>> G.add_edge('B', 'C')
>>> G.add_edge('C', 'D')
>>> G.add_edge('C', 'A')
>>> nx.simple_cycles(G)
[['A', 'B', 'C', 'A']]
Back2Basics
la source
J'ai également pensé à utiliser une sorte d'algorithme de graphe, mais je ne sais pas si une arborescence de répertoires avec des liens symboliques peut être représentée de manière adéquate dans un graphe simple. Dans une arborescence de répertoires abc où c est un lien symbolique vers .., il y a une boucle, mais les chemins comme a / b / c / b / c / b se résolvent toujours car ils ne suivent la boucle qu'un nombre fini de fois et ne le font pas continuer à boucler.
JanKanis
@Somejan: un espace de noms de système de fichiers est un graphe et un nom de fichier est un chemin choisi sur ce graphe.
ninjalj
@ninjalj: Oui, un système de fichiers est un graphe, mais je ne pense pas qu'un nom de fichier soit simplement un chemin sur ce graphe. Le nom de fichier peut être vu comme un ensemble d'instructions sur la façon de parcourir le graphique. Même si le graphique contient des cycles qui ne signifie pas qu'un nom de fichier qui suit ce cycle ne résout pas nécessairement, voir mon exemple dans mon commentaire précédent.
JanKanis
3

Sur un système au repos (c'est-à-dire quand aucun changement n'a lieu), oui, il y a un algorithme. Il existe un nombre fini de liens symboliques, ils constituent donc un graphe fini, et la détection des cycles est un processus finitaire.

Sur un système sous tension, il n'y a aucun moyen de détecter les cycles, car les liens symboliques peuvent changer pendant que le détecteur de cycle fonctionne. La lecture de chaque lien symbolique est atomique, mais suivre un lien symbolique ne l'est pas. Si certains liens symboliques continuent de changer pendant que le noyau effectue la traversée, il pourrait se retrouver sur un chemin infini impliquant des liens distincts.

Gilles 'SO- arrête d'être méchant'
la source
Il existe des moyens d'atténuer ces changements pour atteindre une précision de 98 à 99%. Vous pourriez lui faire prêter attention aux horodatages sur les fichiers et je ne suggérerais pas de suivre les liens. Puisqu'il est récursif à partir de la racine, il trouvera le répertoire réel plus tard.
Back2Basics
1
@ Back2Basics Ces chiffres n'ont aucun sens. Il s'agit d'une interface noyau. Si ça ne marche pas tout le temps, ça ne marche pas, point final.
Gilles 'SO- arrête d'être méchant'
2

Autant que je sache en regardant les sources actuelles du noyau Linux, tout ce que fait le noyau, c'est de compter le nombre de liens qu'il suit, et il se trompe s'il est plus grand qu'un certain nombre. Voir la ligne 1330 dans namei.c pour le commentaire et la nested_symlink()fonction. La macro ELOOP (le numéro d'erreur renvoyé par un read(2)appel système pour cette situation) apparaît à plusieurs endroits dans ce fichier, donc ce n'est peut-être pas aussi simple que de compter les liens suivis, mais c'est sûr à quoi cela ressemble.

Il existe un certain nombre d'algorithmes pour trouver des "cycles" dans des listes chaînées ( algorithme de détection de cycle de Floyd ) ou dans des graphiques dirigés . Pour moi, je ne sais pas lequel il faudrait faire pour détecter une "boucle" ou un "cycle" réel dans un chemin particulier. Dans tous les cas, les algorithmes peuvent prendre du temps à s'exécuter, donc je suppose que le simple fait de compter le nombre de liens symboliques suivis vous permet d'atteindre 90% de votre objectif.

Bruce Ediger
la source
Pour des utilisations pratiques, il suffit de compter le nombre de liens traversés, d'autant plus que c'est ce que fait le noyau, donc même si vous rencontrez un chemin de résolution correcte qui a trop de liens symboliques, vous ne pouvez toujours pas utiliser ce chemin pour quoi que ce soit de pratique ( c'est-à-dire qui n'implique pas la résolution manuelle des liens symboliques)
JanKanis