在给定文件描述符的情况下确定POSIX/OS X上文件大小的可靠方法
我编写了一个函数来观察一个文件(给出一个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
编译测试程序来测试它)。
- 没有规定的fsync()真正解决我的问题
- 我不想FSYNC()因为性能
你的问题不是“FSTAT缓存结果“,这是I/O系统缓冲写入。直到内核将I/O缓冲区刷新到底层文件系统,Fstat才会更新。
这就是为什么fsync修复你的问题,任何解决方案或多或少的问题必须做相当于fsync。 (这是开放/关闭解决方案的副作用。)
不能帮你2,因为我没有看到任何方式来避免做fsync。
编辑我的问题,'fsync()'显然不能完全解决问题,我看到一个案例,它不足够,我想它只是暂停程序执行一段时间,因此减少了我可以观察竞态条件的情况的数量。 –
你可以尝试在调用stat之前做一个'fchown(fd,-1,-1)'吗? – cnicutar
@cnicutar试过了,没有帮助:-( –