Ruby でファイルのタイムスタンプを調べる

## タイムスタンプは3種類(+1種類)

UNIX/Linuxの部屋 コマンド:タイムスタンプ http://x68000.q-e-d.net/~68user/unix/pickup?%A5%BF%A5%A4%A5%E0%A5%B9%A5%BF%A5%F3%A5%D7

> UNIX・Linux のファイルには、一般的に 3つのタイムスタンプがある。
> ・atime … 最終アクセス時刻 (access time)
> ・mtime … 最終変更時刻 (modify time)
> ・ctime … 最終ステータス変更時刻 (change time)

ファイルシステムによってはファイル作成日時を表す birthtime というタイムスタンプも存在する。

## Ruby でファイルのタイムスタンプを調べるスクリプト

ファイルのタイムスタンプは File クラスや File::Stat クラスで調べることができる

Ruby でファイルを作成、更新(ファイル追記)、状態更新(chmod相当)、読み込み、コピーなどしてどのようにタイムスタンプが変化するのかを調べてみる。

```ruby
require 'fileutils'

# ファイルの情報を表示
def stat(path)
  s = File::Stat.new(path)
  begin
    puts "btime: #{s.birthtime} (ファイル作成日時)"
  rescue NotImplementedError => e
    puts e.inspect
  end
  puts "mtime: #{s.mtime} (最終更新日時 modify time)"
  puts "ctime: #{s.ctime} (最終状態変更日時 change time)"
  puts "atime: #{s.atime} (最終アクセス日時 access time)"
end

path  = 'abc.txt'  # 作成するファイルパス
path2 = 'abc2.txt' # コピー先のファイルパス

# すでに存在している場合はファイルを削除
File.delete(path)  if File.exist?(path)
File.delete(path2) if File.exist?(path2)

# ファイルを作成
puts '[Create file]'
File.write(path, "hello, world\n")
stat(path)
sleep(3)

# ファイルを更新
puts '[Update file]'
File.write(path, "good-bye, world\n", {:mode => 'a+'})
stat(path)
sleep(3)

# ファイルの状態を更新
puts '[Change mode]'
File.chmod(0666, path)
stat(path)
sleep(3)

# ファイルを読み込む
puts '[Read file]'
f = File.read(path)
stat(path)
sleep(3)

# ファイルをコピー
puts '[Copy file]'
FileUtils.copy(path, path2, {:preserve => true})
stat(path2) # ここだけコピー先ファイルの情報を出力している　
sleep(3)

# 更新日時を更新
puts '[Update time]'
now = Time.now
File.utime(now, now, path)
stat(path)
sleep(3)
```

macOS Sierra + Ruby 2.4.1 でのスクリプト実行結果。

```
[Create file]
btime: 2017-05-17 13:48:13 +0900 (ファイル作成日時)
mtime: 2017-05-17 13:48:13 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:13 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:13 +0900 (最終アクセス日時 access time)
[Update file]
btime: 2017-05-17 13:48:13 +0900 (ファイル作成日時)
mtime: 2017-05-17 13:48:16 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:16 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:13 +0900 (最終アクセス日時 access time)
[Change mode]
btime: 2017-05-17 13:48:13 +0900 (ファイル作成日時)
mtime: 2017-05-17 13:48:16 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:19 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:16 +0900 (最終アクセス日時 access time)
[Read file]
btime: 2017-05-17 13:48:13 +0900 (ファイル作成日時)
mtime: 2017-05-17 13:48:16 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:19 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:22 +0900 (最終アクセス日時 access time)
[Copy file]
btime: 2017-05-17 13:48:16 +0900 (ファイル作成日時)
mtime: 2017-05-17 13:48:16 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:25 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:25 +0900 (最終アクセス日時 access time)
[Update time]
btime: 2017-05-17 13:48:13 +0900 (ファイル作成日時)
mtime: 2017-05-17 13:48:28 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:28 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:28 +0900 (最終アクセス日時 access time)
```

## birthtime について

Linux 上で FileStat#birthtime をコールすると NotImplementedError が発生する。

```
`birthtime': birthtime() function is unimplemented on this machine (NotImplementedError)
```

df -T コマンドで調べてみたら、この Linux のファイルシステムは ext4 だった

UNIX/Linuxの部屋 コマンド:タイムスタンプ http://x68000.q-e-d.net/~68user/unix/pickup?%A5%BF%A5%A4%A5%E0%A5%B9%A5%BF%A5%F3%A5%D7

> 伝統的な UNIX では、ファイル生成時刻は保持していない。ただし FreeBSD 5.x などで使用されている UFS2 では、inode が生成された時刻「birthtime」という情報がある (struct stat の st_birthtime)。これぞまさしくファイル生成時刻と言えるだろう。

macOS では birthtime がサポートされている。

stat(2) Mac OS X Developer Tools Manual Page https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/stat.2.html

> st_birthtime
> 
> Time of file creation. Only set once when the file is created. This field is only available in the 64 bit inode variants. On filesystems where birthtime is not available, this field holds the ctime instead.

Linux (ファイルシステムは ext4) + Ruby 2.4.1 でのスクリプト実行結果。

```
[Create file]
#<NotImplementedError: birthtime() function is unimplemented on this machine>
mtime: 2017-05-17 13:48:06 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:06 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:06 +0900 (最終アクセス日時 access time)
[Update file]
#<NotImplementedError: birthtime() function is unimplemented on this machine>
mtime: 2017-05-17 13:48:09 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:09 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:06 +0900 (最終アクセス日時 access time)
[Change mode]
#<NotImplementedError: birthtime() function is unimplemented on this machine>
mtime: 2017-05-17 13:48:09 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:12 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:06 +0900 (最終アクセス日時 access time)
[Read file]
#<NotImplementedError: birthtime() function is unimplemented on this machine>
mtime: 2017-05-17 13:48:09 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:12 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:15 +0900 (最終アクセス日時 access time)
[Copy file]
#<NotImplementedError: birthtime() function is unimplemented on this machine>
mtime: 2017-05-17 13:48:09 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:18 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:15 +0900 (最終アクセス日時 access time)
[Update time]
#<NotImplementedError: birthtime() function is unimplemented on this machine>
mtime: 2017-05-17 13:48:21 +0900 (最終更新日時 modify time)
ctime: 2017-05-17 13:48:21 +0900 (最終状態変更日時 change time)
atime: 2017-05-17 13:48:21 +0900 (最終アクセス日時 access time)
```

## statx について

Linux 4.11 からはシステムコールの statx 関数で birthtime が取得できるようになるが、 glibc ではまだラッパー関数が提供されていないし、 Ruby 側でもサポートしていない。

statx(2) - Linux manual page http://man7.org/linux/man-pages/man2/statx.2.html

> statx() was added to Linux in kernel 4.11.

> Glibc does not (yet) provide a wrapper for the statx() system call; call it using syscall(2).

statx 関数で取得できる statx 構造体に birthtime となる stx_btime メンバがある。

```
/* The following fields are file timestamps */
struct statx_timestamp stx_atime;  /* Last access */
struct statx_timestamp stx_btime;  /* Creation */
struct statx_timestamp stx_ctime;  /* Last status change */
struct statx_timestamp stx_mtime;  /* Last modification */
```

試しに自分の環境 Linux 3.16 で確認したところまだ入っていなかった。

```
$ man statx
statx というマニュアルはありません

$ man 2 statx
statx というマニュアルはセクション 2 にはありません

$ uname -rs
Linux 3.16.0-4-amd64
```

## 参考資料

- class File::Stat (Ruby 2.4.0) https://docs.ruby-lang.org/ja/latest/class/File=3a=3aStat.html
- UNIX/Linuxの部屋 コマンド:タイムスタンプ http://x68000.q-e-d.net/~68user/unix/pickup?%A5%BF%A5%A4%A5%E0%A5%B9%A5%BF%A5%F3%A5%D7
- stat(2) Mac OS X Developer Tools Manual Page https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/stat.2.html