cp: 無法建立普通檔案 : 檔案已存在

all發表於2020-05-25

背景

碰到一個偶現的編譯出錯問題,如圖

報錯的資訊是

cp: 無法建立普通檔案"xxx": 檔案已存在

排查原因

看了下 Makefile,這句非常簡單,就是 cp ./xxx ../xxx 而已,本身沒什麼問題。

那再結合上下文出現的列印,一個異常之處就是 Makfeile 被並行重複執行了,猜測是並行導致 cp 操作出錯。

只考慮解決問題,那無疑是修改外層 Makefile ,避免此處被並行重複執行,至少這句 cp 不要被並行,就可以解決了。

但為什麼 cp 並行執行會出錯呢?如果在另外的場景下確實有並行執行cp的可能,有沒有辦法規避這個錯誤呢?這就得探究下了。

單獨執行 cp,預設的行為就是覆蓋已存在的檔案,並不會因為 “檔案已存在” 這樣的原因出錯,隨便做下實驗,touch a b; cp a b就可以確認正常是不會報錯的。

那問題還是得結合並行來分析,碰到這種情況,要麼是從搜尋資料獲得提示,要麼就是實踐出真知,自己設計一個可快速復現的方式,然後使用除錯工具來追蹤問題發生時的具體情況。

具體到這個問題,我是搜尋到相同的stackexchange問題,那就省點工夫不用自己去復現分析了。

這裡插下題外話,搜尋優先使用google,對於中文報錯資訊查不到的可改成英文查詢。例如中文的 cp: 無法建立普通檔案 檔案已存在 就不好找到答案,換成 cp cannot create regular file file exists 就好找了。(只敲一部分,搜尋引擎就能提示完整的資訊)

stackexchage上給出了一個指令碼,用於復現問題並使用 strace 將追蹤的系統呼叫記錄下來

#!/bin/bash

touch a

f() {
  while true; do
    rm -f b
    strace -o /tmp/cp${BASHPID}.trace cp a b || break
  done
}

cleanup() {
  kill -9 %1 %2
}

f &
f &

trap cleanup exit

wait

附上我自己的實驗結果,可以看出cp的實現上,會先用stat來判斷目標檔案b是否存在,如果不存在則會使用 open("b", O_WRONLY|O_CREAT|O_EXCL, 0664) 來建立目標檔案並將原始檔寫入目標檔案,完成複製。

那麼如果兩個 cp 併發,就可能出現

cp1                  cp2
stat判斷b不存在     
                     stat判斷b不存在
open成功,建立檔案b         
                     open失敗,因為此時檔案已經被cp1建立好了

stracelog 看到的就是

由於 cp 不是原子的,如果兩個 cp 剛好幾乎同時執行,則可能兩個 cpstat都判斷到檔案不存在,那最終只有一個 cp 能建立檔案,另一個就失敗了。

順便看看,檔案存在和不存在的open引數差異

解決辦法

既然兩個cp同時執行會出錯,那就加鎖唄。

如果所有呼叫 cp 的地方都是我們可控的,那勸告鎖就足夠了,在 shell 中可以直接使用 flock

約定好一個檔案鎖x, 將原來的cp a b 改成 flock x cp a b 即可。

例如正常在兩個控制檯中,執行top是可以並行的,但如果改成執行 flock /tmp/toplock top,那就只有控制檯1會執行top,控制檯2則處於等待檔案鎖的狀態。此時若控制檯1退出top,則控制檯2獲得鎖,開始執行top

更多檔案鎖的細節,可以看看 man flock

blog: https://www.cnblogs.com/zqb-all/p/12942556.html
公眾號:https://sourl.cn/S42YSr

相關文章