テクノロジー · 実用ガイド

rsync --link-dest でファイルがコピーされた? パス構造と保持属性を検証する

比較元ディレクトリのパスを転送先から解決し、属性の不一致を明らかにします。ハッシュの一致だけを信用せず、デバイス番号と inode 番号でハードリンクを検証しましょう。

相対ファイルパスが一致する転送元、前回の比較元、新しいスナップショットのツリーを示した、独自制作の編集用イラスト。
独自制作の編集用イラスト:ハードリンクが作成されると期待する前に、--link-dest を転送先から解決し、相対パスが同じであることを確認します。 Watch at Ease

rsync --link-dest では、ファイルのバイト内容が同一であるだけでは不十分です。前回のファイルが同じ相対パスに存在し、コマンドが保持するすべての属性も一致している必要があります。転送先を基準とした比較元パスの誤り、--relative のプレフィックス、モードやグループの差、あるいは --ignore-times の指定によって、ファイルの内容が変わっていなくても別のコピーが作成されることがあります。

ハードリンクの作成を検証可能なバックアップ特性として扱います。正確なコマンドと受信側で解決したパスを記録し、同じコマンドに --dry-run と --itemize-changes を加えて実行してから、空のスナップショットへ書き込み、デバイス番号と inode を比較します。ハッシュの一致はバイト列の一致だけを示し、ストレージの共有を証明しません。

1. コマンドを確定し、空の転送先を用意する

転送元と転送先の末尾のスラッシュ、-a を展開した各オプション、-R または --relative、-H、ACL または xattr のオプション、除外指定、すべての --link-dest を含め、実行する rsync コマンドをそのまま障害調査メモに記録します。新しく作成した空のスナップショットディレクトリを使ってください。rsync の公式マニュアルは、転送先に既存ファイルがあると、その属性が変更され、変更内容を項目別に示す出力の解釈も難しくなる場合があると注意しています。

診断を手早く済ませるために --ignore-times を追加してはいけません。rsync のマニュアルは、このオプションを使うと --link-dest によるリンク作成が行われなくなると明記しています。リンク作成は転送の代わりに実行される処理であり、更新後に行う二巡目の処理ではないためです。

2. 比較元のパスを転送先から解決する

DEST=/backups/2026-10-28、--link-dest=../2026-10-27 の場合、比較元は /backups/2026-10-27 として解決します。転送先パスと、転送先に比較元の相対パスを組み合わせたパスの両方に realpath を使い、新しい転送で作成されるのと同じ相対パスに、前回のファイルが存在することを確認します。比較元の相対パスを、現在のシェルの作業ディレクトリから解決してはいけません。

末尾がスラッシュのソースはディレクトリの内容を転送し、スラッシュがなければソースディレクトリの階層が一つ増えます。--relative はソースの接頭パスも保持します。絶対パスの基準は DIR の開始位置の曖昧さをなくすだけで、--relative が加えた接頭パスを消しません。以前のツリーには、転送後と同じ相対パスが必要です。

3. バイト内容の一致と属性の一致を分けて確認する

元の rsync コマンドを、同じ実行元ホスト、ユーザーアカウント、作業ディレクトリから実行します。転送元と転送先の指定、およびその他のすべてのオプションは変えず、--dry-run と --itemize-changes を追加してください。比較元と転送先のパス、およびそれらのデバイス番号は受信側ホストで確認します。チェックサムが一致しても止めず、項目別表示の文字を解釈します。p、o、g、t、a、x の差は、rsync がローカルコピーを作成した理由になり得ます。ハードリンクされた名前ごとに異なる inode メタデータを持つことはできません。

転送元と前回スナップショットの対応する候補ファイルを stat で比較し、モード、所有者、グループ、サイズ、最終更新時刻を記録します。特定の属性を意図的に保持しない場合は、その方針を明示的に決め、転送元、前回スナップショット、今回の実行で一貫させてください。重複排除を無理に成立させるために、属性保持の指定をむやみに外してはいけません。

4. ファイルシステムと rsync の制約を確認する

GNU のドキュメントは、ハードリンクがファイルシステムの境界を越えられないことを説明しています。前回のファイルと、新しい転送先ディレクトリのデバイス番号を記録します。番号が異なる場合、rsync は前回のファイルを転送の基準ファイルとして使える可能性がありますが、ファイルシステムをまたぐハードリンクは作成できません。特殊なファイル種別やファイルシステムには、さらに制限がある場合もあります。

-H と --link-dest を区別してください。-H は転送元にすでに存在するハードリンクのグループを保持します。--link-dest は、変更されていないファイルについて、別のスナップショット階層内のファイルへのリンクを作成します。一方を指定しても、もう一方に関する比較元パスの誤りは修正されません。

5. 一度実行し、リンクを実証する

実際に実行する際は、確認済みのコマンドから --dry-run(-n を使った場合は -n)を外し、ほかの確認済みオプションは保持したまま rsync で空の転送先へ書き込みます(以下の stat -c の確認には GNU stat を使います)。変更のない通常ファイルをいくつか抽出し、前回と新しいスナップショットの両方のパスに対して stat -c '%d %i %h %a %s %Y %n' を実行します。デバイス番号と inode 番号が一致した場合にのみ、容量が節約されたという主張を認めます。リンク数にも、名前が一つ追加されたことが反映されているはずです。

内容の完全性を確認するためにハッシュも記録しますが、そこから導く結論はリンクの検証結果と分けてください。ローカルの rsync 3.2.7 検証用フィクスチャでは、属性が完全に一致すると inode が同じになり、リンク数は 2 でした。モードだけを変更すると、項目別出力は cf...p..... となり、新しい inode が作られました。--ignore-times を指定した場合は >f......... と表示され、さらに別の新しい inode が作られました。いずれの場合も、SHA-256 ハッシュはすべて一致していました。

6. バージョン固有の失敗は証拠を添えて報告する

エスカレーション前に観測結果で分岐します。計算した --relative パスに以前の候補ファイルがない場合は、転送されるパスの階層と、選んだ以前のスナップショットにそのファイルが含まれ、正常に完了しているかの両方を確認します。ファイルがないだけでは接頭パスの不一致を証明できません。階層の不一致を確認した場合は、転送設定のパス構造を修正します。参照先が不完全、またはそのファイルを含まない場合は、利用できれば、その候補を含む検証済みの完全な参照スナップショットを選びます。利用できる参照がなければ、そのファイルには新しいコピーが必要だと考えてください。p、o、g、t、a、x は、保持対象の属性値が異なることを示します。変更を決める前に、転送元と以前のファイルのメタデータを、意図した保持方針と照合してください。-I または --ignore-times はリンクによる転送の代替を無効にし、デバイス番号が異なるとハードリンクは作成できません。これらの確認をすべて通過した場合だけ、ローカルとリモートの rsync バージョン、転送モード、項目別出力、最小再現を記録します。2026 年の一次情報では、daemon の相対基準パスの回帰によってデータが警告なく再転送される問題が記録され、プロジェクトで修正されました。すべての失敗を同じ不具合と決めつけず、バージョンの証拠を残してください。

この手順で実証できるのは、抽出したパスにおける通常ファイルのハードリンク再利用です。あらゆる ACL、xattr、シンボリックリンク、デバイスノード、リモートマウント、古い rsync ビルドを保証するものではありません。バックアップが対応を約束している具体的なファイル種別に合わせてサンプルを広げ、その後は新しいスナップショットで inode が再利用されているか、想定外の転送バイト数が発生していないかを監視してください。

デバイス番号と inode 番号の一致という証拠を、ハッシュは同じでも inode が異なるファイルと比較した、独自制作の編集用イラスト。
独自制作の編集用イラスト:ハッシュの一致はバイト内容の一致を証明し、デバイス番号と inode 番号の一致は、二つの名前がハードリンクであることを証明します。 Watch at Ease