我編寫了一個函數來觀察一個文件(給出一個fd)增長到一定的大小,包括超時。我使用kqueue()
/kevent()
等待文件被「擴展」,但在得到文件增長的通知後,我必須檢查文件大小(並將其與所需大小進行比較)。這似乎很容易,但我無法找到一種在POSIX中可靠地執行此操作的方法。在給定文件描述符的情況下確定POSIX/OS X上文件大小的可靠方法
注意:如果文件在指定的時間內根本不增長,超時將會命中。所以,這不是一個絕對超時,只是文件發生一些增長的超時。我在OS X上,但這個問題是爲「每個POSIX有kevent()
/kqueue()
」,這應該是OS X和我認爲的BSD。
這是我目前我的版本功能:
/**
* Blocks until `fd` reaches `size`. Times out if `fd` isn't extended for `timeout`
* amount of time. Returns `-1` and sets `errno` to `EFBIG` should the file be bigger
* than wanted.
*/
int fwait_file_size(int fd,
off_t size,
const struct timespec *restrict timeout)
{
int ret = -1;
int kq = kqueue();
struct kevent changelist[1];
if (kq < 0) {
/* errno set by kqueue */
ret = -1;
goto out;
}
memset(changelist, 0, sizeof(changelist));
EV_SET(&changelist[0], fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_RENAME | NOTE_EXTEND, 0, 0);
if (kevent(kq, changelist, 1, NULL, 0, NULL) < 0) {
/* errno set by kevent */
ret = -1;
goto out;
}
while (true) {
{
/* Step 1: Check the size */
int suc_sz = evaluate_fd_size(fd, size); /* IMPLEMENTATION OF THIS IS THE QUESTION */
if (suc_sz > 0) {
/* wanted size */
ret = 0;
goto out;
} else if (suc_sz < 0) {
/* errno and return code already set */
ret = -1;
goto out;
}
}
{
/* Step 2: Wait for growth */
int suc_kev = kevent(kq, NULL, 0, changelist, 1, timeout);
if (0 == suc_kev) {
/* That's a timeout */
errno = ETIMEDOUT;
ret = -1;
goto out;
} else if (suc_kev > 0) {
if (changelist[0].filter == EVFILT_VNODE) {
if (changelist[0].fflags & NOTE_RENAME || changelist[0].fflags & NOTE_DELETE) {
/* file was deleted, renamed, ... */
errno = ENOENT;
ret = -1;
goto out;
}
}
} else {
/* errno set by kevent */
ret = -1;
goto out;
}
}
}
out: {
int errno_save = errno;
if (kq >= 0) {
close(kq);
}
errno = errno_save;
return ret;
}
}
所以基本算法的工作方式如下:
- 搭建KEVENT
- 檢查大小
- 等待文件增長
重複步驟2和3,直到文件達到所需的大小。
代碼使用功能int evaluate_fd_size(int fd, off_t wanted_size)
將返回< 0
爲「某些錯誤發生或文件不是想大」,== 0
爲「文件不夠大呢。」,或> 0
文件已經達到想要的大小。
顯然這隻有在evaluate_fd_size
在確定文件大小方面是可靠的。我的第一步是用off_t eof_pos = lseek(fd, 0, SEEK_END)
來實現它,並比較eof_pos
與wanted_size
。不幸的是,lseek
似乎緩存了結果。所以即使kevent
與NOTE_EXTEND
返回,所以文件增長,結果可能是相同的!然後我想轉到fstat
,但found articles that fstat
caches as well。
我試過的最後一件事是在off_t eof_pos = lseek(fd, 0, SEEK_END);
之前使用fsync(fd);
,並突然開始工作。但是:
- 沒有指出
fsync()
真正解決我的問題 - 我不想
fsync()
因業績
編輯:這真的很難重現,但我看到了一個案例其中fsync()
沒有幫助。這似乎需要(非常少的)時間,直到在事件觸及用戶空間後文件大小更大。 fsync()
可能只是作爲一個足夠好的sleep()
,因此它大多數時間工作: - 。
換言之:如何在不打開/關閉文件的情況下可靠地檢查POSIX中的文件大小,這是因爲我不知道文件名。此外,我找不到保證,這將有助於
順便說一下:int new_fd = dup(fd); off_t eof_pos = lseek(new_fd, 0, SEEK_END); close(new_fd);
沒有克服緩存問題。我也創建了all in one demo program。如果在退出前打印Ok, success
,則一切正常。但通常它打印出Timeout (10000000)
這表示競爭條件:在此刻觸發的最後一個kevent的文件大小檢查小於實際文件大小。奇怪的是,當使用ftruncate()
來生長文件而不是write()
時,它似乎可行(您可以使用-DUSE_FTRUNCATE
編譯測試程序來測試它)。
你可以嘗試在調用stat之前做一個'fchown(fd,-1,-1)'嗎? – cnicutar
@cnicutar試過了,沒有幫助:-( –