我们有非常有限的锁定需求,但下面的代码似乎足以模拟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锁定的主要问题是,正如您所注意到的,有一点需要注意(来自文档):
如果使用独占锁和共享锁锁定同一范围,则需要两个解锁操作来解锁该区域;第一个解锁操作解锁独占锁,第二个解锁操作解锁共享锁。
这意味着,如果不首先完全释放同一区域上的锁,就不能从一个共享锁变成同一区域的唯一锁。你使用标志的方法很接近了,我想如果你添加了另一个标志,上面写着我有两个锁,但你只想拥有唯一的锁,那么你的解决方案就可以工作了。我们没有足够的时间编写自己的界面(我们被fcntl困住了)。但是,幸运的是,使用fcntl的代码只想锁定整个文件,而且文件很小。另一种解决方案是在fcntl调用中放置std::map来跟踪fcntl锁和所拥有的FileLock锁。