Esiste un algoritmo per decidere se un collegamento simbolico passa?


16

I sistemi Unix di solito si limitano ad errori se si trovano di fronte a un percorso che contiene un ciclo di collegamenti simbolici o troppi collegamenti simbolici, poiché hanno un limite al numero di collegamenti simbolici che attraverseranno nella ricerca di un percorso. Ma c'è un modo per decidere se un determinato percorso si risolve in qualcosa o contiene un ciclo, anche se contiene più collegamenti di quelli che un unix è disposto a seguire? O è un problema formalmente indecidibile? E se può essere deciso, può essere deciso in un ragionevole lasso di tempo / memoria (ad es. Senza dover visitare tutti i file su un filesystem)?

Qualche esempio:

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

Modifica :

Per chiarire, non sto chiedendo di trovare loop nel file system, sto chiedendo un algoritmo decisionale che decide di un determinato percorso se si risolve in un file / directory definito o se non si risolve affatto. Ad esempio nel seguente sistema, esiste un ciclo, ma il percorso specificato si risolve comunque correttamente:

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

Questo albero di directory ha chiaramente un ciclo, ma il percorso a/b/b/b/b/bsi risolve ancora bene /a.


Che cosa dice lo strumento da riga di comando readlink ...sulle situazioni precedenti?
slm

1
Ci stai chiedendo se possiamo dire solo dal nome del percorso se ci sono anelli? Oppure possiamo farlo in un vero sistema operativo, usando gli strumenti standard e controllando a cosa risolvono i vari componenti del percorso?
Mike Diehn,

@MikeDiehn Ovviamente non si può dire da un solo percorso se si risolve senza eseguire operazioni sul filesystem. Ma anche con un ambiente OS non è semplice distinguere un percorso che richiede semplicemente di attraversare molti collegamenti simbolici da risolvere da uno che non si risolve affatto.
JanKanis,

Risposte:


10

Non capisco perfettamente cosa stai chiedendo. Se non avessi saputo nulla di meglio, penso che mi stavi chiedendo se c'era un modo per rilevarlo mentre si trattava di gestire un file. Non credo sia possibile.

L'unico metodo che riesco a concepire è fare una ricerca in cui inizi specificamente a cercare un determinato ramo nella struttura di directory.

Esempio

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

5 directories, 1 file

Il findcomando rileverà questo loop ma non ti dirà molto su di esso.

$ 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

Ho scelto arbitrariamente 15 livelli in modo da bloccare qualsiasi output visualizzato dal find. Puoi comunque rilasciare quel parametro ( -mindepth) se non ti interessa l'albero della directory visualizzato. Il findcomando rileva ancora il loop e si ferma:

$ 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

Per inciso, se si desidera sovrascrivere il valore predefinito MAXSYMLINKSapparentemente 40 su Linux (versioni 3.x più recenti del kernel) è possibile vedere queste domande e risposte U&L intitolate: Come aumentare MAXSYMLINKS .

Utilizzando il comando symlink

Esiste uno strumento che i manutentori del sito FTP potrebbero utilizzare chiamato symlinksche aiuterà a esporre i problemi con alberi lunghi o penzolanti causati da collegamenti simbolici.

In alcuni casi lo symlinksstrumento potrebbe essere utilizzato anche per eliminare collegamenti offensivi.

Esempio

$ 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 libreria glibc

La libreria glibc sembra offrire alcune funzioni C in questo senso, ma non conosco del tutto il loro ruolo o come usarle effettivamente. Quindi posso solo indicarteli.

La pagina man, man symlinkmostra la definizione della funzione per una funzione chiamata symlink(). La descrizione va così:

symlink () crea un collegamento simbolico chiamato newpath che contiene la stringa oldpath.

Uno degli errori indica che questa funzione restituisce:

ELOOP Troppi collegamenti simbolici sono stati riscontrati nella risoluzione di newpath.

Ti indirizzerò anche alla pagina man, man path_resolutionche discute come Unix determina i percorsi verso gli elementi sul disco. Nello specifico questo paragrafo.

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").

Se possibile, vorrei un modo per rilevare un ciclo di collegamenti simbolici quando viene fornito un singolo percorso e risolvere manualmente i collegamenti simbolici in un programma invece di lasciare che il sistema operativo lo faccia. Ma mi chiedo se questo sia possibile. La soluzione find sembra interessante, ma hai idea / come / find rileva loop symlink e se il metodo che utilizza è completo (ovvero rileva tutti i loop possibili e non identifica erroneamente alcun percorso non loop)?
JanKanis,

@Somejan - guarda i miei aggiornamenti su A. Fammi sapere se ha senso.
slm

5

OK, dopo qualche altro pensiero penso di avere una soluzione chiara.

L'intuizione critica è che se ogni collegamento che fa parte di un percorso si risolve in qualcosa, l'intero percorso si risolve. O viceversa, se un percorso non si risolve, allora deve esserci un collegamento simbolico specifico che richiede un attraversamento che non si risolve.

Mentre pensavo a questo problema in precedenza stavo usando un algoritmo che attraversava elementi di un percorso partendo dalla radice e quando ha incontrato un collegamento simbolico ha sostituito quell'elemento percorso con il contenuto del collegamento simbolico e poi ha continuato ad attraversare. Poiché questo approccio non ricorda quale collegamento simbolico sta attualmente risolvendo, non è in grado di rilevare quando si trova in un ciclo non risolto.

Se l'algoritmo tiene traccia di quale collegamento simbolico sta attualmente risolvendo (o quali collegamenti simbolici in caso di collegamenti ricorsivi), può rilevare se sta tentando di risolvere nuovamente un collegamento ricorsivamente che è ancora occupato a risolvere.

Algoritmo:

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

modifica :

Ho un'implementazione funzionante di questo in Python su https://bitbucket.org/JanKanis/python-inotify/src/853ed903e870cbfa283e6ce7a5e41aeffe16d4e7/inotify/pathresolver.py?at=pathwatcher .


3

Python ha una funzione chiamata networkx.simple_cycles () che può essere usata per questo. Ma sì, dovrebbe leggere tutti i file sul sistema.

>>> 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']]

Ho anche pensato di utilizzare un qualche tipo di algoritmo grafico, ma non sono sicuro che un albero di directory con collegamenti simbolici possa essere adeguatamente rappresentato in un semplice grafico. In un albero di directory abc in cui c è un collegamento simbolico a .., esiste un ciclo, ma i percorsi come a / b / c / b / c / b continuano a risolversi poiché seguono il ciclo solo un numero finito di volte e non continua ad andare in loop.
JanKanis,

@Somejan: uno spazio dei nomi del filesystem è un grafico e un nome file è un percorso scelto su quel grafico.
ninjalj,

@ninjalj: Sì, un filesystem è un grafico, ma non credo che un nome file sia semplicemente un percorso su quel grafico. Il nome file può essere visto come una serie di istruzioni su come attraversare il grafico. Anche se il grafico contiene cicli che non significano che un nome file che segue quel ciclo non si risolve necessariamente, vedi il mio esempio nel mio commento precedente.
JanKanis,

3

Su un sistema quiescente (ovvero quando non si verificano cambiamenti), sì, esiste un algoritmo. Esiste un numero finito di collegamenti simbolici, quindi costituiscono un grafico finito e rilevare i cicli è un processo finanziario.

Su un sistema attivo, non è possibile rilevare i cicli, poiché i collegamenti simbolici possono cambiare mentre il rilevatore di cicli è in funzione. La lettura di ogni collegamento simbolico è atomica, ma non seguire un collegamento simbolico. Se alcuni collegamenti simbolici continuano a cambiare mentre il kernel sta eseguendo l'attraversamento, potrebbe finire su un percorso infinito che coinvolge collegamenti distinti.


Esistono modi per mitigare tali modifiche per portarlo con un'accuratezza del 98-99%. Potresti farlo prestare attenzione ai timestamp sui file e non suggerirei effettivamente di seguire i link. Poiché è ricorsivo dalla radice, troverà la directory effettiva in un secondo momento.
Back2Basics,

1
@ Back2Basics Questi numeri sono completamente privi di significato. Questa è un'interfaccia del kernel. Se non funziona sempre, non funziona, punto.
Gilles 'SO- smetti di essere malvagio' il

2

Per quanto posso dire osservando le attuali fonti del kernel Linux, tutto il kernel fa è tenere conto del numero di collegamenti che viene seguito ed errori se è maggiore di un numero. Vedere la riga 1330 in namei.c per il commento e la nested_symlink()funzione. La macro ELOOP (il numero di errore restituito da una read(2)chiamata di sistema per questa situazione) viene visualizzata in diverse posizioni in quel file, quindi potrebbe non essere semplice come il conteggio dei collegamenti seguiti, ma è sicuro che aspetto ha.

Esistono numerosi algoritmi per la ricerca di "cicli" negli elenchi collegati ( algoritmo di rilevamento del ciclo di Floyd ) o nei grafici diretti . Non mi è chiaro quale dovresti fare per rilevare un vero "loop" o "ciclo" in un determinato percorso. In ogni caso, l'esecuzione degli algoritmi potrebbe richiedere molto tempo, quindi immagino che il solo conteggio del numero di collegamenti simbolici seguiti ti permetta di raggiungere il 90% del tuo obiettivo.


Per usi pratici, solo il conteggio del numero di collegamenti attraversati va bene, soprattutto perché è quello che fa il kernel, quindi anche se incontri un percorso che si risolve correttamente con troppi collegamenti simbolici, non puoi ancora usare quel percorso per qualcosa di pratico ( cioè che non implica la risoluzione manuale dei
collegamenti
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.