У нас очень ограниченные требования к блокировке, но следующий код, кажется, работает для имитации POSIX fcntl, достаточного для наших целей.Обратите внимание на хак, чтобы различить блокировки чтения и записи на основе размера заблокированной области (этот хак может работать для вас на основе вашего примера).В приведенном ниже коде предполагается, что файлы имеют размер менее 4 ГБ.
// 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.