背景
実行中のファイルに対してcpで上書きする場合だとText file busy
で置き換えられず、mvだと何も聞かれず置き換えられる理由を深掘ってみました。
データ構造
mvやcpの動きを確認する前に必要な前提知識を説明します。
プロセスとinodeとの関係
プロセスとinodeは以下のように
プロセステーブルエントリ
└ファイルテーブルエントリ
└v-node(i-node)
という形で繋がっています。
ref: Lec 21: File System, Kernel Data Structures, and Open Files
inodeのデータ構造
inodeは以下のような情報を持ちます。
- inode番号
- ファイル種類(レギュラファイル、ディレクトリファイル、ソケット、etc...)
- OwnerID
- GroupID
- アクセス許可ビット
- ファイルサイズ
- データブロックへのポインタ
- リンクカウント
- 最終 inode 更新時(ctime)、最終ファイル更新時(mtime)、最終参照時(atime) を示すタイムスタンプ群
図で表すと以下のようなデータ構造です。
ref: Section 4.14. File Systems
上記の項目には「ファイル名」がありません。ファイル名はinode自体ではなく、ディレクトリエントリに格納されています。
ディレクトリエントリには
- inode番号
- ファイル名
の2つが格納されており、inode番号を元にinodeへリンクされていることが上図から確認できます。
例)mkdirしてみるとどうなるか
例えばinode: 1267
のディレクトリ内で
$ mkdir testdir
としてみると、以下のようなデータ構造に変化します。
動きとしてはこうなります。
inode: 2549
のディレクトリが作成されるinode: 2549
のディレクトリにはカレントディレクトリの.
と親ディレクトリの..
の2つのディレクトリエントリが存在するinode: 1267
のディレクトリにはカレントディレクトリの.
と親ディレクトリの..
の2つに加え、testdir
のディレクトリエントリが追加される
リンクカウントの働きは?
リンクカウントはディレクトリエントリの個数です。通常ファイルであれば最低1つ、ディレクトリであれば最低2つのリンクが存在します。
ディレクトリエントリを消すことでリンクカウントが0になると、ファイルは削除可能な状態(=ファイルに割り当てられたデータブロックを解放できる)になります。
注意として削除可能な状態になるだけであって、常にすぐに削除されるわけではありません。何かしらのプロセスがそのinodeを参照している場合は削除されず、プロセスが終了してクローズされたタイミングで削除されます。
なのでディレクトリエントリを消す関数はdeleteではなくunlink
と呼ばれます。
inode番号の確認方法
ls
に-i
オプションを付ければ確認できます。
$ ls -i main.go 6747582
mvの動き
以下のようなファイルがあるとします。
$ ls -i 246 bar 123 foo
リネームする場合
foo
をhoge
にリネームしてみます。
リネームではファイルの実内容を移動する必要はなく、既存inodeを指す新たなディレクトリエントリを追加し、古いディレクトリエントリをアンリンクするだけです。
なのでinode番号は変わりません。
$ ls -i 246 bar 123 hoge
既存ファイルを上書きする場合
次にbar
をhoge
で上書きします。
先程は
という2ステップでしたが、既存ファイルを上書きする場合はまず最初に既存ディレクトリエントリをアンリンクします。
bar
ディレクトリエントリをアンリンク(これによってbarファイルはリンクカウントが0になり削除される)hoge
のinodeにリンクするbar
というディレクトリエントリを追加hoge
というディレクトリエントリをアンリンク
よって上書きされたbar
ファイルのinode番号はhoge
のinode番号と同じです。
$ ls -i 123 bar
cpの動き
cpはinodeはそのままで、中身(データブロックなど)をコピーします。
$ ls -i 246 bar 123 hoge
なのでmvではhogeのinodeになりましたが、cpではbarのinodeのままです。
$ cp hoge bar $ ls -i 246 bar 123 hoge
実行中ファイルをcpで上書きはNGでmvで上書きはOKな理由
これらの動きから、
- cpは実行中ファイルのデータを上書きする(改変する)から怒られる
- mvは実行中ファイルが残ったままで改変されない(ディレクトリエントリが消えてるので見た目上見えなくなるが)。終了したら元ファイルは消える。
ということだと分かります。