1 ポイント 投稿者 GN⁺ 2024-09-30 | 1件のコメント | WhatsAppで共有
  • 32ビット glibc システムでは 2038 年以降、現在時刻の取得や stat() 呼び出しが失敗する可能性があり、Gentoo には 64ビット time_t へ安全に移行する経路が必要
  • glibc の time64 は Large File Support(LFS) と併用する必要があり、32ビット環境には既存 ABI、LFS ABI、LFS+time64 ABI が併存する
  • time_t が API、構造体、関数引数に含まれると、型幅の変化は ABI 破壊 につながり、time32 と time64 のバイナリを混在させるとランタイムの誤動作やセキュリティリスクが生じる
  • ソースベースのディストリビューションである Gentoo では、@world の再ビルド中に失敗や循環依存が起きるため、半分だけ移行されたシステム が残る可能性がある
  • 2024-09-30 の訂正以降、libdir の切り替えだけでは不十分となり、動的ローダーや複数言語のツールチェーンまで含めた time64 マーカー が中核的な制約として残っている

2038年問題と time64 移行の範囲

  • 32ビット time_t を使う 32ビットアプリケーションは、2038 年に現在時刻の代わりに -1 エラーを受け取ったり、ファイルに対して stat() を実行できなくなったりする可能性がある
  • 移行の基本方針は time_t64ビット型 に変えること
    • musl はすでに移行済み
    • glibc はこれをオプションとしてサポートしている
    • Debian など一部のディストリビューションはすでに移行している
  • Gentoo のような ソースベースのディストリビューション では、ユーザーがシステムを自分で再ビルドするため、パッケージ同士が異なる ABI 状態にとどまる時間を減らす必要がある
  • 中核的なリスクは、time_t の幅の変更が ABI を壊す点にある
    • ライブラリ API に time_t が含まれていれば、そのライブラリにリンクされるすべてのコードが同じ型幅を使わなければならない
    • 一部だけを time64 に切り替える方式は安全ではない

LFS と 3つの 32ビット下位 ABI

  • 32ビットアーキテクチャでは、以前からファイル関連の型幅に問題があった
    • off_t はファイルオフセットに使われる
    • ino_t は inode 番号に使われる
    • もともと 32ビット幅だったため、2GiB を超えるファイルや 32ビット範囲を超える inode 番号が問題になっていた
  • これを解決するため Large File Support(LFS) が導入された
    • off_tino_t を 64ビット版に変更する
    • glibc では現在でも選択項目である
    • 多くのパッケージが upstream 側で LFS を有効にし、ABI 破壊に対応してきたが、問題が完全に解決したわけではない
  • glibc の time64 サポートは LFS の使用を要求 するため、ファイルサイズと時間の問題を同時に解決する構造になっている
  • 32ビットシステムには 3 つの下位 ABI が存在する
    • 既存 ABI: 32ビット型
    • LFS: 64ビット off_t、64ビット ino_t、32ビット time_t
    • time64: LFS + 64ビット time_t
  • 1つの glibc ビルドはこの 3 つのバリエーションと互換性を持つが、これらの型を API で使うライブラリでは 3 つのバリエーションを相互に混在させることはできない

ABI 変更が実際に壊れる仕組み

  • time_t が 32ビットから 64ビットに変わると構造体レイアウトが変わる
    • 例の構造体で int a, time_t b, int c がこの順にある場合、c のオフセットは 32ビット time_t と 64ビット time_t で異なる
    • time32 と time64 のバイナリを混在させると、誤ったフィールドを読み書きしたり、範囲外アクセスが起きたりする可能性がある
  • struct stat のサイズも ABI によって異なる
    • 32ビット x86 glibc のデフォルト: 88 バイト
    • LFS: 96 バイト
    • LFS + time64: 108 バイト
  • 構造体を使わなくても関数引数で問題が起きる
    • x86 では関数引数がスタック経由で渡される
    • 引数の 1 つが time_t だと、それに続く引数のスタック位置が変わる
  • 例の実験では、time32 プログラムが time64 で再ビルドされたライブラリにリンクされると値が壊れる
    • 本来の出力は a = 1、正常な時刻値、c = 3
    • ライブラリだけを -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64 で再ビルドすると、bc が誤って解釈される
  • 現在はこのような ABI 混在を防ぐ実効的な保護手段がなく、ランタイム破損 とセキュリティ問題が生じ得る

Gentoo での移行がより難しい理由

  • バイナリ配布のディストリビューションでは、すべてのパッケージを再ビルドした後、ユーザーは比較的アトミックな段階でアップグレードする
    • サードパーティのリポジトリやローカルビルドのプログラムがあると問題が起きることはあるが、全体の流れとしては比較的安全である
  • Gentoo では @world を再ビルドしながら、その場で ABI を変更しなければならない
    • 2つのパッケージがそれぞれ再ビルドされる間に、相互に互換性のない ABI が混在する可能性がある
    • 一部の再ビルド失敗によってシステムが 半分移行された状態 のまま残る可能性がある
    • 循環依存のため依存パッケージを再ビルドするとビルドツールが壊れ、その後の再ビルドが不可能になることもある

検討中の緩和策

  • 議論されている方向性は 3 つある
    • 新しい ABI を既存の 32ビット ABI と区別するため、プラットフォームタプルである CHOST を変更する
    • 新しい ABI の libdir を変更し、再ビルドしたライブラリを既存ライブラリとは別にインストールする
    • 異なる下位 ABI のバイナリがリンクされないように、バイナリレベルの ABI 識別 を導入する
  • 3 つの方法はある程度独立して実装できるが、一部は相互依存する可能性がある
  • 記事中の例示文字列は、最終的な解決策で使われる実際の文字列とは限らない

CHOST で ABI を区別する

  • プラットフォームタプルはツールチェーンが対象とするプラットフォームを識別するもので、Gentoo では multilib サポートのため ABI を一意に区別する用途にも使われる
  • タプルはアーキテクチャ、vendor、オペレーティングシステム、libc の 4 つの部分から構成される
    • 例: i386-pc-linux-gnu
    • 例: i686-pc-linux-gnu
    • 例: i686-unknown-linux-gnu
  • 新しい ABI を導入する際には、vendor フィールドを変えるか、libc フィールドに ABI 表記を付け加える方式が使われてきた
    • ARM hardfloat ABI では、過去に armv7a-hardfloat-linux-gnueabiarmv7a-unknown-linux-gnueabihf のような形式が使われていた
  • time64 ABI にも似たような選択肢がある
    • i686-gentoo_t64-linux-gnu
    • i686-pc-linux-gnut64
    • armv7a-gentoo_t64-linux-gnueabihf
    • armv7a-unknown-linux-gnueabihft64
  • タプル変更には大量のパッチは不要だと思われる
    • GNU ツールチェーンと GNU build system は、libc フィールド内で gnu の後ろにある内容を無視する
    • Clang では、タプルに応じて正しい ABI を自動選択できるようにするパッチが必要になる

libdir の変更と preserved-libs

  • libdir はライブラリのインストールディレクトリのデフォルト名
    • 一般的なデフォルト値は lib
    • 64ビット対応アーキテクチャでは慣例的に lib64 がよく使われる
    • x86 の x32 ABI は libx32、MIPS n32 ABI は lib32 を使う
  • time64 用 32ビット ABI では、libdir を lib から libt64 のような値に変える案が検討されている
  • 別個の libdir は移行中の ABI 混在を減らす仕組みになる
    • time64 実行ファイルが誤って time32 ライブラリにリンクされるリスクを減らす
    • Portage の preserved-libs 機能により time32 ライブラリを保持できる
    • 必要に応じて time32 + time64 の multilib プロファイルを提供し、既存の time32 事前ビルドアプリケーションとの互換性を維持できる
  • preserved-libs があれば、既存の実行ファイルは再ビルドされるまで time32 ライブラリを使い続け、time64 で再ビルドされたライブラリは新しい libdir にインストールされる
  • libdir の変更にはツールチェーンへのパッチが必要になる
    • glibc は同じライブラリ群が複数の下位 ABI に有効であるため、特別扱いできる可能性がある
    • time64 実行ファイルの .interp が time64 用 ld.so を参照するように、別個の ld.so が必要になる可能性がある
  • proper multilib サポートのためには、その ABI に固有のプラットフォームタプルも必要となる

バイナリレベルの非互換性の表示

  • 異なる ABI のバイナリを混在させた場合、通常はリンカや動的ローダーがそれを防ぐべきである
    • 64ビットプログラムを 32ビットライブラリにリンクすると、リンカは file in wrong format として拒否する
    • 動的ローダーも wrong ELF class: ELFCLASS32 のようなエラーで拒否する
  • 既存の ABI 識別には複数のメカニズムが使われている
    • ELFCLASS32ELFCLASS64
    • EM_386EM_X86_64 のような machine identifier
    • ARM と MIPS の flags フィールド
    • アーキテクチャ固有の attribute section
  • time32 と time64 にも同様のメカニズムが必要だが、簡単ではない
    • 再利用可能な汎用メカニズムが見当たらない
    • 複数アーキテクチャに対応する解決策が必要である
    • 新しい ELF note section を追加し、ツールチェーン側のサポートを実装する方法が現実的な候補に見える
  • 保護手段をユーザーが無効化する可能性も考慮しなければならない
    • ソースのない事前ビルドソフトウェアが time_t を使う API を呼び出さないなら、システムライブラリと引き続き動作できる可能性がある
    • これを無条件にブロックする処方は、問題そのものより悪い場合がある
  • 別個の libdir を使えば、比較的簡単な 非致命的 QA チェック を作れる
    • .interp により time64 実行ファイルを識別する
    • time32 プログラムが libt64 からライブラリを読み込んでいないか確認する
    • time64 プログラムが lib から直接ライブラリを読み込んでいないか確認する

事前ビルド 32ビットアプリケーションの限界

  • ソースからビルドされるパッケージとは別に、x86 と PowerPC には古い事前ビルドバイナリしか提供されないアプリケーションがある
    • 特にプロプライエタリソフトウェアや古いゲームが該当する
  • これらはシステムライブラリとの互換性問題と 2038年問題そのもの の両方を抱えている
  • 互換性問題については既存の multilib 構造がある程度の解決策を提供する
    • amd64 で 32ビットソフトウェアをサポートするため、すでに multilib レイアウトや複数ライブラリバージョンをビルドする仕組みがある
    • abi_x86_32abi_x86_t64 を区別する形に拡張できる
    • 両方の ABI をサポートする新しい multilib x86 プロファイルを作成できる
  • 2038 年以降の 32ビットプログラム自体の失敗は、より難しい問題として残る
    • faketime でシステム時刻を制御する方法がある
    • 時刻を過去に戻した VM を実行する方法もある

2024-09-30 訂正: libdir だけでは不十分

  • 当初の構想は楽観的すぎ、libdir の変更だけでは安定した分離は難しい
  • すべての libdir が ld.so.conf に列挙されるため、ld.so 内に libdir パスをハードコードする方式には依存できない
    • カスタム LLVM prefix でもすでにパスを調整しており、この場合にも特別な扱いが必要になる
  • このため、libdir の変更は バイナリ非互換性の識別 に依存する可能性が高くなった
  • 満たすべき基本目標は 3 つある
    • 動的ローダーが time32 と time64 のバイナリを区別できること
    • 明示的な time64 マーカーのないすべてのバイナリは、後方互換性のため time32 とみなされること
    • 新しくビルドされるすべてのバイナリは明示的な time64 マーカーを持つこと。Rust のような非 C 環境でビルドされたバイナリも含む
  • この目標は、複数言語にまたがる複数のツールチェーンへパッチを当てる必要がある規模の作業である
    • Gentoo がローカルだけで維持するのは難しく、複数の当事者による協力が必要になる
    • 対象アーキテクチャは概してレガシーと見なされるか、すでに十分なサポートがなくなっている場合がある
  • 他のツールチェーンが正しい time64 実行ファイルを生成できるかも別の問題である
    • C プログラムのように _TIME_BITS に従うよう調整されていなければ、特定の time_t 幅をハードコードして壊れる可能性がある
  • 明示的な time64 マーカーのないすべてのバイナリが time32 ライブラリを使うことになるため、Gentoo は適切なマーカーを付けるようパッチされていないサードパーティ実行ファイルを実行できなくなる
  • 目標を下げる代替案も検討されている
    • すべての time64 実行ファイルに RPATH を注入し、time64 libdir を直接強制する
    • この方法は動的ローダーが time32 ライブラリを使うことを完全には防げないが、大きな互換性問題なしに移行を助けられる
  • 逆に、time64 libdir を恒久的には変えず、time32 libdir を一時的に変える案もある
    • 既存プログラムに RPATH を注入し、libdir 名を変更する
    • 新しい time64 ライブラリは既存の libdir にインストールする
    • 新しい time64 プログラムは、time32 ライブラリを強制する RPATH を持たない
    • すでに移行済みの他ディストリビューションとの互換性を維持できる利点がある

残された課題

  • 3 つの解決策をすべて実装すれば、glibc を使う 32ビット Gentoo システムに対して、よりクリーンで比較的安全な移行経路を提供できる
  • ただし、これらの解決策は主にソースからビルドされるパッケージに適用される
  • 事前ビルドの 32ビットアプリケーションには、ABI 互換性が維持されても 2038年問題が残る
  • 全体設計はまだ草案段階であり、実験、議論、パッチ投稿に応じて今後も変わり得る

1件のコメント

 
GN⁺ 2024-09-30
Hacker News のコメント
  • Gentoo には記事で取り上げられていない選択肢がいくつかあり、Gentoo のシステム設計上、作業量が大きいため省かれたように思える

    1. パッケージをインストールせずに、そのパッケージを対象としてビルドできるようにすること。要点は、Gentoo のパッケージのビルドとインストールが一段階になっているため、相互に依存する複数の項目を先にビルドしてから成果物を原子的にインストールできない点にある。ABI 変更を伴うアップデート中は、システムが部分的に壊れやすい
    2. 一般的な .so バージョン管理を拡張し、依存パッケージの ABI 変化まで反映すること。通常、共有ライブラリは libfoo.so.1.0.0 のようにファイル名と内部バージョンにバージョン番号が入り、パッケージが自身の ABI 破壊を追跡する。64ビット time_t をサポートするには、各 .so の依存 ABI が制御するバージョン要素を追加する必要がある。記事の「別の libdir を使う」と結果は似ているが、将来の ABI 変更にも再利用できる基盤になり得る一方で、はるかに侵襲的である可能性が高い
    • 「インストールせずにビルド」するには、部分的な段階的アップデートが最も合っているように見える
      複数の新しいパッケージのビルドを予約してサンドボックス内でビルドし、新しいコンパイルは union を通じてまずサンドボックスを参照し、システムへフォールバックするようにできる。すべてビルドできたら成果物をパッケージングし、サンドボックスから実システムへ移せばよい。こうすれば Gentoo のアップデート全体をトランザクションのようにでき、他の面でも大きな利点が生まれる
    • Gentoo はすでに、完成したイメージを最終的にインストールするディレクトリとして指定する方式で 1 番をサポートしている。通常は / に設定される ROOT を変更すればよい
      @system@world 全体を再ビルドして指定したサブディレクトリにインストールし、その後まとめて同期できる。可能ならライブセッションで行うのがよく、理論上は新しくインストールした場所のサブディレクトリに / をバインドマウントしてから chroot に入り、実際の親 / へ同期する方法も可能である
      https://devmanual.gentoo.org/ebuild-writing/variables/#root
    • Gentoo はすでに、すべての依存項目が更新されるまで旧ライブラリを保持するので、ABI 変更を、本来 ABI 変更を記録すべき場所であるアーキテクチャと SONAME にエンコードすれば、この問題を解決できる
  • Mac OS X が off_tino_t を扱った方法がヒントになるかもしれない。既存の呼び出しと構造体は動作を維持し、64 接尾辞が付いた新しい呼び出しと型を追加し、プリプロセッサマクロで実際の参照先を選べるようにしていたが、直接使うことはまれだった
    代わりに OS と SDK がバージョン管理され、ビルド時にバイナリが実行される必要のある最も古い OS バージョンを指定できる。ヘッダはこれに基づいて適切なマクロを自動選択し、新 API/廃止 API の注釈も同じ仕組みで weak link や警告を生成していた。当初はプリプロセッサで処理していたが、今ではコンパイラが Apple の言う API availability をより精密に理解しているため、他のプラットフォームでも同じ方式が可能に見える

    • TFA が説明した核心的な問題は解決できない。異なる「コンパイル対象 OS バージョン」を使うアプリケーション同士は、もはやリンクできなくなる
      OS v.B 上で実行中でも、OS v.B 向けとして宣言されたアプリケーション X は、OS v.A 向けとして宣言されたアプリケーション Y とリンクできない可能性がある。実際、この方式はほとんどすべてのプラットフォームがすでに行っていることに近く、違うやり方をすれば既存バイナリ互換性が即座に壊れる
    • glibc 以外の補助ライブラリは、off_t のサイズごとに関数を複数組定義することはないだろうし、クライアントプログラムが望む型サイズに応じて正しい関数群をヘッダ内で透過的に選ぶスイッチもないだろう
      それでも記事は、off_t より time_t のほうが大きな問題だと強調している。もっともらしい理由は、time_t のほうがはるかに広く行き渡っているからである。off_t は比較的少数のインターフェースに関与する POSIX 型である一方、time_t は ISO C に属し、あちこちで使われている。さらに多くの C コードは time_tint と同じ幅の整数型だと仮定しているが、off_t についてそのような仮定をするケースはそれほど一般的ではない
    • 優雅な解法のように聞こえるが、実際にはひどいハックのように読める。型のないマクロは二度と扱いたくない悪夢だ
    • プラットフォーム全体に追従を強制できる場合にのみ機能する。良い解法ではあるが、C ライブラリを制御する必要がある。Gentoo は libc が何をするかを制御できず、ユーザーは GNU libc でも musl でも、別のものでも使える
  • Debian も非常に苦痛だった。何人かは燃え尽きた可能性があり、多くの人がソースベースのディストリビューションを指して「あちらはとても簡単だろう」と言っていた

    • Debian の time64 移行がどのように苦痛だったのか、詳しく見られる資料が気になる。外から見る限りでは比較的論争もなくスムーズで、たとえば /usr マージよりはずっと良く見えた
    • m68k、powerpc、sh4 の移行を行い、hppa も一部手伝ったが、他の Debian 開発者たちの助けもあってまだ生きている
    • 「簡単」というのが「ユーザーに一度に全部再ビルドしろと言えばよい」という意味なら、その通りだと思う
  • こうした問題で苦労しているのを見るたびに、FreeBSD の初期 amd64 ポート時にこの問題を推し進めておいて本当によかったと感じる。ABI の基本型を決めることができ、過去より未来を見ると決断したのだ。
    amd64 には、この作業を容易にした興味深い特徴があった。関数呼び出し中に 32 ビットの関数引数が 64 ビットへ自動キャストされるため、64 ビットの time_t を期待する関数に 32 ビットの時刻整数を渡しても、プラットフォーム初期の作業中はたいていそのまま動作した。そのため、細かな仕上げは後回しにできた。
    当時ほかの 64 ビットプラットフォームもあったが、64 ビットの time_t はなく、FreeBSD/amd64 が 2003〜2005 年ごろ、その系統では最初だった。記憶では sparc64 も 64 ビットの time_t に移行した。
    最大の問題は、当時 tzcode が 64 ビット安全ではなかったことだ。struct tm の正規化アルゴリズムが、time_t(2^62) の日/月/年を反復計算しようとするような縮退条件に陥っていた。tzcode を大きく変更するより、おおむね 1900 年以前や 10000 年以降は失敗させるように処理したと記憶している。今ではずっと以前に upstream で修正されている可能性が高い。
    数年にわたり、サードパーティコードがファイルやネットワーク上のデータ構造で int/long/time_t を雑に扱ったことによる 32/64 ビット時刻の混同を、モグラたたきのように処理したが、概して大きな問題ではなかった。初日から 64 ビットの time_t を使ったことが大半の問題を避けさせてくれたし、最初からやるのは簡単だった。Linux は amd64/x86_64 ポートを始める際に、同じことをする大きな機会を逃した。
    付け加えると、当時 64 ビットの ino_t は完了できなかった。32 ビットの inode 番号が、ファイルシステムのオンディスク構造、UFS ディレクトリ構造など、非常に多くの場所に露出していた。FreeBSD/amd64 が低い等級のプラットフォームだった時点で、ほかの tier-1 アーキテクチャを大きく揺るがさずに最初から対処する現実的な方法はなかった。作業は二度行ったが、最終的には別の人が仕上げ、mountpoint のパス長のような短すぎた定数もあわせて修正した。

    • 私の理解では、すべての 64 ビット Linux ポートは最初から 64 ビットの time_toff_tino_t を使っていた。今の問題は、32 ビット Linux を 64 ビット time_t に移行することだ。
    • FreeBSD は off_t もより大胆に扱い、2.0 から 64 ビットになっていた。Linux の 32 ビット版には、まだ古いサイズの痕跡が残っている。
      32 ビットの関数引数が呼び出し中に 64 ビットへ自動キャストされるという部分は、符号なし引数でのみ動作するものと理解している。%edi にロードすると %rdi の上位部分がクリアされるためだ。x86-64 用の SysV ABI 仕様は、レジスタやスタック上のすべての値を 64 ビット全体の値へ拡張するとは述べておらず、ブール値に関する注記も下位 1 バイトだけが意味を持つという趣旨なので、これが一般規則であることを示唆している。
    • amd64 が登場したときに FreeBSD が i386 の time_t も 64 ビットへ移植したという意味なら、かなり驚きだ。Motorola 68000 や sparc32 のようなほかの 32 ビットアーキテクチャも 64 ビットの time_t に移行したのか気になる。
  • 古い大型の 32 ビット Unix システムで将来の日付を扱うために、符号付き 32 ビット time_t の libc 関数群を、符号なし 32 ビット time_t 対応関数に置き換えたことがある。これで 2038 年以降さらに 68 年を稼げたし、そのころには私はもういないだろう。
    欠点は Unix epoch である 1970 年以前の日付を表現できないことだが、スケジュール管理システムなので問題にはならなかった。過去の日付が重要なら、epoch を数十年ずらしたり、時間分解能を 1 秒から 2 秒に下げたりすることもできる。それぞれ微妙な問題があるので、ユースケース次第だ。

    • システム全体を符号付きから符号なしに変更できるなら、なぜ 64 ビットにしなかったのか気になる。
  • 元の BSD マニュアルページでは、tunefs の “Bugs” セクションに “You can tune a file system, but you can't tune a fish.” という有名なジョークがあったが、“Expert C Programming” によれば、このマニュアルページのソースコードでは、そのジョークの横に次のようなコメントがあったという。
    「これを削除すると、UNIX デーモンが今から time_t が wrap around するまで、四つ足であなたを追い回すだろう。」
    70 年代にこの文が書かれたとき、2038 年は想像もできないほど遠い未来だったに違いない。
    https://progforperf.github.io/Expert_C_Programming.pdf

  • 最も強く感じたのは、努力は尊重するが、ユーザーの立場としては Debian のような ソースベースではないディストリビューションに移行して、この問題を終わらせたいということだ。

    • ソースベースのディストリビューションの難しさは、ABI に互換性のない変更を加えながら インプレースアップグレードを試みる点にあるようだ。そのため、まったく別のディストリビューションへ乗り換えることは、新しい ABI を使う Gentoo をクリーンインストールするのと同じくらい、あるいは時間は少なく済んでも、少なくとも同程度に disruptive になり得る。
    • Gentoo でも簡単に処理する方法はある。USB などから起動し、//usr パーティションに mkfs.ext4 か使用しているファイルシステムを実行してからマウントし、stage3 を展開して chroot に入り、emerge $all-my-packages-that-where-installed-before-mkfs を実行すればよい。
      段階的にアップグレードする代わりに、新しい Gentoo のコピーをインストールできる。
    • ソースベースではないディストリビューションに変えればよい、という区別はもう少し微妙だ。NixOS のようなソースベースのディストリビューションには同じ問題はない。核心はソースからビルドするかどうかより、Gentoo がパッケージをビルドしてインストールする方式にある。
      サードパーティのクローズドソースソフトウェアがある場合、バイナリシステムでも依然として問題が起きる可能性がある。独立した段階で別途インストールされるファーストパーティパッケージでも問題が起こり得る。
  • C の専門家ではありませんが、off_t のような型エイリアスは後から変更できるように導入されたものだと思っていました。ところが明確には機能していないように見えるので、自分の理解が間違っているのか気になります

    • ソース互換性とバイナリ互換性の違いです。off_t のような typedef を使えば、通常コードを書き直す必要はありませんが、その型を使っているものはすべて再コンパイルする必要があります
    • ある程度は機能しますが、ソースベースのディストリビューションではうまく合いません。off_t の定義を変えた後で @world をアトミックに再ビルドできるなら問題ありませんが、ソースベースのディストリビューションは @world をアトミックに再ビルドせず、パッケージを一つずつ再ビルドします
      すると libc.so は 64 ビットの off_t を使っているのに、gcc は 32 ビットの off_t を前提にビルドされていて gcc が止まる、といった問題が起こり得ます。bashcoreutilsmakebinutils のように @world の再ビルドに必要なパッケージが壊れることもあり、その時点で行き詰まります。だからこの種のアップグレードには注意が必要です
    • それはパズルの最初の一段階、あるいは半段階にすぎません。記事で述べられているように、off_t が構造体に入ったり、関数呼び出しで使われたり、プロトコルに組み込まれたりした瞬間に抽象化は消え、実際のサイズが重要になります
      ライブラリをロードしたりプロトコルで通信したりしながら古いコードと新しいコードを混在させると、オフセットがずれてクラッシュし始めます。結局、移行では全員がプログラムを「レガシー」と「移植済み、または少なくとも検証済み」に分けなければならず、非常に苦痛です
    • 型エイリアスはソースコードレベルで楽にしてくれるだけです。本当の、まして完全な抽象化ではありません。たとえば内部型を浮動小数点型に変えれば意味が大きく変わり、ユーザーコードに完全に露出します
      意味が似ているより大きな型に変えても壊れることがあります。単純な例として構造体のパディングがありますし、ポインタを整数に変換してから戻す使い方も多く、内部表現が変われば壊れざるを得ません。それが良い慣行かどうかは別として、珍しいことではありません。要点は ABI 互換性 です
    • 機能はしますが、ABI 変更には、変更時に あらゆる場所を同時に 変えなければならないという問題があります。基本的には、32 ビットの off_t でビルドされたライブラリと 64 ビットの off_t でビルドされたライブラリをリンクできないようにする仕組みはなく、結果の挙動は非常に予測不能になり得ます
  • 例の構造体では、32 ビットの time_t のとき c のオフセットが 8、64 ビット型のとき 12 だとされていますが、実際には 16 であるべきではないか と思います。b は 64 ビット境界にアラインされる必要があるので、ab の間にパディングが入るはずです。むしろ著者の主張をさらに強める部分です

    • 多くの x86 ABI では、当時 64 ビットロードがなかったため、64 ビット型に対する パディング を強制していません
  • こうしたことをすべて見ると、Windows の奇妙な時刻表現、つまり 1601 年 1 月 1 日 00:00 GMT をグレゴリオ暦で見た時点から 100 ns 単位の 64 ビットで数える方式にも、小さな利点はあります。解像度も素晴らしく、銀河全体が征服される時代まで動作するでしょう