はじめに
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