在不更改 inode 的情況下原子地寫入文件(保留硬連結)
X
在 Unix 上安全、原子地寫入文件的正常方法是:
- 將新文件內容寫入臨時文件
Y
。rename(2)
Y
到X
在兩個步驟中,我們似乎除了
X
“就地”改變之外什麼也沒做。它可以防止競爭條件和意外數據失去(其中
X
被破壞但Y
不完整或被破壞)。這樣做的缺點(在這種情況下)是它不會寫入
X
in-place 引用的 inode;rename(2)
使得X
引用一個新的 inode 號。當
X
一個文件的連結計數> 1(顯式硬連結)時,現在它不像以前那樣引用相同的inode,硬連結被破壞了。消除該缺點的明顯方法是就地寫入文件,但這不是原子的,可能會失敗,可能導致數據失去等。
有什麼方法可以像原子一樣進行
rename(2)
但保留硬連結?也許將
Y
(臨時文件)的 inode 編號更改為與 相同X
,並為其X
命名?一個 inode 級別的“重命名”。這將有效地寫入
X
withY
的新內容所引用的 inode,但不會破壞其硬連結屬性,並會保留舊名稱。如果假設的 inode “重命名”是原子的,那麼我認為這將是原子的並且可以防止數據失去/競爭。
問題
您在這裡有一個(大部分)詳盡的系統呼叫列表。
您會注意到沒有“替換此 inode 的內容”呼叫。修改該內容總是意味著:
步驟 4 可以提前完成。還有一些快捷方式,例如pwrite,它直接在指定的偏移量處寫入,結合步驟#2和#3,或分散寫入。
另一種方法是使用記憶體映射,但它會變得更糟,因為寫入的每個字節都可能獨立發送到底層文件(概念上就像每次寫入都是 1 字節
write
呼叫)。→ 重點是您可以擁有的最佳方案仍然是 2 個操作:一個
write
和一個truncate
。無論您執行它們的順序如何,您仍然冒著另一個程序在其間弄亂文件並最終導致文件損壞的風險。
解決方案
正常解決方案
正如您所指出的,這就是為什麼規範方法是創建一個新文件,您知道您是唯一的作者(您甚至可以通過組合
O_TMPFILE
and來保證這一點linkat
),然後自動將舊名稱重定向到新文件。還有其他兩種選擇,但都以某種方式失敗:
強制鎖定
它允許通過設置特殊標誌組合來拒絕其他程序的文件訪問。聽起來像是工作的工具,對吧?然而:
- 它必須在文件系統級別啟用(它是掛載時的標誌)。
- >
**警告:**強制鎖定的 Linux 實現是不可靠的。
從 Linux 4.5 開始,強制鎖定已成為可選功能。這是完全刪除此功能的第一步。
這是合乎邏輯的,因為 Unix 總是避開鎖。它們容易出錯,並且不可能涵蓋所有邊緣情況並保證沒有死鎖。
諮詢鎖定
它是使用fcntl系統呼叫設置的。但是,它只是建議性的,大多數程序都會忽略它。
事實上,它只適用於管理多個協作程序之間共享文件的鎖。
結論
有沒有辦法像 rename(2) 一樣原子地做它但保留硬連結?
不。
索引節點是低級的,幾乎是一個實現細節。很少有 API 承認它們的存在(我相信
stat
呼叫系列是唯一的)。無論您嘗試做什麼,都可能依賴於濫用 Unix 文件系統的設計,或者只是對其要求過高。
這可能是一個XY問題嗎?