2013-12-09 65 views
2

我編寫了一個函數來觀察一個文件(給出一個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試過了,沒有幫助:-( –

回答

2
  1. 沒有規定的fsync()真正解決我的問題
  2. 我不想FSYNC()因爲性能

你的問題不是「FSTAT緩存結果「,這是I/O系統緩衝寫入。直到內核將I/O緩衝區刷新到底層文件系統,Fstat纔會更新。

這就是爲什麼fsync修復你的問題,任何解決方案或多或少的問題必須做相當於fsync。 (這是開放/關閉解決方案的副作用。)

不能幫你2,因爲我沒有看到任何方式來避免做fsync。

+0

編輯我的問題,'fsync()'顯然不能完全解決問題,我看到一個案例,它不足夠,我想它只是暫停程序執行一段時間,因此減少了我可以觀察競態條件的情況的數量。 –

相關問題