名前を消してもファイルは残る
目標
読み物で「名前と実体は別々にある」と読みました。ここでは、その分離が実際にどんな動作を生み出すのかを、手で確認します。
8つのステップを終えると、次のような質問に答えられるようになります。dfは空きがあると言っているのになぜ書き込みが失敗するのか、ログを削除したのになぜ容量が戻らないのか、設定ファイルをどう差し替えれば、読む側が半分だけ書かれたファイルを見ずに済むのか。
作業ディレクトリ
mkdir -p /root/fs && cd /root/fs
すべての出力物はここに作ります。
使うツール
stat -c '%i %h %d %n' 파일 # 아이노드, 링크 수, 장치 번호, 이름
ls -i # 아이노드 번호를 함께 보기
ls -l /proc/<PID>/fd # 그 프로세스가 열어 둔 파일들
statの%iが、実体の身分証明書です。2つの名前の%iが同じなら、同じファイルです。サイズや内容が同じであることとは違います。
採点方法
3つのステップでは、採点ツールがみなさんが作ったスクリプトを直接実行します。
hold.py 채점기가 만든 파일을 넘겨 주고, 이름을 뗀 뒤에도
읽어 내는지 봅니다
findleak.sh 채점기가 직접 유출을 만들어 놓고, 그것을 찾아내는지
봅니다. 유출이 없어진 뒤에도 계속 보고하면 실패입니다
swap.sh 채점기가 대상 파일을 열어 둔 채로 여러분의 교체를
시킵니다. 열어 둔 디스크립터로 새 내용이 보이면
대상에 직접 쓴 것이므로 실패입니다
言葉で説明したものは採点しません。動作するものだけを見ます。
同じファイルかどうか、どうやってわかるのか
orig.txtを作り、同じ実体を指す2つ目の名前hard.txtと、内容だけが同じコピーcopy.txtを作ってください。3つのinode番号を確認して、identity.txtに書きます。
ハードリンクはln 원본 새이름、コピーはcp 원본 새이름です(プレースホルダーは、元のファイル名と新しい名前です)。
確認はstat -c '%i %h %n' orig.txt hard.txt copy.txtで行います。
内容が同じであることと、同じファイルであることは別です。diffでは3つとも同じに見えます。実体が1つか2つかを教えてくれるのは、inode番号だけです。
リンク数は何を数えるのか
同じ実体を指す3つ目の名前third.txtを作ってください。orig.txtのリンク数がどうなるか、そしてその値がいくつになるとブロックが回収されるのかを、nlink.txtに書きます。
stat -c %h orig.txtがリンク数です。名前が3つなら3です。
rmはファイルを消すコマンドではなく、名前を外すコマンドです。システムコール名がunlinkである理由です。名前が1つも残らず、そのファイルを開いているプロセスもないときに、初めてブロックが回収されます。
削除したファイルを読む
hold.pyを作ってください。引数として受け取ったファイルを先に開き、次に名前を外してから、そのディスクリプターで内容を読んで出力します。そのディスクリプターがまだ何をつかんでいるかも、あわせて出力してください。
Pythonで名前を外すのはos.unlink(경로)です(プレースホルダーはパスです)。順序がすべてです。開いて、外して、読みます。
ディスクリプターが何をつかんでいるかは、os.readlink('/proc/self/fd/%d' % f.fileno())で見られます。名前が外れたファイルなら、カーネルが末尾に(deleted)を付けてくれます。
内容をまるごと読んでおいてから削除するのとは違います。それは単なるコピーであり、ここで見たいのは、名前がなくても実体が生きているという事実です。
容量をつかんでいる犯人を探す
findleak.shを作ってください。今このシステムで、名前はなくなったのにまだ開かれているファイルをすべて探し、つかんでいるプロセスとパスを出力します。実行権限を付けてください。
/proc/<PID>/fdは、そのプロセスが開いているファイルをシンボリックリンクとして見せてくれます。名前が外れたものには、末尾に(deleted)が付きます。
ls -l /proc/[0-9]*/fd 2>/dev/null | grep deletedから始めてください。権限のないプロセスは読めないので、エラーは捨てる必要があります。
結果を埋め込んではいけません。採点ツールがリークを作ってからなくした後でも、報告し続けるかどうかを確認します。
元のファイルを移動すると何が壊れるのか
base.txtを作り、そのハードリンクkeep.txtとシンボリックリンクsoft.txtを作ってください。その後、base.txtをmoved.txtに移動します。どちらが壊れ、なぜそうなるのかをlinks.txtに書いてください。(ステップ1–2で作ったファイルはそのまま残します。)
シンボリックリンクはln -s、ハードリンクは-sなしのlnです。
移動した後、cat soft.txtとcat keep.txtをそれぞれ実行してみてください。ls -l soft.txtで、それが何を指しているかも見てください。
ハードリンクはinodeを、シンボリックリンクはパス文字列を持っています。シンボリックリンクは、アクセスするたびにそのパスを改めてたどります。
設定ファイルを安全に差し替える
swap.sh <새내용파일> <대상파일>を作ってください(プレースホルダーは、新しい内容のファイルと対象ファイルです)。対象を読んでいる側が半分だけ書かれたファイルを決して見ないように、差し替えます。実行権限を付け、一時ファイルを残さないでください。
対象に直接書いてはいけません。同じディレクトリの一時ファイルに新しい内容を書き、mvで名前を差し替えます。
同じファイルシステムの中では、mvはディレクトリエントリ1つだけを書き換えるので、アトミックです。読む側は古い実体か新しい実体のどちらかを見て、その中間は存在しません。
採点ツールは、対象ファイルを開いたまま、みなさんのスクリプトを実行します。名前を差し替えたなら、そのディスクリプターには古い内容が見え続けているはずです。
名前の差し替えが止まる場所
作業ディレクトリと/dev/shmのデバイス番号を確認してください。その境界を越えて名前を差し替えようとするとどんなエラーが出るのかを自分で実行して確かめ、そのときmvは実際に何をしているのかをexdev.txtに書いてください。
デバイス番号はstat -c %d 경로です(プレースホルダーはパスです)。値が違えば、別のファイルシステムです。
実行して確かめるには、python3 -c "import os; os.rename('a.txt', '/dev/shm/a.txt')"を使います。mvはこのエラーに出会うと、黙ってコピーして元のファイルを削除するので、失敗する様子を見るにはrenameを直接呼ぶ必要があります。
これが、安全な置き換えを必ず同じファイルシステムの中で行わなければならない理由です。コピーは時間がかかり、途中で切れると半分だけ書かれたファイルが残ります。
dfが嘘をついているように見えるとき
report.mdに診断書を書いてください。dfは空きがあると言っているのに書き込みが失敗する2つのケース、それぞれを確認するコマンド、容量を取り戻す方法、そしてこの事故が主にどこで起きるのかを盛り込みます。
2つのケースは、前のステップですでに扱いました。1つはブロックではなく別のものが枯渇したケース(df -i)で、もう1つはステップ4で自分で見つけ出したものです。
原因を見つけて終わりにすると、事故は続きます。容量を今取り戻す方法と、再発させない方法を分けて書いてください。
次の人が読んで、そのまま手順どおりに実行できる文書を目標にします。