2010-07-02 174 views
3

我需要打開一個文件,對其進行讀取鎖定,然後嘗試獲取寫入鎖定,但在讀取鎖定失敗時保持讀取鎖定。LockFileEx讀取/寫入升級/降級

這對POSIX使用fcntl鎖定效果很好。

在Windows中,我可以使用LockFileEx來獲取文件鎖定。我可以讀取和寫入鎖(共享和排他)。

但是,似乎在Windows中,我必須採取獨佔寫鎖第一個,然後添加讀鎖定。這與我在POSIX上做的相反,它會導致我的抽象層出現問題。當我在POSIX中按照該順序執行操作時,我通過讀取鎖定來丟失寫入鎖定,因爲fcntl取代了現有鎖定,而不像Windows那樣添加鎖定。

我可以用#ifdefs改變鎖定順序在呼叫站點,但我正在尋找好的想法來修復我的抽象代碼。

// This is the header file 
struct LockFileImpl; 
class LockFile { 
    protected: 
    boost::scoped_ptr<LockFileImpl> p; 

    public: 
    LockFile(const File &); 
    virtual ~LockFile(); 

    void unlock() const; 
    void rd_lock() const; 
    void wr_lock() const; 
    bool rd_try() const; 
    bool wr_try() const; 
}; 

class LockFileRead : public LockFile{ 
    public: 
    LockFileRead(const File &f) : LockFile(f) 
    { rd_lock(); } 
}; 

class LockFileWrite : public LockFile{ 
    public: 
    LockFileWrite(const File &f) : LockFile(f) 
    { wr_lock(); } 
}; 

// This is the Win32 implementation file. There's a different one for POSIX. 
struct LockFileImpl 
{ 
    handle_t hFile; 
    bool rd_locked; 
    bool wr_locked; 

    LockFileImpl(handle_t x) : hFile(x), rd_locked(false), wr_locked(false) 
    {} 
}; 

LockFile::LockFile(const File &f) 
    : p(new LockFileImpl(f.handle())) 
{ 
} 

LockFile::~LockFile() 
{ 
    unlock(); 
} 


void LockFile::unlock() const 
{ 
    if(p->wr_locked) { 
     throw_win32_err_if(UnlockFile(p->hFile, 0, 0, 1, 0) == 0); 
     p->wr_locked = false; 
    } 
    if(p->rd_locked) { 
     throw_win32_err_if(UnlockFile(p->hFile, 0, 0, 1, 0) == 0); 
     p->rd_locked = false; 
    } 
} 

void LockFile::rd_lock() const 
{ 
    OVERLAPPED over = {0}; 
    over.Offset = 0; 
    throw_win32_err_if(!LockFileEx(p->hFile, 0, 0, 1, 0, &over)); 
    p->rd_locked = true; 
    if(p->wr_locked) { 
     throw_win32_err_if(UnlockFile(p->hFile, 0, 0, 1, 0) == 0); 
     p->wr_locked = false; 
    } 
} 

void LockFile::wr_lock() const 
{ 
    OVERLAPPED over = {0}; 
    over.Offset = 0; 
    throw_win32_err_if(!LockFileEx(p->hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &over)); 
    p->wr_locked = true; 
} 

bool LockFile::rd_try() const 
{ 
    OVERLAPPED over = {0}; 
    over.Offset = 0; 
    bool r = !!LockFileEx(p->hFile, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &over); 
    if(r) { 
     p->rd_locked = true; 
     if(p->wr_locked) { 
      throw_win32_err_if(UnlockFile(p->hFile, 0, 0, 1, 0) == 0); 
      p->wr_locked = false; 
     } 
    } 
    return r; 
} 

bool LockFile::wr_try() const 
{ 
    OVERLAPPED over = {0}; 
    over.Offset = 0; 
    bool r = !!LockFileEx(p->hFile, LOCKFILE_FAIL_IMMEDIATELY|LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &over); 
    if(r) { 
     p->wr_locked = true; 
    } 
    return r; 
} 
+0

您是否需要在如此低的水平上操作?我的意思是,底層的狀態機顯然是不同的,如果你以這種方式直接暴露它們,你的班級在每個平臺上的表現都會有所不同。但是,如果您決定希望自己的類能夠抽象出正確的狀態模型,那麼您需要對底層平臺調用進行不同的命令,以便在暴露的抽象方面獲得一致的結果。 – 2010-07-06 13:32:06

+0

@克里斯:不,我不需要低水平運作。我需要能夠進行文件鎖定並升級並將其降級爲寫入/排除,而不會丟失讀取/共享鎖定。我也可以使用命名的Mutex或類似的東西在Windows上完全替代它。 – 2010-07-06 16:10:20

回答

0

爲什麼不使用pimpl方法?無論如何,你幾乎都在那裏。創建WinLockFileImplPosixLockFileImpl,它們都繼承了摘要LockFileImpl。然後,圍繞以下代碼放置一個ifdef以確定在編譯時使用哪個類。你必須已經有一個ifdef,在其他平臺上編譯時刪除Windows代碼,對嗎?

LockFile::LockFile(const File &f) 
#ifdef POSIX 
    : p(new PosixLockFileImpl(f.handle())) 
#else 
    : p(new WinLockFileImpl(f.handle())) 
#endif 

哦,你需要你的代碼進入執行類,其改變LockFile看起來更像是這樣的:

void LockFile::unlock() const 
{ 
    p->unlock(); 
} 
+0

我認爲你錯過了這個問題的重點。你錯過了我已經在使用pImpl的事實。發佈的代碼被簡化爲僅顯示Windows版本。 – 2010-11-03 17:17:09

+0

@贊:我的回答和@Chris Becke上面所說的一樣。爲什麼不在Windows impl中執行額外的步驟並將其從抽象的用戶中隱藏起來呢?這是否是一個問題,您不能在Windows上重新應用寫入鎖而不先刪除讀取鎖,然後重新應用它? – Harvey 2010-11-05 15:16:32

+0

沒錯。一旦我有鎖,我不能放棄,否則另一個進程會抓住它。 – 2010-11-05 16:04:26

0

我們有非常有限的鎖定要求,但下面的代碼似乎努力模仿POSIX fcntl足以達到我們的目的。請注意,根據鎖定區域大小區分讀鎖和寫鎖的方法(根據您的示例,此攻擊可能適用於您)。下面的代碼假定文件小於4GB。

// fcntl flock definitions 
#define F_SETLK 8 // Non-Blocking set or clear a lock 
#define F_SETLKW 9 // Blocking set or clear a lock 
#define F_RDLCK 1 // read lock 
#define F_WRLCK 2 // write lock 
#define F_UNLCK 3 // remove lock 
struct flock { 
    short l_type; // F_RDLCK, F_WRLCK, or F_UNLCK 
    short l_whence; // flag to choose starting offset, must be SEEK_SET 
    long l_start; // relative offset, in bytes, must be 0 
    long l_len; // length, in bytes; 0 means lock to EOF, must be 0 
    short l_pid; // unused (returned with the unsupported F_GETLK) 
    short l_xxx; // reserved for future use 
}; 

// only works for (SEEK_SET, start=0, len=0) file locking. 
__inline int fcntl(int fd, int cmd, ...) 
{ 
    va_list a; 
    va_start(a, cmd); 
    switch(cmd) 
    { 
    case F_SETLK: 
     { 
      struct flock *l = va_arg(a, struct flock*); 
      switch(l->l_type) 
      { 
      case F_RDLCK: 
       { 
        OVERLAPPED o = { 0 }; 
        HANDLE h = (HANDLE)_get_osfhandle(fd); 
        if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0) 
        { 
         _set_errno(ENOTSUP); 
         return -1; 
        } 
        if (!LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY, 0, 0, 1, &o)) // read lock 
        { 
         unsigned long x = GetLastError(); 
         _set_errno(GetLastError() == ERROR_LOCK_VIOLATION ? EAGAIN : EBADF); 
         return -1; 
        } 
        UnlockFile(h, 0, 0, 1, 1); // write lock 
       } 
       break; 
      case F_WRLCK: 
       { 
        OVERLAPPED o = { 0 }; 
        HANDLE h = (HANDLE)_get_osfhandle(fd); 
        if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0) 
        { 
         _set_errno(ENOTSUP); 
         return -1; 
        } 
        if (!LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY|LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 1, &o)) // write lock 
        { 
         unsigned long x = GetLastError(); 
         _set_errno(GetLastError() == ERROR_LOCK_VIOLATION ? EAGAIN : EBADF); 
         return -1; 
        } 
        UnlockFile(h, 0, 0, 0, 1); // read lock 
       } 
       break; 
      case F_UNLCK: 
       { 
        HANDLE h = (HANDLE)_get_osfhandle(fd); 
        if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0) 
        { 
         _set_errno(ENOTSUP); 
         return -1; 
        } 
        UnlockFile(h, 0, 0, 0, 1); // read lock 
        UnlockFile(h, 0, 0, 1, 1); // write lock 
       } 
       break; 
      default: 
       _set_errno(ENOTSUP); 
       return -1; 
      } 
     } 
     break; 
    case F_SETLKW: 
     { 
      struct flock *l = va_arg(a, struct flock*); 
      switch(l->l_type) 
      { 
      case F_RDLCK: 
       { 
        OVERLAPPED o = { 0 }; 
        HANDLE h = (HANDLE)_get_osfhandle(fd); 
        if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0) 
        { 
         _set_errno(ENOTSUP); 
         return -1; 
        } 
        if(!LockFileEx(h, 0, 0, 0, 1, &o)) // read lock 
        { 
         unsigned long x = GetLastError(); 
         return -1; 
        } 
        UnlockFile(h, 0, 0, 1, 1); // write lock 
       } 
       break; 
      case F_WRLCK: 
       { 
        OVERLAPPED o = { 0 }; 
        HANDLE h = (HANDLE)_get_osfhandle(fd); 
        if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0) 
        { 
         _set_errno(ENOTSUP); 
         return -1; 
        } 
        if (!LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 1, &o)) // write lock 
        { 
         unsigned long x = GetLastError(); 
         return -1; 
        } 
        UnlockFile(h, 0, 0, 0, 1); // read lock 
       } 
       break; 
      case F_UNLCK: 
       { 
        flock *l = va_arg(a, flock*); 
        HANDLE h = (HANDLE)_get_osfhandle(fd); 
        if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0) 
        { 
         _set_errno(ENOTSUP); 
         return -1; 
        } 
        UnlockFile(h, 0, 0, 0, 1); // read lock 
        UnlockFile(h, 0, 0, 1, 1); // write lock 
       } 
       break; 
      default: 
       _set_errno(ENOTSUP); 
       return -1; 
      } 
     } 
     break; 
    default: 
     _set_errno(ENOTSUP); 
     return -1; 
    } 

    return 0; 
} 

用的fcntl鎖定的主要問題的FileLock鎖定是,當你注意的是,小告誡說(來自文檔):

如果相同的範圍被鎖定有一個專用和共享鎖定,解鎖該區域需要兩次解鎖操作;第一次解鎖操作解鎖排他鎖,第二次解鎖操作解鎖共享鎖。

這意味着你不能只從一個共享鎖進入同一區域的排他鎖,而沒有先完全釋放該區域的鎖。你使用標誌的方法很接近,我想如果你添加另一個標誌,說i_have_both_locks_but_only_really_want_the_exclusive_lock,那麼你的解決方案可以工作。我們沒有編寫我們自己的界面的奢侈(我們被fcntl困住了)。但幸運的是,使用fcntl的代碼只是想鎖定整個文件而且文件很小。另一種解決方案是將std :: map放在fcntl調用中,以跟蹤fcntl鎖與擁有的FileLock鎖。