在给定文件描述符的情况下确定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; 
    } 
} 

所以基本算法的工作方式如下:

  1. 搭建KEVENT
  2. 检查大小
  3. 等待文件增长

重复步骤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_poswanted_size。不幸的是,lseek似乎缓存了结果。所以即使keventNOTE_EXTEND返回,所以文件增长,结果可能是相同的!然后我想转到fstat,但found articles that fstat caches as well

我试过的最后一件事是在off_t eof_pos = lseek(fd, 0, SEEK_END);之前使用fsync(fd);,并突然开始工作。但是:

  1. 没有指出fsync()真正解决我的问题
  2. 我不想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编译测试程序来测试它)。

+0

你可以尝试在调用stat之前做一个'fchown(fd,-1,-1)'吗? – cnicutar

+0

@cnicutar试过了,没有帮助:-( –

  1. 没有规定的fsync()真正解决我的问题
  2. 我不想FSYNC()因为性能

你的问题不是“FSTAT缓存结果“,这是I/O系统缓冲写入。直到内核将I/O缓冲区刷新到底层文件系统,Fstat才会更新。

这就是为什么fsync修复你的问题,任何解决方案或多或少的问题必须做相当于fsync。 (这是开放/关闭解决方案的副作用。)

不能帮你2,因为我没有看到任何方式来避免做fsync。

+0

编辑我的问题,'fsync()'显然不能完全解决问题,我看到一个案例,它不足够,我想它只是暂停程序执行一段时间,因此减少了我可以观察竞态条件的情况的数量。 –