Apakah ada algoritma untuk memutuskan apakah suatu symlink loop?

16

Sistem Unix biasanya hanya error keluar jika mereka dihadapkan dengan path yang berisi loop symlink atau terlalu banyak symlink, karena mereka memiliki batas jumlah symlink yang akan mereka lalui dalam pencarian satu path. Tetapi apakah ada cara untuk benar-benar memutuskan apakah jalur yang diberikan menyelesaikan sesuatu atau berisi loop, bahkan jika itu berisi lebih banyak tautan daripada yang ingin diikuti oleh unix? Atau apakah ini masalah yang secara formal tidak dapat diputuskan? Dan jika dapat diputuskan, dapatkah diputuskan dalam jumlah waktu / memori yang wajar (mis. Tanpa harus mengunjungi semua file pada sistem file)?

Beberapa contoh:

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

Edit :

Untuk memperjelas, saya tidak bertanya tentang menemukan loop dalam sistem file, saya bertanya tentang algoritma keputusan yang memutuskan jalur yang diberikan apakah itu memutuskan untuk file / direktori yang pasti atau apakah itu tidak menyelesaikan sama sekali. Misalnya dalam sistem berikut, ada loop, tetapi jalur yang diberikan masih terselesaikan dengan baik:

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

Pohon direktori ini jelas memiliki siklus, tetapi jalan a/b/b/b/b/bmasih diselesaikan dengan baik /a.

JanKanis
sumber
Apa yang dikatakan oleh alat baris perintah readlink ...tentang situasi di atas?
slm
1
Apakah Anda bertanya apakah kami dapat mengetahui hanya dari pathname jika ada loop? Atau bisakah kita melakukan ini dalam sistem operasi nyata, menggunakan alat standar dan memeriksa apa yang dipecahkan oleh berbagai komponen pathname?
Mike Diehn
@ MikeDiehn Jelas tidak ada yang tahu dari jalur jika itu diselesaikan tanpa melakukan operasi sistem file. Tetapi juga dengan lingkungan OS, tidak mudah untuk membedakan jalur yang hanya memerlukan melintasi banyak symlink untuk menyelesaikan dari yang tidak menyelesaikan sama sekali.
JanKanis

Jawaban:

10

Saya tidak sepenuhnya mengerti apa yang Anda minta. Jika saya tidak tahu apa-apa, saya pikir Anda bertanya apakah ada cara untuk mendeteksi ini ketika sedang berurusan dengan file. Saya tidak percaya ini mungkin.

Satu-satunya metode yang dapat saya bayangkan adalah melakukan pencarian di mana Anda secara khusus mulai mencari melalui cabang tertentu di pohon direktori.

Contoh

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

5 directories, 1 file

The findperintah akan mendeteksi lingkaran ini, tetapi tidak benar-benar memberitahu Anda secara keseluruhan banyak tentang hal itu.

$ 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

Saya sewenang-wenang memilih 15 level untuk memblokir setiap output yang ditampilkan oleh find. Namun Anda dapat menjatuhkan sakelar itu ( -mindepth) jika Anda tidak peduli tentang susunan direktori yang ditampilkan. The findperintah masih mendeteksi loop dan berhenti:

$ 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

Secara kebetulan, jika Anda ingin mengganti default MAXSYMLINKSyang tampaknya 40 di Linux (versi 3.x kernel yang lebih baru), Anda dapat melihat U&L T&J ini berjudul: Bagaimana Anda meningkatkan MAXSYMLINKS .

Menggunakan perintah symlinks

Ada alat yang disebut oleh pengelola situs FTP symlinksyang akan membantu memaparkan masalah dengan alat pohon yang panjang atau menggantung yang disebabkan oleh tautan simbolis.

Dalam kasus tertentu symlinksalat ini dapat digunakan untuk menghapus tautan yang menyinggung juga.

Contoh

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

Perpustakaan glibc

Pustaka glibc tampaknya menawarkan beberapa fungsi C di sekitar ini, tapi saya tidak sepenuhnya tahu peran mereka atau bagaimana cara menggunakannya. Jadi saya hanya bisa menunjukkannya kepada Anda.

Halaman manual, man symlinkmenunjukkan definisi fungsi untuk fungsi yang disebut symlink(). Uraiannya seperti ini:

symlink () membuat tautan simbolis bernama newpath yang berisi string oldpath.

Salah satu kesalahan menyatakan bahwa fungsi ini mengembalikan:

ELOOP Terlalu banyak tautan simbolik yang ditemukan dalam menyelesaikan jalur baru.

Saya juga akan mengarahkan Anda ke halaman manual, man path_resolutionyang membahas bagaimana Unix menentukan jalur ke item pada disk. Khususnya paragraf ini.

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
sumber
Jika memungkinkan saya ingin cara untuk mendeteksi loop symlink ketika diberi jalur tunggal, dan menyelesaikan symlink secara manual dalam suatu program alih-alih membiarkan OS melakukannya. Tetapi saya bertanya-tanya apakah ini mungkin sama sekali. Solusi find terlihat menarik, tetapi apakah Anda memiliki ide / bagaimana / temukan mendeteksi loop symlink, dan jika metode yang digunakannya lengkap (yaitu mendeteksi semua loop yang mungkin dan tidak salah mengidentifikasi jalur non-looping)?
JanKanis
@Omejan - lihat pembaruan saya ke A. Beri tahu saya jika itu masuk akal.
slm
5

OK, setelah beberapa pemikiran lagi saya pikir saya punya solusi yang jelas.

Wawasan kritisnya adalah bahwa jika setiap tautan yang merupakan bagian dari jalur menyelesaikan sesuatu, maka keseluruhan jalur tersebut menyelesaikannya. Atau sebaliknya, jika jalan tidak menyelesaikan maka harus ada symlink khusus yang memerlukan melintasi yang tidak menyelesaikan.

Sambil memikirkan masalah ini sebelumnya, saya menggunakan algoritme yang menelusuri elemen jalur mulai dari root, dan ketika menemui symlink, ia mengganti elemen path tersebut dengan konten symlink lalu melanjutkan traverse. Karena pendekatan ini tidak mengingat symlink mana yang sedang diselesaikan, ia tidak dapat mendeteksi ketika ia berada dalam loop yang tidak terselesaikan.

Jika algoritme melacak symlink mana yang saat ini sedang diselesaikan (atau symlink mana dalam hal tautan rekursif), ia dapat mendeteksi jika berusaha menyelesaikan kembali tautan secara rekursif yang masih sibuk diselesaikan.

Algoritma:

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

sunting :

Saya memiliki implementasi yang berfungsi dalam python di https://bitbucket.org/JanKanis/python-inotify/src/853ed903e870cbfa283e6ce7a5e41aeffe16d4e7/inotify/pathresolver.py?at=pathwatcher .

JanKanis
sumber
3

Python memiliki fungsi yang disebut networkx.simple_cycles () yang dapat digunakan untuk ini. Tapi ya itu perlu membaca setiap file di sistem.

>>> 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
sumber
Saya juga berpikir tentang menggunakan semacam algoritma grafik, tapi saya tidak yakin apakah pohon direktori dengan symlink dapat diwakili secara memadai dalam grafik sederhana. Di pohon direktori abc di mana c adalah symlink ke .., ada sebuah loop, tetapi path seperti a / b / c / b / c / b masih menyelesaikan karena mereka hanya mengikuti loop beberapa kali dan tidak terus berulang.
JanKanis
@Omejan: namespace filesystem adalah grafik, dan nama file adalah jalur yang dipilih di atas grafik itu.
ninjalj
@ninjalj: Ya sistem file adalah grafik, tapi saya tidak berpikir nama file hanyalah jalan di atas grafik itu. Nama file dapat dilihat sebagai seperangkat instruksi tentang cara melintasi grafik. Bahkan jika grafik berisi siklus yang tidak berarti bahwa nama file yang mengikuti siklus itu tidak menyelesaikan, lihat contoh saya di komentar saya sebelumnya.
JanKanis
3

Pada sistem diam (yaitu ketika tidak ada perubahan yang terjadi), ya, ada algoritma. Ada sejumlah terbatas tautan simbolik, sehingga mereka membentuk grafik terbatas, dan mendeteksi siklus adalah proses keuangan.

Pada sistem langsung, tidak ada cara untuk mendeteksi siklus, karena tautan simbolik dapat berubah saat pendeteksi siklus berjalan. Membaca setiap tautan simbolis adalah atom, tetapi mengikuti tautan simbolis tidak. Jika beberapa symlink terus berubah saat kernel melakukan traversal, itu bisa berakhir pada jalur tak terbatas yang melibatkan tautan berbeda.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Ada beberapa cara untuk memitigasi perubahan tersebut hingga mencapai akurasi 98-99%. Anda dapat membuatnya memperhatikan perangko waktu pada file dan saya tidak akan menyarankan untuk benar-benar mengikuti tautan. Karena bersifat rekursif dari root, ia akan menemukan direktori yang sebenarnya nanti.
Back2Basics
1
@ Back2Basics Angka-angka ini sama sekali tidak berarti. Ini adalah antarmuka kernel. Jika itu tidak berhasil sepanjang waktu, itu tidak berfungsi, titik.
Gilles 'SANGAT berhenti menjadi jahat'
2

Sejauh yang saya tahu dari melihat sumber-sumber kernel Linux saat ini, semua kernel lakukan adalah menghitung berapa banyak tautan yang diikuti, dan kesalahan jika itu lebih besar dari beberapa nomor. Lihat baris 1330 di namei.c untuk komentar, dan nested_symlink()fungsinya. Makro ELOOP (nomor kesalahan kembali dari aread(2) panggilan sistem untuk situasi ini) muncul di sejumlah tempat di file itu, jadi mungkin tidak semudah menghitung tautan yang diikuti, tapi itu pasti terlihat seperti apa.

Ada sejumlah algoritma untuk menemukan "siklus" dalam daftar tertaut ( algoritme deteksi siklus Floyd ) atau dalam grafik yang diarahkan . Tidak jelas bagi saya yang mana yang harus Anda lakukan untuk mendeteksi "lingkaran" atau "siklus" yang sebenarnya di jalur tertentu. Bagaimanapun, algoritme bisa memakan waktu lama untuk dijalankan, jadi saya menduga bahwa hanya dengan menghitung jumlah tautan simbolis yang diikuti, Anda mendapat 90% jalan menuju tujuan Anda.

Bruce Ediger
sumber
Untuk kegunaan praktis, hanya menghitung jumlah tautan yang dilalui baik-baik saja, terutama karena itulah yang dilakukan kernel, jadi bahkan jika Anda menemukan jalur penyelesaian yang benar yang memiliki terlalu banyak symlink, Anda masih tidak dapat menggunakan jalur itu untuk hal-hal praktis ( yaitu yang tidak melibatkan penyelesaian symlink secara manual)
JanKanis