工程趣聞 2026 年 8 月 10 日

2026-08-10 — 追出 Zsh 歷史紀錄遺失的訊號競態、Project Oberon 移植上 RISC-V、nixpkgs 全版本封存計畫

primary=https://michael.stapelberg.ch/posts/2026-08-09-zsh-history-truncation-bug/ primary=https://github.com/zsh-users/zsh/commit/a6760226c75c8a13e78f8b4c7163f1256322531a primary=https://github.com/rochus-keller/OberonSystem/tree/op2-rv32 primary=https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed

追查 Zsh 歷史紀錄憑空消失的訊號競態

michael.stapelberg.ch · 2026-08-09

Michael Stapelberg 發現自己的 ~/.zsh_history 偶爾會憑空少掉前一天的指令,檔案沒有壞掉,只是變短了。這個 bug 存在於 zsh 5.9.1,最後被追查到是登出時按 Ctrl+D、Ctrl+C 的習慣動作觸發的一個訊號競態。檔案結構完好但內容被截斷,讓問題長期難以被發現。

問題現象

症狀很單純:某些登出後,.zsh_history 的行數會比平常少很多,且只保留很久以前的紀錄。沒有任何錯誤訊息或崩潰紀錄,只有安靜消失的資料。這種間歇性、無法穩定重現的特性,是排查的最大障礙。

追查過程

作者先用 inotify 觀察到 zsh 會讀出舊檔、寫到 .zsh_history.new,再 rename 回原檔;接著用 fatrace 確認兇手就是 zsh 本身。真正定位問題靠的是自寫的 bpftrace 腳本,追蹤系統呼叫後發現:發生截斷時,zsh 讀到的位元組數比正常情況少,而且少了代表 EOF 的 read = 0

// Src/hist.c,加入計數器,寫入行數 < 50000 就主動觸發 crash
if (lineno < 50000) abort();

靠這個土法煉鋼的插樁,作者用 coredumpctl 配合 GDB 檢查 core dump,看到 errflag = 2lasthist.interrupted = 1,證實是訊號中斷造成的。

根因與修法

登出時 zexit() 會呼叫 savehistfile() 壓縮歷史檔;readhistfile() 內部會檢查 errflag & ERRFLAG_INT 並在收到中斷時提前結束讀取,但 savehistfile() 寫入階段完全沒有做同樣的檢查。連續按 Ctrl+D、Ctrl+C 送出的 SIGINT 因此讓寫入在讀完舊資料前就被截斷。修法已在 zsh 5.9.2(2026-07-12 發布)納入,對應上游 commit a6760226c75c8a13e78f8b4c7163f1256322531a,在 savehistfile() 遞迴呼叫前補上中斷檢查,中斷時直接回傳錯誤而非繼續寫入。

原始來源:Tracking down a Zsh history data loss bugzsh commit a676022


把 Niklaus Wirth 的 Project Oberon 系統搬上 RISC-V

GitHub (rochus-keller/OberonSystem) · op2-rv32 分支

Rochus Keller 在 GitHub 上放出一個把 Project Oberon——Niklaus Wirth 在 1988 年設計的教學型作業系統、編譯器與硬體——移植到 RISC-V 的分支 op2-rv32。原版 Oberon 是綁定在 Wirth 自製的 RISC-5 處理器上執行的完整軟硬體系統,這次移植讓它能在現代開放指令集上重現。

原本的問題

Project Oberon 原始碼是為 Wirth 自己的 RISC-5 架構寫的 Oberon-07 語言,與市面上任何現行處理器都不相容。過去的移植嘗試多半得改寫 Wirth 的原始編譯器本身,維護成本高,也難以套用到其他移植目標。

採用的方法

這次移植不改 Wirth 的原始編譯器,改用支援 RISC-V 後端的 OP2 編譯器,並把系統原始碼從 Oberon-07 轉寫成 Oberon-90(例如把 INTEGER 重新命名為 LONGINT),讓同一顆編譯器能服務多個移植目標。整個系統仍編進單一開機映像檔,所有模組靜態連結、沒有動態載入,記憶體映射與 Kernel、Display、Input 等核心模組的介面都刻意維持原樣,把「軟硬體契約」的改動降到最低。

實際效果

目前系統跑在基於 rv32emu 模擬的 RISC-V 虛擬機器上,重現了 Wirth 原本處理器的行為,並附有 Linux/Windows 的預編譯執行檔可直接試跑,畫面截圖顯示系統已能在虛擬機上原生運作。下一步目標是移植到 ESP32-P4-PC 之類便宜的實體開發板,讓 Project Oberon 不只活在模擬器裡。

原始來源:rochus-keller/OberonSystem (op2-rv32)


nixpkgs-multiverse:把 nixpkgs 存在過的每個版本都封存起來

fzakaria.com · 2026-08-09

Farid Zakaria 做了一個叫 nixpkgs-multiverse 的 Nix flake,目標是讓「nixpkgs 史上出現過的每個套件版本」都能隨時取用,不用再手動釘死一堆舊的 nixpkgs input。整個方案只用約 200 行 Nix 程式碼加上 5MB 的 JSON 索引,不需要額外建置、鏡像或架站。

原本的問題

nixpkgs 版本一升級,舊版套件就從當前分支消失,使用者只能在 flake 裡疊加多個 nixpkgs input 去釘住舊版本。這些 input 全部是急切(eager)抓取,就算專案根本用不到,也會拖慢 flake 的 evaluation 速度。

採用的方法

作者用 nix-releases S3 bucket 找出所有真正被 Hydra 建置過的 commit,整理成兩份索引:記錄 1,393 個 nixpkgs revision(commit、日期、channel、narHash)的 revisions.json,以及把套件屬性與版本對應到 revision 位移的 versions.json關鍵設計是每個版本只保留「最新出現過它的那個 revision」,而不是保存全部歷史紀錄,索引才能一直維持在 5.18MB。真正抓取套件時透過 builtins.fetchTree 用 narHash 做懶惰(lazy)拉取,而非預先下載。

實際效果

索引目前涵蓋 289,521 組不重複的(套件屬性, 版本)配對。實測上,5 個沒用到的傳統 nixpkgs input 光是抓取就要耗掉約 26 秒;相比之下,nixpkgs-multiverse 即使背後有 1,393 個 revision 可查,解析 JSON 只要 0.2 秒。使用者可以直接指定版本執行,例如:

nix run 'github:fzakaria/nixpkgs-multiverse#versions.python3."3.6.2"' -- --version

原始來源:nixpkgs-multiverse: every version that ever existed


End of article
0
Would love your thoughts, please comment.x
()
x