cp: 無法創建普通文件 : 文件已存在


背景

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

報錯的信息是

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM