2010-07-11 98 views
6

如果我要重命名AB,但只有當B不存在,天真的事情會被檢查是否存在B(與access("B", F_OK)或類似的東西),如果它不與rename進行。不幸的是,這會打開一個窗口,在此窗口期間,其他進程可能會決定創建B,然後它會被覆蓋 - 更糟糕的是,沒有跡象表明發生過類似情況。如何在沒有競爭條件的情況下重命名()?

其他文件系統訪問功能不從這個痛苦 - openO_EXCL(所以複製的文件是安全的),而最近的Linux得到了*at系統調用是防止大多數其他競爭條件的整個家庭 - 但不是這個特殊的(renameat存在,但防止完全不同的問題)。

那麼它有一個解決方案?

+0

也許您應該考慮使用顯式鎖定機制,而不是依賴隱式鎖定(包裝)重命名函數。如果創建'B'是您控制的程序,則可以使用進程間同步原語。 – 2010-07-11 09:37:55

回答

14

您應該可以將link(2)更改爲新的文件名。如果鏈接失敗,那麼你放棄了,因爲該文件已經存在。如果鏈接成功,您的文件現在既存在舊名稱又存在新名稱。那麼你unlink(2)舊的名字。沒有可能的競爭條件。

+0

什麼是在不同的掛載點下的文件? – 2010-07-11 17:47:11

+2

@Moron:rename()不適用於不同的文件系統。 – Dummy00001 2010-07-11 19:54:51

+0

@ Dummy00001:我明白了。我錯過了標題中的括號。 +1然後:-) – 2010-07-11 21:11:39

1

從命名手冊頁:

如果NEWPATH已經存在,它將被 原子代替(但有幾個 條件;見下文錯誤),使 沒有一點在其另一 嘗試訪問新路徑 的進程會發現它丟失。

因此,當B文件已經存在時,不可能避免重命名。我想你也許別無選擇,只能在你嘗試重命名之前檢查是否存在(使用stat()而不是access()),如果你不想在文件已經存在的情況下發生重命名。忽略競賽狀況。

否則,下面用link()提出的解決方案似乎符合您的要求。

+0

如果文件B不存在,如果您測試是否存在,則仍然存在競爭條件 - 兩個進程都可能檢測到它不存在,然後兩者都進行重命名,並關於最後哪個進程重命名的結果具有不確定的結果。唯一的解決方案是rename()是原子的,如果新的文件名已經存在,就會失敗,這就是它在Windows上所做的,以及我認爲C標準指定的(看起來不正確)。 – 2010-07-11 08:58:33

6

你可以link()到你想要的新文件名的現有文件,然後刪除現有的文件名。

link()只有在新路徑名尚不存在的情況下才能成功創建新鏈接。

是這樣的:

int result = link("A", "B"); 

if (result != 0) { 
    // the link wasn't created for some reason (maybe because "B" already existed) 
    // handle the failure however appropriate... 
    return -1; 
} 

// at this point there are 2 filenames hardlinked to the contents of "A", 
// filename "A" and filename "B" 

// remove filename "A" 
unlink("A"); 

這種技術在該文檔爲link()(見有關修改passwd文件的討論)討論:

3

對不起用於添加一些舊的線程。而且做這麼長的職位。

我只知道一種方法來完成競爭條件免費rename()在沒有鎖定的情況下,它應該在任何文件系統上都可以正常工作,即使在NFS間歇服務器重新啓動和客戶機時間扭曲就位。

下面的配方是無競爭條件的,因爲在任何情況下數據都不會丟失。它也不需要鎖,可以由不想合作的客戶執行,除了他們都使用相同的算法。

從某種意義上講,它不是競爭條件,如果某件事嚴重破壞,所有東西都會保持整齊乾淨的狀態。它也有很短的時間,在那裏既不是來源也不是目的地,然而來源仍然是另一個名字。而且對於攻擊者試圖挑起傷害的情況並沒有硬化(rename()是罪魁禍首,去圖)。

S是源,d是目的地,P(x)是dirname(x),C(X,Y)是x/y路徑的級聯

  1. 檢查所述目的地不存在。只是爲了確保我們不會徒勞地做下一步。
  2. 創建可能唯一的名稱T:= C(P(d),隨機)
  3. MKDIR(T),如果失敗循環到以前的步驟
  4. 開放(C(T, 「鎖」),O_EXCL ),如果失敗命令rmdir(T)忽略錯誤和循環到以前的步驟
  5. 重命名(S,C(T, 「TMP」))
  6. 鏈路(C(T, 「TMP」),d)
  7. 的unlink(C(T, 「TMP」))
  8. 的unlink(C(T, 「鎖定」))
  9. 命令rmdir(T)

算法safe_rename(S,D)解釋說:

的問題是,我們要確保沒有競爭條件,既不在源也沒有對目的地。假設(幾乎)每一步之間可能發生任何事情,但所有其他進程在進行無競爭狀態自由重命名時遵循完全相同的算法。這包括臨時目錄T永遠不會被觸及,除非在確認(這是一個手動過程)之後,使用該目錄的進程已經死亡並且不能被複活(如在還原之後繼續虛擬機休眠)。

要正確做到rename(),我們需要一些地方隱藏起來。所以我們構建一個目錄的方式可以確保沒有其他人(誰遵循相同的算法)意外地使用它。

但是mkdir()不能保證在NFS上原子化。因此,我們需要確保我們有一定的保證,我們在目錄中獨處。這是lockfile上的O_EXCL。嚴格來說,這不是鎖定,而是信號量。

除了這種罕見的情況,mkdir()通常是原子。我們還可以創建一些使用密碼安全的隨機名稱作爲目錄,添加一些GUID,主機名和PID以確保其他人偶然選擇相同名稱的可能性不大。然而爲了證明算法是正確的,我們需要這個文件名爲lock

既然我們有一個大部分都是空的目錄,那麼我們就可以安全地在rename()的源代碼那裏。這確保沒有其他人改變來源,直到我們將unlink()它。 (好吧,內容可以改變,這不是問題。)

現在link()技巧可以用來確保我們不覆蓋目的地。

然後unlink()可以在剩餘的來源完成比賽條件免費。剩下的就是清理。

只有一個問題留給:

萬一link()失敗,我們已經搬到源。爲了正確清理,我們需要將其移回。這可以通過調用safe_rename(C(T,"tmp"),S)來完成。如果這也失敗了,我們所能做的就是嘗試儘可能多地清理(unlink(C(T,"lock")),​​),並讓管理員手動清理垃圾。

最後說明:

爲了幫助碎片情況進行清理,你都不可能使用一些更好的文件名比tmp。聰明地選擇名字也可以使算法免受攻擊。

如果你正在移動文件的列車載入文件,你可以重複使用目錄。

但是,我同意,這個算法是純粹的矯枉過正和O_EXCL之類的rename()丟失。