実際の変化確認
macOS (APFS)
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
ls |
0 |
0 |
0 |
0 |
2 |
上書き作成 |
4 |
4 |
0 |
0 |
4 |
touch |
6 |
6 |
6 |
0 |
6 |
touch (Ruby) |
8 |
8 |
8 |
0 |
8 |
追記 |
10 |
10 |
8 |
0 |
10 |
chmod |
10 |
12 |
8 |
0 |
12 |
chown |
10 |
14 |
8 |
0 |
14 |
新規作成 |
16 |
16 |
16 |
16 |
16 |
cat |
16 |
16 |
18 |
16 |
18 |
touch |
20 |
20 |
20 |
16 |
20 |
cat |
20 |
20 |
20 |
16 |
22 |
追記 |
24 |
24 |
20 |
16 |
24 |
cat |
24 |
24 |
25 |
16 |
26 |
cat |
24 |
24 |
25 |
16 |
28 |
cat |
24 |
24 |
25 |
16 |
30 |
Linux (ext4)
action |
modify |
change |
access |
now |
新規作成 |
0 |
0 |
0 |
0 |
ls |
0 |
0 |
0 |
2 |
上書き作成 |
4 |
4 |
0 |
4 |
touch |
6 |
6 |
6 |
6 |
touch (Ruby) |
7 |
7 |
7 |
8 |
追記 |
10 |
10 |
7 |
10 |
chmod |
10 |
12 |
7 |
12 |
chown |
10 |
14 |
7 |
14 |
新規作成 |
16 |
16 |
16 |
16 |
cat |
16 |
16 |
17 |
18 |
touch |
19 |
19 |
19 |
20 |
cat |
19 |
19 |
21 |
22 |
追記 |
24 |
24 |
21 |
24 |
cat |
24 |
24 |
25 |
26 |
cat |
24 |
24 |
25 |
28 |
cat |
24 |
24 |
25 |
30 |
- 数字は対応する時間の秒を表わす
- now は現在日時を表す
- 1秒間隔にすると、より不可解な結果になるため2秒間隔にしている
- 2秒間隔だと奇数になるはずがないのに実際は奇数秒になったりする(謎)
- ext4 に birth は無い
参照するだけでは何も変化しない
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
find |
0 |
0 |
0 |
0 |
1 |
ls |
0 |
0 |
0 |
0 |
2 |
exist? |
0 |
0 |
0 |
0 |
3 |
参照するということはアクセスするということなので当然 access は更新されるものと考えていたが実際は find
, ls
, ファイル存在確認程度では何も変化しなかった。
上書き作成と新規作成は異なる
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
上書作成 |
1 |
1 |
0 |
0 |
1 |
新規作成 |
2 |
2 |
2 |
2 |
2 |
更新 |
3 |
3 |
2 |
2 |
3 |
- 同名のファイルをいったん削除してから作るのと上書きしてファイルを作るのとではファイルの情報に差が出る
- 「上書き作成」は「更新」と同じで modify, change だけが更新されているのがわかる
chmod や chown で change だけが更新される
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
chmod |
0 |
1 |
0 |
0 |
1 |
chown |
0 |
2 |
0 |
0 |
2 |
これはわかる
modify だけ更新するつもりが change まで更新される
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
追記 |
1 |
1 |
0 |
0 |
1 |
どういうことだ……?
modify だけを変更するのは難しい
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
touch -m |
2 |
2 |
0 |
0 |
2 |
touch -m (Ruby) |
4 |
4 |
0 |
0 |
4 |
難しいというかできなかった。
touch --help
に -m change only the modification time
と出てくるから touch -m
としても change まで更新されてしまう。
File.utime を使って File.utime(File.atime("x"), Time.now, "x")
としても change まで更新されてしまう。
ctime は作成日時ではなかった
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
追記 |
2 |
2 |
0 |
0 |
2 |
追記 |
4 |
4 |
3 |
0 |
4 |
- ctime の c は change のこと
- この change はコンテンツを更新すると modify と一緒に更新されているのがわかる
- つまり「作成日時」ではなく「更新日時」に近い
- それだと modify と同じになってしまって意味がないように感じる
- 2回目の追記でいきなり access が更新されたのは謎
- ctime の命名がよくない
touch は modify, change, access が変化する
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
touch |
1 |
1 |
1 |
0 |
1 |
touch (Ruby) |
2 |
2 |
2 |
0 |
2 |
- さすがに birth は変化しない (それでいい)
- 上書き作成のあとで access が変化しないのが気になるなら touch しておく
- Ruby の FileUtils.touch の説明には modify と access が変化すると書いてあるが実際は change も変化した。このあたりはファイルシステムによって違うのかもしれない
birth は本当に必要だったもの
action |
modify |
change |
access |
birth |
now |
新規作成 |
0 |
0 |
0 |
0 |
0 |
追記 |
1 |
1 |
0 |
0 |
1 |
chmod |
1 |
2 |
0 |
0 |
2 |
新規作成 |
3 |
3 |
3 |
3 |
3 |
access の変化タイミングはよくわからない
action |
modify |
change |
access |
birth |
now |
|
新規作成 |
0 |
0 |
0 |
0 |
0 |
|
cat |
0 |
0 |
1 |
0 |
1 |
0 > 0 は偽なのに更新された(謎) |
touch |
2 |
2 |
2 |
0 |
2 |
|
cat |
2 |
2 |
2 |
0 |
3 |
2 > 2 は偽なので更新されない |
追記 |
4 |
4 |
2 |
0 |
4 |
|
cat |
4 |
4 |
5 |
0 |
5 |
4 > 2 は真なので更新された |
cat |
4 |
4 |
5 |
0 |
6 |
4 > 5 は偽なので更新されない |
action |
modify |
change |
access |
birth |
now |
|
追記 |
14 |
14 |
12 |
10 |
14 |
|
cat |
14 |
14 |
15 |
10 |
15 |
|
cat |
14 |
14 |
16 |
10 |
16 |
14 > 15 は偽なのにさらに更新された(謎) |
cat |
14 |
14 |
16 |
10 |
17 |
|
こちらによると「modify > access または change > access なら更新」というロジックになっているそうで、これが APFS にも合てはまるのかはわからないけど実際は上のようになっていてどういうロジックなのかよくわからなかった。
また noatime でマウントする と atime が更新されなくなるらしい。
ファイルシステムの調べ方
$ df -T /
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/disk3s1s1 apfs 971350180 452586272 518763908 47% /
$ df --output=fstype / | tail -1
apfs
検証用コード
検証用コード
require "fileutils"
require "table_format"
F = FileUtils
class Context
attr_reader :records
def call(...)
@records = []
instance_eval(...)
tp @records.collect(&:to_h)
exit
end
def test(action)
time_next
yield
@records << Record.new(self, action, @records.last)
end
def time_next
i = Time.now.to_i
nil until Time.now.to_i >= (i + step)
end
def step
2
end
class Record
attr_reader :context
attr_reader :before
attr_reader :times
attr_reader :now
def initialize(context, action, before)
@context = context
@action = action
@before = before
@now = Time.now
s = File.stat("x")
@times = {
:modify => TimeOne.new(self, :modify, s.mtime),
:change => TimeOne.new(self, :change, s.ctime),
:access => TimeOne.new(self, :access, s.atime),
}
begin
@times[:birth] = TimeOne.new(self, :birth, s.birthtime)
rescue NotImplementedError
end
end
def to_h
{
"action" => @action,
**@times.inject({}) {|a, (_, e)| a.merge(e.to_h) },
"now" => @now.to_i - @context.records.first.now.to_i,
}
end
class TimeOne
def initialize(record, name, time)
@record = record
@name = name
@time = time
end
def to_i
@time.to_i - @record.context.records.first.now.to_i
end
def to_s
if @record.before && @record.before.times[@name].to_i != to_i
"**#{to_i}**"
else
to_i.to_s
end
end
def to_h
{ @name => to_s }
end
end
end
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("ls") { `ls x` }
test("上書き作成") { `echo > x` }
test("touch") { `touch x` }
test("touch (Ruby)") { F.touch("x") }
test("追記") { `echo >> x` }
test("chmod") { `chmod a+w x` }
test("chown") { `chown :wheel x` }
test("新規作成") { `rm -f x; echo > x` }
test("cat") { `cat x` }
test("touch") { `touch x` }
test("cat") { `cat x` }
test("追記") { `echo >> x` }
test("cat") { `cat x` }
test("cat") { `cat x` }
test("cat") { `cat x` }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("追記") { File.open("x", "a") { |e| e.puts "x" } }
test("追記") { File.open("x", "a") { |e| e.puts "x" } }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("touch -m") { `touch -m x` }
test("touch -m (Ruby)") { File.utime(File.atime("x"), Time.now, "x") }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("touch -m") { `touch -m x` }
test("FileUtils.touch") { F.touch("x", mtime: Time.now) }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("追記") { `echo >> x` }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("chmod") { `chmod a+w x` }
test("chown") { `chown :wheel x` }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("追記") { `echo >> x` }
test("chmod") { `chmod a+w x` }
test("新規作成") { `rm -f x; echo > x` }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("追記") { `echo >> x` }
test("追記") { `echo >> x` }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("cat") { `cat x` }
test("touch") { `touch x` }
test("cat") { `cat x` }
test("追記") { `echo >> x` }
test("cat") { `cat x` }
test("cat") { `cat x` }
test("cat") { `cat x` }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("touch") { `touch x` }
test("touch (Ruby)") { F.touch("x") }
end
Context.new.call do
test("新規作成") { `rm -f x; echo > x` }
test("上書作成") { `echo > x` }
test("新規作成") { `rm -f x; echo > x` }
test("追記") { `echo >> x` }
end
まとめ
- ctime の c は change の c
- atime の a は access だけど中身を読み出したときに変化するので言葉のイメージとしては access というより read の方があっている
- birth は ext4 では使えない
- ls したぐらいじゃ何も更新されない
- 追記すると modify だけが更新されるらしいが実際は change も更新される
- 追記では access は更新されないが、たまに更新される
- chmod や chown で change のみ更新される
- 上書きは中身を更新したのと同じ
- cat するとmodify > access または change > access のとき access が更新されるのは確かだけど、条件無視で更新されることもあった
- access の更新タイミングは予想がつかない
- 反映される時間が現在の時間と1秒ずれていることがある
- 調べたことで余計にわからないことが増えた
Discussion