はじめに
Linuxでシンボリックリンクの参照先を更新したい場合、以下のようにするのが簡単です。
> ln -snf src dst
今回他のプロセスがdst
にアクセス中に更新したかったので、この方法で安全に更新できるかどうか調べてみました。
確認した環境はRHEL7.2/Gentoo Linuxですが大抵のLinux環境で同じような挙動になるのではないかと思います。
(POSIXとかで決まってそうな気もしますが調べてません…)
strace
で調べる
まずln
コマンドでの更新の様子を見てみます。
> strace ln -snf src dst
...
symlink("src", "dst") = -1 EEXIST (File exists)
unlink("dst") = 0
symlink("src", "dst") = 0
...
このようにln
は1コマンドですが、システムコール的には「unlink
で削除してからsymlink
でシンボリックリンクを作る」という実装になっているようです。
そのためunlink
とsymlink
の間にdst
にアクセスされると失敗する可能性があります。
更新中に割り込めるか?
2つのシステムコールの間にアクセスされる可能性はありそうだったので、
実際に起きるかどうか以下のようなシェルスクリプトで実験してみました。
#!/bin/sh
echo test1 > src1
echo test2 > src2
while :
do
ln -snf src1 dst
ln -snf src2 dst
done
#!/bin/sh
while :
do
cat dst
done
ln.sh
がdst
をsrc1
/src2
に交互に更新し、cat.sh
がdst
を読みます。この2つを同時に実行すると以下のような結果になりました。
> ./cat.sh
test1
test2
test1
test2
test1
cat: dst: そのようなファイルやディレクトリはありません
test2
test1
test2
test1
test2
上記は抜粋ですが20~30回に1回程度はcat
に失敗するようです。
アトミックなシンボリックリンク更新
先ほどのln.sh
の代わりに
#!/bin/sh
echo test1 > src1
echo test2 > src2
while :
do
ln -s src1 tmp
mv -Tf tmp dst
ln -s src2 tmp
mv -Tf tmp dst
done
このln_atomic.sh
を使ってみると
> ./cat.sh
test1
test2
test1
test2
test1
test2
test1
test2
test1
test2
という感じでcat
の失敗は発生しなくなりました。
実際にシンボリックリンクを更新しているのはmv
コマンドです。このコマンドは同一ファイルシステム内に限り、単一のシステムコールrename
で実装されています。従って更新中に割り込まれることはありません。
ちなみにファイルシステム間のmv
は単にファイルコピー+削除になるようです。
そのためsrc
とdst
はファイルシステムが異なっていてもよいのですが、tmp
とdst
は同一ファイルシステムになければいけません。
まとめ
シンボリックリンクをアトミックに更新したい場合は以下のようにするといいようです。
(tmp
とdst
が同一ファイルシステムになるよう注意)
> ln -s src tmp
> mv -Tf tmp dst