多進程同時讀寫 .ini 格式設定檔時,檔案經常發生損毀。初期嘗試使用檔案鎖(BSD flock 與 POSIX fcntl)均無法解決。
核心問題:使用 `stat(filename, &sb)` 根據檔名取得檔案大小,再依此大小讀取檔案內容到記憶體。但在多進程環境下,另一進程可能正在寫入該檔案,導致 `stat` 取得的 size 不正確(過大或過小),進而讀取到不完整或錯誤的資料,寫回時造成檔案損毀。
| 階段 | 錯誤做法 | 正確做法 |
|---|---|---|
| 1. 開啟檔案 | fopen / open | fopen / open |
| 2. 取得大小 | stat(filename, &sb) ← 竄態條件 | fstat(fd, &sb) ← 基於已開啟的 fd |
| 3. 加鎖 | flock/fcntl | flock/fcntl (讀鎖/寫鎖) |
| 4. 讀取內容 | 依 stat 的 size 讀取 | 依 fstat 的 size 讀取 |
| 5. 寫入/釋放 | 寫回 → 解鎖 | 寫回 → 解鎖 |
// 錯誤:根據檔名 stat,檔案可能已被其他進程修改
// stat(pconfig->fileName, &sb);
// 正確:根據已開啟的檔案描述符 fstat,原子性較高
ret = fstat(fd, &sb);
int fd = open(filename, O_RDWR);
// 1. 申請讀鎖(共享鎖)或寫鎖(獨佔鎖)
flock(fd, LOCK_SH); // 或 fcntl(fd, F_SETLK, &read_lock)
// 2. 獲取鎖成功後,立即 fstat 取得準確大小
struct stat sb;
fstat(fd, &sb);
size_t size = sb.st_size;
// 3. 根據 size 分配記憶體並讀取
char *buf = malloc(size + 1);
read(fd, buf, size);
buf[size] = '\0';
// 4. 處理資料...
// 5. 寫入時申請寫鎖
flock(fd, LOCK_EX);
ftruncate(fd, 0); // 清空檔案
lseek(fd, 0, SEEK_SET);
write(fd, new_data, new_size);
flock(fd, LOCK_UN);
close(fd);
原文提到:ini 庫在 `ret` 返回失敗時,未正確設定輸出參數。建議修改:
#define NULL_STR '\0'
#define NULL_VAL 0xFFFF
// 失敗時將輸出參數設為非法值,避免呼叫端使用髒資料
if (ret != SUCCESS) {
*out_string = NULL_STR;
*out_value = NULL_VAL;
}
這個案例深刻說明了:檔案鎖只能保護「已開啟檔案描述符」的操作序列,無法保護「檔名路徑查找」過程。正確的同步邊界必須包含 `open → lock → fstat → read/write` 完整鏈路。