systemd環境におけるハードウェアクロック調整

切迫した事情があったわけではありませんが、気になって調べたので結果をまとめておきます。
テスト環境はopenSUSE Tumbleweedですが、まぁsystemd環境では大概同じでしょう。

# まずはsystemdに尋ねる
`$ timedatectl status`して、得られる出力はこんな感じになります。

```console:bash
$ timedatectl status
      Local time: 火 2017-11-07 18:29:15 JST
  Universal time: 火 2017-11-07 09:29:15 UTC
        RTC time: 火 2017-11-07 18:29:15
       Time zone: Asia/Tokyo (JST, +0900)
 Network time on: no
NTP synchronized: yes
 RTC in local TZ: no
$ 
```

ただし上例は一点を除いて全く正常で、その点も例外事項で異常ではありません。
で、以下の箇所を確認しましょう。

1. NTPを使う設定になっているのに`NTP synchronized: no`になっていないか
2. 特別な理由がないのに`RTC in local TZ: yes`になっていないか

## NTPが機能するようにする
お使いのディストリビューションでどのNTPデーモンを使っているかに注意して、使用中のNTPデーモンの設定を確認しましょう。
systemd-timesyncd(8)、ntpd(1)、chrony(1)、openntpd[^1]など色々な実装がありますが、`Network time on: yes`となるのはsystemd-timesyncdがenabledな場合に限られます[^2]ので多くの環境では当該項目はあてにならないことを頭に入れておいてください。先ほどの例では、ntpd(1)により時刻が同期されていたためnoながら`NTP synchronized: yes`になっています。
ここでは各実装における設定ファイルの書き方には触れません。

[^1]: ある事情のためPiで使ってるので知っていますが、ハリセンボン族の一員であるopenntpdにはopenntpdとしてのmanpageはありません。その代わり、具体的なコマンドについての`ntpd(8)`や`ntpctl(8)`がありますが、他を快調にカッコ付けした続きにこうくるとかなり難しいものがあります。。一般的な方のntpdと丸かぶり。

[^2]: 20181020追記： ~~この項目の名前ですが、現在のsystemdでは`systemd-timesyncd.service active`というものに変更されています。普通のNTPデーモン使ってる人には微妙な変更ですよね。`time-sync.target`あたりで上手いことできないのでしょうか。~~ **→20181228追記：バージョン239でsystemd-time-sync-wait.serviceなるものが追加され、同時に`NTP service`というジェネリックな表記に変わった** ので今後のディストリビューションではこの辺の微妙さは解消されていることでしょう。

## RTCがローカル時刻に設定されている場合
Windows環境を上書きしてインストールした環境などでよくなっていますが、Linuxにおいてこの状態は好ましくありません[^3]。Windowsとのデュアルブート仕様である[^4]などといった特別な事情がなければ、コマンド一発`# timedatectl set-local-rtc 0`で直してしまいましょう。

なお、この操作により`/etc/adjtime`[^5]が書き換えられるはずですが、手元の環境では真っ白のままでした。

[^3]: その理由は[ArchWiki](https://wiki.archlinux.jp/index.php/%E6%99%82%E5%88%BB#.E6.99.82.E5.88.BB.E7.B3.BB)に書かれており、要約すると「これをやると夏時間の類による切り替えをLinux側ではハンドルできず、それだけのためにローカル時刻RTCの実装された別のOSを起動する必要が発生する」というものであるようです。

[^4]: [Archwiki](https://wiki.archlinux.jp/index.php/%E6%99%82%E5%88%BB#Windows_.E3.81.A7_UTC_.E3.82.92.E4.BD.BF.E3.81.86)によれば、Windows側をUTCなハードウェアクロックに適応させる方法もあるようです。

[^5]: 2015年の更新で現れた[FHS3.0において、このパスは`/var/lib/hwclock/adjtime`というものに定義の変更がされています](https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html#varlibhwclockStateDirectoryForHwclo)。しかし2018年10月現在util-linux側ではこの変更に追従していないため、現在もほとんどの環境では`/etc/adjtime`となっているはずです。

# ハードウェア時刻のお世話をする
まずは`$ cat /etc/adjtime`でadjtimeファイルの中身を見てみましょう。

```text:/etc/adjtime
0.000000 1510044982 0.000000
1510044982
UTC
```

まず、3行目はタイムゾーンではなく「UTCかローカルタイムか」が書かれている部分です。ローカルタイムの場合、LOCALと書かれますが先ほど書いた通りそれは推奨されませんし、timedatectl(8)で変更した場合ここが書き換えられるので、手で触れることはないでしょう。

で、2行目と1行目の真ん中にあるのがdrift値で、ここに整数が入っていれば一回以上このファイルは書き込まれているわけです。
1行目の両端にある数字は、おそらく2回以上drift調整が行われた時に入る差分の値または係数かと思います[^6]。

1行目、2行目がオール0だった場合、これまたコマンド一発`# hwclock --utc --update-drift --systohc`で書くことができます。
しかし、システムクロックとハードウェアクロックの差分で調整するという仕組み上どうしても一回書くだけではあまり意味をなしてくれないでしょう。何らかの事情でNTPが機能しなくなった場合には当然役に立ちますから、週一や月一でsystemd.timer(5)なりcron(8)なりに入れておいて損はないでしょう[^7]。

なお、ntpdを運用中の場合はどうもntpd自身がよろしく調整してくれるらしいという情報も（但し手元の環境ではそんなことありませんでした）。その場合は下手に触らない方がいいと思います。

[^6]: このファイル、ググった限りでは正確な定義がどこにも載っていないのです。hwclock(8)のソースを読めば多分わかるのでしょうがそこまではやっておらず。。→20180904追記：[読みました。](https://github.com/karelzak/util-linux/blob/master/sys-utils/hwclock.c)私の稚拙なCスキルで読んだ限りですが、どうも右端の数字は「RTCを書けないが読めはするプログラムがズレを認識した時にここに書いておくと、hwclock(8)はこれを加味してズレを計測する」といったような役割があるようです。つまりRTCに触れる存在がhwclock(8)のみであればここの値は誰も書かないので0こそ正常値だということのようです。
[^7]: systemd.timer(5)を使った場合はサービスファイルも書かねばなりませんが、ジャーナルに出力をリダイレクトさせることができるので[--verbose付きのサービスファイルを書いておけば時刻調整の様子を克明に知ることができる](https://qiita.com/slug/items/5165d60860d3dc9d4f9c)ようになります。

# 調整の完了
単に`$ timedatectl status`と`$ cat /etc/adjtime`の出力を確認して、おかしなところがなければOKです。
hwclockコマンドをtimer作ったりcronに入れたりした人は、drift値の変動が収まったタイミングを見計らって`# systemctl disable`なりcrontabから削除なりしても良いでしょう。

お疲れ様でした。