Rename

在不更改 inode 的情況下原子地寫入文件(保留硬連結)

  • November 2, 2018

X在 Unix 上安全、原子地寫入文件的正常方法是:

  1. 將新文件內容寫入臨時文件Y
  2. rename(2) YX

在兩個步驟中,我們似乎除了X“就地”改變之外什麼也沒做。

它可以防止競爭條件和意外數據失去(其中X被破壞但Y不完整或被破壞)。

這樣做的缺點(在這種情況下)是它不會寫入Xin-place 引用的 inode;rename(2)使得X引用一個新的 inode 號。

X一個文件的連結計數> 1(顯式硬連結)時,現在它不像以前那樣引用相同的inode,硬連結被破壞了。

消除該缺點的明顯方法是就地寫入文件,但這不是原子的,可能會失敗,可能導致數據失去等。

有什麼方法可以像原子一樣進行rename(2)但保留硬連結?

也許將Y(臨時文件)的 inode 編號更改為與 相同X,並為其X命名?一個 inode 級別的“重命名”。

這將有效地寫入XwithY的新內容所引用的 inode,但不會破壞其硬連結屬性,並會保留舊名稱。

如果假設的 inode “重命名”是原子的,那麼我認為這將是原子的並且可以防止數據失去/競爭。

問題

您在這裡有一個(大部分)詳盡的系統呼叫列表。

您會注意到沒有“替換此 inode 的內容”呼叫。修改該內容總是意味著:

  1. 打開文件以獲取文件描述符。
  2. 可選 尋找所需的寫入偏移量
  3. 寫入文件。
  4. 可選 如果新數據較小,則截斷舊數據。

步驟 4 可以提前完成。還有一些快捷方式,例如pwrite,它直接在指定的偏移量處寫入,結合步驟#2和#3,或分散寫入

另一種方法是使用記憶體映射,但它會變得更糟,因為寫入的每個字節都可能獨立發送到底層文件(概念上就像每次寫入都是 1 字節write呼叫)。

→ 重點是您可以擁有的最佳方案仍然是 2 個操作:一個write和一個truncate

無論您執行它們的順序如何,您仍然冒著另一個程序在其間弄亂文件並最終導致文件損壞的風險。

解決方案

正常解決方案

正如您所指出的,這就是為什麼規範方法是創建一個新文件,您知道您是唯一的作者(您甚至可以通過組合O_TMPFILEand來保證這一點linkat),然後自動將舊名稱重定向到新文件。

還有其他兩種選擇,但都以某種方式失敗:

強制鎖定

它允許通過設置特殊標誌組合來拒絕其他程序的文件訪問。聽起來像是工作的工具,對吧?然而:

  • 它必須在文件系統級別啟用(它是掛載時的標誌)。
  • >

**警告:**強制鎖定的 Linux 實現是不可靠的。

從 Linux 4.5 開始,強制鎖定已成為可選功能。這是完全刪除此功能的第一步。

這是合乎邏輯的,因為 Unix 總是避開鎖。它們容易出錯,並且不可能涵蓋所有邊緣情況並保證沒有死鎖。

諮詢鎖定

它是使用fcntl系統呼叫設置的。但是,它只是建議性的,大多數程序都會忽略它。

事實上,它只適用於管理多個協作程序之間共享文件的鎖。

結論

有沒有辦法像 rename(2) 一樣原子地做它但保留硬連結?

不。

索引節點是低級的,幾乎是一個實現細節。很少有 API 承認它們的存在(我相信stat呼叫系列是唯一的)。

無論您嘗試做什麼,都可能依賴於濫用 Unix 文件系統的設計,或者只是對其要求過高。

這可能是一個XY問題嗎?

引用自:https://unix.stackexchange.com/questions/478314