Carpe Diem

備忘録

inodeから見たmvやcpの動き

背景

実行中のファイルに対してcpで上書きする場合だとText file busyで置き換えられず、mvだと何も聞かれず置き換えられる理由を深掘ってみました。

データ構造

mvやcpの動きを確認する前に必要な前提知識を説明します。

プロセスとinodeとの関係

プロセスとinodeは以下のように

プロセステーブルエントリ
└ファイルテーブルエントリ
 └v-node(i-node)

という形で繋がっています。

f:id:quoll00:20191224054722p:plain

ref: Lec 21: File System, Kernel Data Structures, and Open Files

inodeのデータ構造

inodeは以下のような情報を持ちます。

  • inode番号
  • ファイル種類(レギュラファイル、ディレクトリファイル、ソケット、etc...)
  • OwnerID
  • GroupID
  • アクセス許可ビット
  • ファイルサイズ
  • データブロックへのポインタ
  • リンクカウント
  • 最終 inode 更新時(ctime)、最終ファイル更新時(mtime)、最終参照時(atime) を示すタイムスタンプ群

図で表すと以下のようなデータ構造です。

f:id:quoll00:20191224055005p:plain

ref: Section 4.14.  File Systems

上記の項目には「ファイル名」がありません。ファイル名はinode自体ではなく、ディレクトリエントリに格納されています。
ディレクトリエントリには

  • inode番号
  • ファイル名

の2つが格納されており、inode番号を元にinodeへリンクされていることが上図から確認できます。

例)mkdirしてみるとどうなるか

例えばinode: 1267ディレクトリ内で

$ mkdir testdir

としてみると、以下のようなデータ構造に変化します。

f:id:quoll00:20191224060254p:plain

動きとしてはこうなります。

リンクカウントの働きは?

リンクカウントはディレクトリエントリの個数です。通常ファイルであれば最低1つ、ディレクトリであれば最低2つのリンクが存在します。

ディレクトリエントリを消すことでリンクカウントが0になると、ファイルは削除可能な状態(=ファイルに割り当てられたデータブロックを解放できる)になります。

注意として削除可能な状態になるだけであって、常にすぐに削除されるわけではありません。何かしらのプロセスがそのinodeを参照している場合は削除されず、プロセスが終了してクローズされたタイミングで削除されます。

なのでディレクトリエントリを消す関数はdeleteではなくunlinkと呼ばれます。

inode番号の確認方法

ls-iオプションを付ければ確認できます。

$ ls -i main.go
6747582

mvの動き

以下のようなファイルがあるとします。

$ ls -i
246 bar 123 foo

リネームする場合

foohogeにリネームしてみます。

リネームではファイルの実内容を移動する必要はなく、既存inodeを指す新たなディレクトリエントリを追加し、古いディレクトリエントリをアンリンクするだけです。

f:id:quoll00:20191224065914j:plain

なのでinode番号は変わりません。

$ ls -i
246 bar  123 hoge

既存ファイルを上書きする場合

次にbarhogeで上書きします。

先程は

  1. fooのinodeにリンクするhogeというディレクトリエントリを追加
  2. fooというディレクトリエントリをアンリンク

という2ステップでしたが、既存ファイルを上書きする場合はまず最初に既存ディレクトリエントリをアンリンクします。

  1. barディレクトリエントリをアンリンク(これによってbarファイルはリンクカウントが0になり削除される)
  2. hogeのinodeにリンクするbarというディレクトリエントリを追加
  3. hogeというディレクトリエントリをアンリンク

f:id:quoll00:20191224070835j:plain

よって上書きされた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

f:id:quoll00:20191224072112j:plain

実行中ファイルをcpで上書きはNGでmvで上書きはOKな理由

これらの動きから、

  • cpは実行中ファイルのデータを上書きする(改変する)から怒られる
  • mvは実行中ファイルが残ったままで改変されない(ディレクトリエントリが消えてるので見た目上見えなくなるが)。終了したら元ファイルは消える。

ということだと分かります。

ソース