Logo address

SYMLINK と格闘

2017/04/22

SYMLINK

unix は幾つかの醜い面を持っている。(もちろん Windows に比べると遥かに美しい世界ではある。)
その中の1つが SYMLINK である。例えば僕の Mac (unix の1変種である)では

-bash$ ls -l /usr/bin/vi
lrwxr-xr-x  1 root  wheel  3 11 18  2014 /usr/bin/vi -> vim
-bash$

図1: vi

SYMLINK が相手だとアウトプットのフィールドがイレギュラーである。このようなイレギュラーなアウトプットが含まれていると、これを何かのプログラムのインプットとして使いたい時に面倒なことになる。unix の良さは、プログラムのアウトプットが、他のプログラムのインプットとなるように設計しやすい点にあるのだが、これを台無しにするのである。

このアウトプットは /usr/bin/vi の実体は vim であることを伝えている。従って /usr/bin/vi について知りたければ vim を見なくてはならない。この vim の場所は、SYMLINK が作成された vi の場所と同じである。そこで vim の属性を

-bash$ ls -l /usr/bin/vim
-rwxr-xr-x  1 root  wheel  1530240  6 24  2016 /usr/bin/vim
-bash$

図2: vim

で手に入れることができる。これは舞台裏における vi の顔である。表舞台では、こうした仕掛けが隠されて vi は一人前の役者となる。表舞台の様子は lsL オプションで表示される。

-bash$ ls -lL /usr/bin/vi
-rwxr-xr-x  1 root  wheel  1530240  6 24  2016 /usr/bin/vi
-bash$

図3: vi

仕組みは簡単だ。/usr/bin/vi の実体は小さなファイルで、この場合には

ls -l /usr/bin/vi
の表示(図1)をみればわかるように、たった 3B、ここには "vim" と書かれているだけだ。ここで表示された
/usr/bin/vi -> vim
がそのことを表している。SYMLINK は案内標識で、vim を見なさいと言っている。/usr/bin に置かれた案内標識なので、/usr/bin の中で vim を探すことになる。

SYMLINK は仕組みから言えばファイルなのだが、以下では単にファイルと言えば SYMLINK を含めないことにする。特性があまりにも他のファイルと異なっているからである。

SYMLINK の基本的な作り方は

ln -s bar foo
のようなものである1foo にはパス名を書く。絶対パスでもよいし、作業場所からの相対パスでもよい。他方 bar は何でもよい。この部分には案内標識に書き込む内容を書く。例えば "I love you" だって許されるが、間違った案内標識は混乱を招くので、十分に気を付ける必要がある。

観客が見る世界、すなわち、表舞台で大切な点は次の通りである: /usr/bin/vi が SYMLINK であることが隠され、ファイルと同じ扱いを受けている。すなわち、/usr/bin/vi をコマンドとみなしても不都合は生じない。

表舞台では SYMLINK の持つ醜さを隠してしまう。表舞台は幸せな世界のように見えるが... ここは仮装舞台であって、奇妙奇天烈な舞台になり得るのである。

案内標識を辿ると、先にはまた案内標識があり、それに従って辿るとまた案内標識があり... 結局最終的には次の4つのケースがあり得る。

(a) ファイルに行き着く
(b) ディレクトリに行き着く
(c) 何も無い (以下 broken link と言う)
(d) 循環している (以下 cyclic link と言う)


注1: ln は多数のオプションを備えているが、これ以外の形式は必要の無いものである。ところで筆者は時々 barfoo の関係が分からなくなる。そこで link の持つ英語の語感をこの際に整理しておく。コマンドを英語に直すと、
link bar to foo
である。従って英文マニュアルでは bar を linked file、foo を linked-to file と表現している。日本人には分かりにくいが barfoo の関係は対等ではなく、ちょうど
add bar to foo
の関係と似ているのではないかと思える。だから、例えば
link a file to the directory
と言うことがあっても
link a directory to the file
とは言わないのであろう。
この link の持っている語感は s オプションのない link に現れている。


いくつかの例

test1

正しく作られた SYMLINK で、ケース(a)の例である。

-bash$ ls -l
total 32
lrwxr-xr-x  1 arisawa  staff     5  4 17 11:55 file1 -> file2
lrwxr-xr-x  1 arisawa  staff     5  4 17 12:39 file2 -> file3
-rw-r--r--  1 arisawa  staff  2231  4 17 12:39 file3
-rw-r--r--@ 1 arisawa  staff    76  4 17 12:40 memo.txt
-bash$ ls -Ll
total 32
-rw-r--r--  1 arisawa  staff  2231  4 17 12:39 file1
-rw-r--r--  1 arisawa  staff  2231  4 17 12:39 file2
-rw-r--r--  1 arisawa  staff  2231  4 17 12:39 file3
-rw-r--r--@ 1 arisawa  staff    76  4 17 12:40 memo.txt
-bash$

file1file2file3 を表している正しいリンクである。

test2

簡単な cyclic link

-bash$ ls -l
total 24
lrwxr-xr-x  1 arisawa  staff   5  4 17 11:55 file1 -> file2
lrwxr-xr-x  1 arisawa  staff   5  4 17 11:53 file2 -> file1
-rw-r--r--@ 1 arisawa  staff  68  4 17 12:00 memo.txt
-bash$ ls -Ll
total 24
-rw-r--r--@ 1 arisawa  staff  68  4 17 12:00 memo.txt
-bash$
ls コマンドは L オプションの下では cyclick link になっているファイルは表示していない。これは正しい考え方である。

test3

長い cyclic link の例

-bash$ ls -l
total 32
lrwxr-xr-x  1 arisawa  staff    5  4 17 11:55 file1 -> file2
lrwxr-xr-x  1 arisawa  staff    5  4 17 12:39 file2 -> file3
lrwxr-xr-x  1 arisawa  staff    5  4 17 13:05 file3 -> file1
-rw-r--r--@ 1 arisawa  staff  114  4 17 13:05 memo.txt
-bash$ ls -Ll
total 32
-rw-r--r--@ 1 arisawa  staff  114  4 17 13:05 memo.txt
-bash$

ls は正しく働いている。

test4

broken link の例

-bash$ ls -l
total 32
lrwxr-xr-x  1 arisawa  staff    5  4 18 13:24 file1 -> file2
lrwxr-xr-x  1 arisawa  staff    5  4 18 13:24 file2 -> file3
lrwxr-xr-x  1 arisawa  staff    6  4 18 13:25 file3 -> blabla
-rw-r--r--@ 1 arisawa  staff  114  4 18 13:21 memo.txt
-bash$ ls -Ll
total 32
-rw-r--r--@ 1 arisawa  staff  114  4 18 13:21 memo.txt
-bash$

lsL オプションの下では broken link は表示しない。これは正しい考え方である。

test5

次に cyclick link ではないが、下図に示すようにディレクトリを循環的に繋ぐリンクを調べる。

図4: 循環ディレクトリ

これはケース(b)の例であるが、素直ではない。ディレクトリへのリンクの場合には、このように意地悪くリンクを貼ると、表舞台ではあたかも無限に深いディレクトリであるかのように見せることが可能になる。

-bash$ ls -l
total 8
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 dir1
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 dir2
-rw-r--r--@ 1 arisawa  staff  123  4 17 18:36 memo.txt
-bash$ ls -l dir1
total 8
-rw-r--r--  1 arisawa  staff  0  4 18 16:15 bar1
-rw-r--r--  1 arisawa  staff  0  4 18 16:15 foo1
-rw-r--r--  1 arisawa  staff  0  4 18 16:15 hoge1
lrwxr-xr-x  1 arisawa  staff  7  4 17 18:36 ln1 -> ../dir2
-bash$ ls -l dir2
total 8
-rw-r--r--  1 arisawa  staff  0  4 18 16:15 bar2
-rw-r--r--  1 arisawa  staff  0  4 18 16:15 foo2
-rw-r--r--  1 arisawa  staff  0  4 18 16:15 hohe2
lrwxr-xr-x  1 arisawa  staff  7  4 17 18:36 ln2 -> ../dir1
-bash$ ls -lL dir1
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hoge1
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln1
-bash$ ls -lL dir2
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hohe2
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln2
-bash$ ls -lL dir1/ln1
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hohe2
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln2
-bash$ ls -lL dir2/ln2
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hoge1
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln1
-bash$ ls -lL dir1/ln1/ln2/ln1/ln2
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hoge1
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln1
-bash$

ツールの現状

ディレクトリを隈なく調べたいことがしばしば発生して、それに役に立つツールが存在する。例えば R オプションを添えた lsfind などである。しかし、SYMLINK の下ではそうしたツールの作成が難しくなる。特に難しくなるのは L オプション(follow link option)をつけた場合である。
こうしたツールの設計には
(a) 表示の欠落を発生させない
(b) 無限ループに陥いらない
(c) 同じファイルを余剰に表示しない
などの要求事項が考えられる。

欠落を発生させるツールはそもそも役に立たない。従って (a) は当然である。(b) の要求はいわばツールが暴走しないための保護である。安全性が確認されている環境、あるいは使い方の下では
(b) の対策を取らなくてもよいであろう。意地の悪い SYMLINK の下でも (c) の要求を満たす既存のものは僕は知らない。そもそも無限ループに陥るような SYMLINK を張る方が悪いのであるから、(c) に関しては正しい SYMLINK の下で満たされていれば良しとして割り切っても良いと思える。

実験環境

ディレクトリ test1 から test5 をどのように処理できたかを、lsfind について調べる。
なお、正しく SYMLINK が貼られているのは test1 だけで、
test2test3 は cyclic link、test4 は broken link になっている。
test5 はディレクトリへのリンクで、リンクをフォローしていくと、無限に深いディレクトリ(一種の無限ループ)になっている。

SYMLINK をフォローさせない場合には、いずれも正しく処理している。

ls

-bash$ ls -RLl test[1-5]
test1:
total 32
-rw-r--r--  1 arisawa  staff  2231  4 17 12:39 file1
-rw-r--r--  1 arisawa  staff  2231  4 17 12:39 file2
-rw-r--r--  1 arisawa  staff  2231  4 17 12:39 file3
-rw-r--r--@ 1 arisawa  staff    76  4 17 12:40 memo.txt

test2:
total 24
-rw-r--r--@ 1 arisawa  staff  68  4 17 12:00 memo.txt
ls: file1: Too many levels of symbolic links
ls: file2: Too many levels of symbolic links

test3:
total 32
-rw-r--r--@ 1 arisawa  staff  114  4 17 13:05 memo.txt
ls: file1: Too many levels of symbolic links
ls: file2: Too many levels of symbolic links
ls: file3: Too many levels of symbolic links

test4:
total 32
-rw-r--r--@ 1 arisawa  staff  114  4 18 13:21 memo.txt
ls: file1: No such file or directory
ls: file2: No such file or directory
ls: file3: No such file or directory

test5:
total 8
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 dir1
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 dir2
-rw-r--r--@ 1 arisawa  staff  123  4 17 18:36 memo.txt

test5/dir1:
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hoge1
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln1

test5/dir1/ln1:
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hohe2
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln2
ls: ln2: directory causes a cycle

test5/dir2:
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo2
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hohe2
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln2

test5/dir2/ln2:
total 0
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 bar1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 foo1
-rw-r--r--  1 arisawa  staff    0  4 18 16:15 hoge1
drwxr-xr-x  6 arisawa  staff  204  4 18 16:15 ln1
ls: ln1: directory causes a cycle
-bash$

正しく cyclic link を防いでいるが、test5 では同じファイルを多重に表示している。

find

find は SYMLINK をフォローするオプションとして LH を持つ。

-bash$ find -L test[1-5]
test1
test1/file1
test1/file2
test1/file3
test1/memo.txt
test2
test2/file1
test2/file2
test2/memo.txt
test3
test3/file1
test3/file2
test3/file3
test3/memo.txt
test4
test4/file1
test4/file2
test4/file3
test4/memo.txt
test5
test5/dir1
test5/dir1/bar1
test5/dir1/foo1
test5/dir1/hoge1
test5/dir1/ln1
test5/dir1/ln1/bar2
test5/dir1/ln1/foo2
test5/dir1/ln1/hohe2
test5/dir1/ln1/ln2
test5/dir2
test5/dir2/bar2
test5/dir2/foo2
test5/dir2/hohe2
test5/dir2/ln2
test5/dir2/ln2/bar1
test5/dir2/ln2/foo1
test5/dir2/ln2/hoge1
test5/dir2/ln2/ln1
test5/memo.txt
-bash$

find は何故か cyclick link や broken link も表示している。test5 では無限ループを防いだが、ls と同様に同じファイルを多重に表示している。

lr

lr は筆者の自作ツールで、もともとは plan9 用に作成したのであるが、今回 unix 版で SYMLINK をサボートした1。この記事はその経験に基づいている。

-bash$ lr -l test[1-5]
drwxr-xr-x arisawa  staff             0 2017/04/17 12:40:02 test1
lrw-r--r-- arisawa  staff          2231 2017/04/17 12:39:48 test1/file1
lrw-r--r-- arisawa  staff          2231 2017/04/17 12:39:48 test1/file2
-rw-r--r-- arisawa  staff          2231 2017/04/17 12:39:48 test1/file3
-rw-r--r-- arisawa  staff            76 2017/04/17 12:40:02 test1/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/17 12:00:38 test2
-rw-r--r-- arisawa  staff            68 2017/04/17 12:00:38 test2/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/17 13:05:32 test3
-rw-r--r-- arisawa  staff           114 2017/04/17 13:05:01 test3/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/18 13:25:00 test4
-rw-r--r-- arisawa  staff           114 2017/04/18 13:21:59 test4/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/17 18:36:56 test5
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:17 test5/dir1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/bar1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/foo1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/hoge1
lrwxr-xr-x arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:50 test5/dir2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir2/bar2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir2/foo2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir2/hohe2
lrwxr-xr-x arisawa  staff             0 2017/04/18 16:15:17 test5/dir2/ln2
-rw-r--r-- arisawa  staff           123 2017/04/17 18:36:56 test5/memo.txt
-bash$

SYMLINK の表示法が ls と異なっていることに注意しよう。すなわち、SYMLINK 自体の属性ではなく、リンク先のファイルの属性が表示されている。

SYMLINK をフォローさせると次の結果になる。

-bash$ lr -lL test[1-5]
drwxr-xr-x arisawa  staff             0 2017/04/17 12:40:02 test1
-rw-r--r-- arisawa  staff          2231 2017/04/17 12:39:48 test1/file1
-rw-r--r-- arisawa  staff          2231 2017/04/17 12:39:48 test1/file2
-rw-r--r-- arisawa  staff          2231 2017/04/17 12:39:48 test1/file3
-rw-r--r-- arisawa  staff            76 2017/04/17 12:40:02 test1/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/17 12:00:38 test2
# test2/file1: broken or cyclic link
# test2/file2: broken or cyclic link
-rw-r--r-- arisawa  staff            68 2017/04/17 12:00:38 test2/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/17 13:05:32 test3
# test3/file1: broken or cyclic link
# test3/file2: broken or cyclic link
# test3/file3: broken or cyclic link
-rw-r--r-- arisawa  staff           114 2017/04/17 13:05:01 test3/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/18 13:25:00 test4
# test4/file1: broken or cyclic link
# test4/file2: broken or cyclic link
# test4/file3: broken or cyclic link
-rw-r--r-- arisawa  staff           114 2017/04/18 13:21:59 test4/memo.txt
drwxr-xr-x arisawa  staff             0 2017/04/17 18:36:56 test5
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:17 test5/dir1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/bar1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/foo1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/hoge1
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1/bar2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1/foo2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1/hohe2
# test5/dir1/ln1/ln2: cyclic directory link
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:50 test5/dir2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir2/bar2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir2/foo2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir2/hohe2
# test5/dir2/ln2: cyclic directory link
-rw-r--r-- arisawa  staff           123 2017/04/17 18:36:56 test5/memo.txt
-bash$

test5 については、余剰表示の個数が lslr に比べて減少している。他方、表示結果は dir1dir2 について非対称である。この非対称性は採用したアルゴリズムの結果である。

このアルゴリズムは以下のようなものである。
ディレクトリにリンクする SYMLINK で出会ったら、置かれているディレクトリをマークする。SYMLINK の先が既にマークされていれば、その SYMLINK は辿らない。

lrls と同様に broken link や cyclic link は表示しない。 代わりに警告メッセージを出すのみである。

余剰表示を完全に防ぐには、辿ったすべてのディレクトリをマークしなくてはならなくなるであろう。これは保護にしては大袈裟すぎる。

一応、余剰表示を完全に防ぐオプション R (no redundant output) を入れてみた。次はそのアウトプットである。

-bash$ lr -lLR test5
drwxr-xr-x arisawa  staff             0 2017/04/17 18:36:56 test5
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:17 test5/dir1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/bar1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/foo1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:17 test5/dir1/hoge1
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1/bar2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1/foo2
-rw-r--r-- arisawa  staff             0 2017/04/18 16:15:50 test5/dir1/ln1/hohe2
# test5/dir1/ln1/ln2: cyclic directory link
drwxr-xr-x arisawa  staff             0 2017/04/18 16:15:50 test5/dir2
-rw-r--r-- arisawa  staff           123 2017/04/17 18:36:56 test5/memo.txt
-bash$

どのファイルも1度だけ表示されている。

R オプションは廃止されるかも知れない。自分はオプションを増やすのを好まない。大切なのは間違った SYMLINK に対して警告メッセージを出し、間違いが訂正されるのに役に立つことであって、間違いをそのままにして余剰を防ぐことではない。その点で find は失格である。


注1: lr のソースコードは http://p9.nyx.link/netlib/cmd/lr/ に置かれている。ただしコンパイルには Plan9port が必要である。

プログラミング

基礎知識

ファイルに対する ls コマンドの例

-bash$ ls -l /usr/bin/vim
-rwxr-xr-x  1 root  wheel  1530240  6 24  2016 /usr/bin/vim
を考える。このうち、
-rwxr-xr-x  1 root  wheel  1530240  6 24  2016
の部分は、(sb を stat 構造体として)
stat("/usr/bin/vim",&sb)
によって得られる。あるいは、(lsb を stat 構造体として)
lstat("/usr/bin/vim",&lsb)
としても全く同じ結果が得られる1

SYMLINK の場合には事情が異なる。stat(path,&sb)lstat(path,&lsb) では得られるものが異なっている。例を示す。/usr/bin/vi は SYMLINK である。その場合、命令

lstat("/usr/bin/vi",&lsb)
によって、
-bash$ ls -l /usr/bin/vi
lrwxr-xr-x  1 root  wheel  3 11 18  2014 /usr/bin/vi -> vim

lrwxr-xr-x  1 root  wheel  3 11 18  2014
の部分が stat 構造体 lsb に与えられる。他方
stat("/usr/bin/vi",&sb)
の結果、stat 構造体 sb には、/usr/bin/vi がリンクされている /usr/bin/vim の stat 情報
stat("/usr/bin/vim",&sb)
が与えられる。その結果は lsL オプションで確認できる。
-bash$ ls -lL /usr/bin/vi
-rwxr-xr-x  1 root  wheel  1530240  6 24  2016 /usr/bin/vi


注1: /dev/ の中のファイルには、SYMLINK でないにも関わらず、stat(path,&sb)lstat(path,&lsb) で得られる sblsb が異なるものが存在する。例えば MacOS の場合 /dev/io8log* がそうである。これらは character device であるが、他の character device では両者は一致している。

stat() と lstat()

筆者は Plan9 の愛好家であって unix のプログラミングには慣れていない。久しぶりに unix でプログラムをしたら色々な発見があった。何しろ Plan9 には SYMLINK は存在しない。

SYMLINK は構造的には単なるテキストファイルである。パスを path とすると、SYMLINK か否かの判定は次のようにすればよい。

# include <sys/stat.h>
char *path;
struct stat lsb;
...
	if(lstat(path,&lsb) < 0){
		/* stat 情報が取れない (ファイルが存在しない) */
		...
	}
	else{
		if(S_ISLNK(lst->st_mode)){
			/* then path is SYMLINK */
			...
		}
		else{
			/* then path is a file or a dir */
			...
		}
	}

ここに S_ISLNKsys/stat.h

#define	S_ISLNK(m)	(((m) & S_IFMT) == S_IFLNK)
として定義されているマクロで、SYMLINK であることを判定する。

path が SYMLINK であれば

# include <sys/stat.h>
char *path;
struct stat sb;
...
	if(stat(path,&sb) < 0){
		/* stat 情報が手に入らなかった。
		 * 原因は path が存在しない、
		 * あるいは broken or cyclic SYMLINK など。
		 * sb はそのまま */
		...
	}
	else{
		/* この場合 SYMLINK チェーンを辿った先の
	 	 *  file や dir の sb が設定される  */
		if(S_ISDIR(st->st_mode)){
			/* dir である */
			...
		}
		else{
			/* file の類 */
			...
		}
	}

stat 構造体には ls -l コマンドで表示されている内容(ただしファイル名を除く)が含まれている。MacOS の場合には次のようになっている。

     struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */
         dev_t           st_dev;           /* ID of device containing file */
         mode_t          st_mode;          /* Mode of file (see below) */
         nlink_t         st_nlink;         /* Number of hard links */
         ino_t           st_ino;           /* File serial number */
         uid_t           st_uid;           /* User ID of the file */
         gid_t           st_gid;           /* Group ID of the file */
         dev_t           st_rdev;          /* Device ID */
         struct timespec st_atimespec;     /* time of last access */
         struct timespec st_mtimespec;     /* time of last data modification */
         struct timespec st_ctimespec;     /* time of last status change */
         struct timespec st_birthtimespec; /* time of file creation(birth) */
         off_t           st_size;          /* file size, in bytes */
         blkcnt_t        st_blocks;        /* blocks allocated for file */
         blksize_t       st_blksize;       /* optimal blocksize for I/O */
         uint32_t        st_flags;         /* user defined flags for file */
         uint32_t        st_gen;           /* file generation number */
         int32_t         st_lspare;        /* RESERVED: DO NOT USE! */
         int64_t         st_qspare[2];     /* RESERVED: DO NOT USE! */
     };

MacOS の stat 構造体。OS によって異なる。

st_mode の各 bit の意味は

     #define S_IFMT 0170000           /* type of file */
     #define        S_IFIFO  0010000  /* named pipe (fifo) */
     #define        S_IFCHR  0020000  /* character special */
     #define        S_IFDIR  0040000  /* directory */
     #define        S_IFBLK  0060000  /* block special */
     #define        S_IFREG  0100000  /* regular */
     #define        S_IFLNK  0120000  /* symbolic link */
     #define        S_IFSOCK 0140000  /* socket */
     #define        S_IFWHT  0160000  /* whiteout */
     #define S_ISUID 0004000  /* set user id on execution */
     #define S_ISGID 0002000  /* set group id on execution */
     #define S_ISVTX 0001000  /* save swapped text even after use */
     #define S_IRUSR 0000400  /* read permission, owner */
     #define S_IWUSR 0000200  /* write permission, owner */
     #define S_IXUSR 0000100  /* execute/search permission, owner */

16bit で定義されているのだね〜
unix が生まれたのは、こんな時代だから...
bit ごとに独立した意味を与えていないことが、判定を(少しだけ)面倒にしている。

opendir() と readdir()

ディレクトリのパス path を与えて、その中に含まれるファイル名を知る。

#include <dirent.h>
char *path;
DIR *d;
struct dirent *de;
...
	d = opendir(path);
	if(d == NULL){
		/* error */
		...
	}
	else{
		while ((de = readdir(d)) != NULL) {
			printf("%s\n",de->->d_name);/* or something else */
			...
		}
		/* dont't free(de) */
	}
	closedir(d);

ここで使われている構造体の内容を次に示す。

typedef struct {
	int	__dd_fd;	/* file descriptor associated with directory */
	long	__dd_loc;	/* offset in current buffer */
	long	__dd_size;	/* amount of data returned */
	char	*__dd_buf;	/* data buffer */
	int	__dd_len;	/* size of data buffer */
	long	__dd_seek;	/* magic cookie returned */
	long	__dd_rewind;	/* magic cookie for rewinding */
	int	__dd_flags;	/* flags for readdir */
	__darwin_pthread_mutex_t __dd_lock; /* for thread locking */
	struct _telldir *__dd_td; /* telldir position recording */
} DIR;

struct dirent { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */
	ino_t      d_fileno;     /* file number of entry */
	__uint64_t d_seekoff;    /* seek offset (optional, used by servers) */
	__uint16_t d_reclen;     /* length of this record */
	__uint16_t d_namlen;     /* length of string in d_name */
	__uint8_t  d_type;       /* file type, see below */
	char    d_name[1024];    /* name must be no longer than this */
};

MacOS の DIR 構造体と dirent 構造体。OS によって異なる。

Linux の場合には d_namlen は dirent 構造体に含まれていない。(困らない)

readlink()

SYMLINK チェーンの先がファイルであればであれば、この path はファイルと同等の、ディレクトリであればディレクトリと同等の扱いを受けるのである。
従って SYMLINK ファイルの中身を覗くには open(path,...) では覗けない。(リンク先のファイルが open されるから)

#include <unistd.h>

int n;
char *path;
char buf[256];/* 適当な十分な大きさのサイズで */
...
	n = readlink(path,buf,sizeof(buf))
	if(n < 0){
		/* error */
		...
	}
	buf[n] = 0;
	printf("%s\n",buf); /* or something */

st_ino

再巡回を避けるために、巡回済(あるいは巡回中)のディレクトリは必要に応じてリストに登録する。リストに登録する情報はディレクトリをユニークに特徴付けるものでなくてはならない。巡回中のプロセスから見えるディレクトリのパスではダメである。ここは一種の仮想空間であり、本来のパス名が分からないからである。では何を使うか?
Plan9 では qid.path なるものをその目的のために使っている。path と名前が付いているが、ディレクトリをユニークに識別する数字である。Russ Cox1 は Plan9 のソフトウェアを unix に移植するにあたって、qid.path の代用として stat 構造体の st_ino を採用している。

Plan9 には SYMLINK なるものは存在しない。システムのメンテ作業においては木構造でないと制御しにくいからである。しかし他方では木構造を基にして変幻自在な仮想空間をアプリケーションに提供する仕組みを持っている。そのためにファイルを識別する情報を必要としているのである。


注1: Russ Cox は Plan9 と unix に精通している、世界的なトッププログラマーの一人である。