diff --git a/dlls/kernel/tests/file.c b/dlls/kernel/tests/file.c index 02177e90b95..3a0ecc60a81 100644 --- a/dlls/kernel/tests/file.c +++ b/dlls/kernel/tests/file.c @@ -674,6 +674,79 @@ void test_offset_in_overlapped_structure(void) ok(DeleteFileA(temp_fname), "DeleteFileA error %ld\n", GetLastError()); } +static void test_LockFile(void) +{ + HANDLE handle; + DWORD written; + OVERLAPPED overlapped; + + handle = CreateFileA( filename, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, 0, 0 ); + if (handle == INVALID_HANDLE_VALUE) + { + ok(0,"couldn't create file \"%s\" (err=%ld)",filename,GetLastError()); + return; + } + ok( WriteFile( handle, sillytext, strlen(sillytext), &written, NULL ), "write failed" ); + + ok( LockFile( handle, 0, 0, 0, 0 ), "LockFile failed" ); + ok( UnlockFile( handle, 0, 0, 0, 0 ), "UnlockFile failed" ); + ok( !UnlockFile( handle, 0, 0, 0, 0 ), "UnlockFile succeeded" ); + + ok( LockFile( handle, 10, 0, 20, 0 ), "LockFile 10,20 failed" ); + /* overlapping locks must fail */ + ok( !LockFile( handle, 12, 0, 10, 0 ), "LockFile 12,10 succeeded" ); + ok( !LockFile( handle, 5, 0, 6, 0 ), "LockFile 5,6 succeeded" ); + /* non-overlapping locks must succeed */ + ok( LockFile( handle, 5, 0, 5, 0 ), "LockFile 5,5 failed" ); + + ok( !UnlockFile( handle, 10, 0, 10, 0 ), "UnlockFile 10,10 succeeded" ); + ok( UnlockFile( handle, 10, 0, 20, 0 ), "UnlockFile 10,20 failed" ); + ok( !UnlockFile( handle, 10, 0, 20, 0 ), "UnlockFile 10,20 again succeeded" ); + ok( UnlockFile( handle, 5, 0, 5, 0 ), "UnlockFile 5,5 failed" ); + + overlapped.Offset = 100; + overlapped.OffsetHigh = 0; + overlapped.hEvent = 0; + ok( LockFileEx( handle, 0, 0, 100, 0, &overlapped ), "LockFileEx 100,100 failed" ); + /* overlapping shared locks are OK */ + overlapped.Offset = 150; + ok( LockFileEx( handle, 0, 0, 100, 0, &overlapped ), "LockFileEx 150,100 failed" ); + /* but exclusive is not */ + ok( !LockFileEx( handle, LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY, 0, 50, 0, &overlapped ), + "LockFileEx exclusive 150,50 succeeded" ); + ok( UnlockFileEx( handle, 0, 100, 0, &overlapped ), "UnlockFileEx 150,100 failed" ); + ok( !UnlockFileEx( handle, 0, 100, 0, &overlapped ), "UnlockFileEx 150,100 again succeeded" ); + overlapped.Offset = 100; + ok( UnlockFileEx( handle, 0, 100, 0, &overlapped ), "UnlockFileEx 100,100 failed" ); + ok( !UnlockFileEx( handle, 0, 100, 0, &overlapped ), "UnlockFileEx 100,100 again succeeded" ); + + ok( LockFile( handle, 0, 0x10000000, 0, 0xf0000000 ), "LockFile failed" ); + ok( !LockFile( handle, ~0, ~0, 1, 0 ), "LockFile ~0,1 succeeded" ); + ok( !LockFile( handle, 0, 0x20000000, 20, 0 ), "LockFile 0x20000000,20 succeeded" ); + ok( UnlockFile( handle, 0, 0x10000000, 0, 0xf0000000 ), "UnlockFile failed" ); + + /* wrap-around lock should not do anything */ + /* (but still succeeds on NT4 so we don't check result) */ + LockFile( handle, 0, 0x10000000, 0, 0xf0000001 ); + ok( LockFile( handle, ~0, ~0, 1, 0 ), "LockFile ~0,1 failed" ); + ok( UnlockFile( handle, ~0, ~0, 1, 0 ), "Unlockfile ~0,1 failed" ); + + /* zero-byte lock */ + ok( LockFile( handle, 100, 0, 0, 0 ), "LockFile 100,0 failed" ); + ok( !LockFile( handle, 98, 0, 4, 0 ), "LockFile 98,4 succeeded" ); + ok( LockFile( handle, 90, 0, 10, 0 ), "LockFile 90,10 failed" ); + ok( LockFile( handle, 100, 0, 10, 0 ), "LockFile 100,10 failed" ); + ok( UnlockFile( handle, 90, 0, 10, 0 ), "UnlockFile 90,10 failed" ); + ok( UnlockFile( handle, 100, 0, 10, 0 ), "UnlockFile 100,10 failed" ); + ok( UnlockFile( handle, 100, 0, 0, 0 ), "UnlockFile 100,0 failed" ); + + CloseHandle( handle ); + DeleteFileA( filename ); +} + + START_TEST(file) { test__hread( ); @@ -690,5 +763,6 @@ START_TEST(file) test_CreateFileW(); test_DeleteFileA(); test_DeleteFileW(); + test_LockFile(); test_offset_in_overlapped_structure(); } diff --git a/files/file.c b/files/file.c index 78a8bf3c6ee..0313936bbe9 100644 --- a/files/file.c +++ b/files/file.c @@ -2995,20 +2995,22 @@ BOOL WINAPI SetFileTime( HANDLE hFile, /************************************************************************** * LockFile (KERNEL32.@) */ -BOOL WINAPI LockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, - DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh ) +BOOL WINAPI LockFile( HANDLE hFile, DWORD offset_low, DWORD offset_high, + DWORD count_low, DWORD count_high ) { BOOL ret; - FIXME("not implemented in server\n"); + TRACE( "%p %lx%08lx %lx%08lx\n", hFile, offset_high, offset_low, count_high, count_low ); SERVER_START_REQ( lock_file ) { req->handle = hFile; - req->offset_low = dwFileOffsetLow; - req->offset_high = dwFileOffsetHigh; - req->count_low = nNumberOfBytesToLockLow; - req->count_high = nNumberOfBytesToLockHigh; + req->offset_low = offset_low; + req->offset_high = offset_high; + req->count_low = count_low; + req->count_high = count_high; + req->shared = FALSE; + req->wait = FALSE; ret = !wine_server_call_err( req ); } SERVER_END_REQ; @@ -3028,41 +3030,76 @@ BOOL WINAPI LockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHig * Per Microsoft docs, the third parameter (reserved) must be set to 0. */ BOOL WINAPI LockFileEx( HANDLE hFile, DWORD flags, DWORD reserved, - DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh, - LPOVERLAPPED pOverlapped ) + DWORD count_low, DWORD count_high, LPOVERLAPPED overlapped ) { - FIXME("hFile=%p,flags=%ld,reserved=%ld,lowbytes=%ld,highbytes=%ld,overlapped=%p: stub.\n", - hFile, flags, reserved, nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh, - pOverlapped); - if (reserved == 0) - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - else + NTSTATUS err; + BOOL async; + HANDLE handle; + + if (reserved) { - ERR("reserved == %ld: Supposed to be 0??\n", reserved); - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; } - return FALSE; + TRACE( "%p %lx%08lx %lx%08lx flags %lx\n", + hFile, overlapped->OffsetHigh, overlapped->Offset, count_high, count_low, flags ); + + for (;;) + { + SERVER_START_REQ( lock_file ) + { + req->handle = hFile; + req->offset_low = overlapped->Offset; + req->offset_high = overlapped->OffsetHigh; + req->count_low = count_low; + req->count_high = count_high; + req->shared = !(flags & LOCKFILE_EXCLUSIVE_LOCK); + req->wait = !(flags & LOCKFILE_FAIL_IMMEDIATELY); + err = wine_server_call( req ); + handle = reply->handle; + async = reply->overlapped; + } + SERVER_END_REQ; + if (err != STATUS_PENDING) + { + if (err) SetLastError( RtlNtStatusToDosError(err) ); + return !err; + } + if (async) + { + FIXME( "Async I/O lock wait not implemented, might deadlock\n" ); + if (handle) CloseHandle( handle ); + SetLastError( ERROR_IO_PENDING ); + return FALSE; + } + if (handle) + { + WaitForSingleObject( handle, INFINITE ); + CloseHandle( handle ); + } + else Sleep(100); /* Unix lock conflict, sleep a bit and retry */ + } } /************************************************************************** * UnlockFile (KERNEL32.@) */ -BOOL WINAPI UnlockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, - DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh ) +BOOL WINAPI UnlockFile( HANDLE hFile, DWORD offset_low, DWORD offset_high, + DWORD count_low, DWORD count_high ) { BOOL ret; - FIXME("not implemented in server\n"); + TRACE( "%p %lx%08lx %lx%08lx\n", hFile, offset_high, offset_low, count_high, count_low ); SERVER_START_REQ( unlock_file ) { req->handle = hFile; - req->offset_low = dwFileOffsetLow; - req->offset_high = dwFileOffsetHigh; - req->count_low = nNumberOfBytesToUnlockLow; - req->count_high = nNumberOfBytesToUnlockHigh; + req->offset_low = offset_low; + req->offset_high = offset_high; + req->count_low = count_low; + req->count_high = count_high; ret = !wine_server_call_err( req ); } SERVER_END_REQ; @@ -3073,221 +3110,17 @@ BOOL WINAPI UnlockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetH /************************************************************************** * UnlockFileEx (KERNEL32.@) */ -BOOL WINAPI UnlockFileEx( - HANDLE hFile, - DWORD dwReserved, - DWORD nNumberOfBytesToUnlockLow, - DWORD nNumberOfBytesToUnlockHigh, - LPOVERLAPPED lpOverlapped -) +BOOL WINAPI UnlockFileEx( HANDLE hFile, DWORD reserved, DWORD count_low, DWORD count_high, + LPOVERLAPPED overlapped ) { - FIXME("hFile=%p,reserved=%ld,lowbytes=%ld,highbytes=%ld,overlapped=%p: stub.\n", - hFile, dwReserved, nNumberOfBytesToUnlockLow, nNumberOfBytesToUnlockHigh, - lpOverlapped); - if (dwReserved == 0) - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - else - { - ERR("reserved == %ld: Supposed to be 0??\n", dwReserved); - SetLastError(ERROR_INVALID_PARAMETER); - } - - return FALSE; -} - - -#if 0 - -struct DOS_FILE_LOCK { - struct DOS_FILE_LOCK * next; - DWORD base; - DWORD len; - DWORD processId; - FILE_OBJECT * dos_file; -/* char * unix_name;*/ -}; - -typedef struct DOS_FILE_LOCK DOS_FILE_LOCK; - -static DOS_FILE_LOCK *locks = NULL; -static void DOS_RemoveFileLocks(FILE_OBJECT *file); - - -/* Locks need to be mirrored because unix file locking is based - * on the pid. Inside of wine there can be multiple WINE processes - * that share the same unix pid. - * Read's and writes should check these locks also - not sure - * how critical that is at this point (FIXME). - */ - -static BOOL DOS_AddLock(FILE_OBJECT *file, struct flock *f) -{ - DOS_FILE_LOCK *curr; - DWORD processId; - - processId = GetCurrentProcessId(); - - /* check if lock overlaps a current lock for the same file */ -#if 0 - for (curr = locks; curr; curr = curr->next) { - if (strcmp(curr->unix_name, file->unix_name) == 0) { - if ((f->l_start == curr->base) && (f->l_len == curr->len)) - return TRUE;/* region is identic */ - if ((f->l_start < (curr->base + curr->len)) && - ((f->l_start + f->l_len) > curr->base)) { - /* region overlaps */ - return FALSE; - } + if (reserved) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; } - } -#endif - - curr = HeapAlloc( GetProcessHeap(), 0, sizeof(DOS_FILE_LOCK) ); - curr->processId = GetCurrentProcessId(); - curr->base = f->l_start; - curr->len = f->l_len; -/* curr->unix_name = HEAP_strdupA( GetProcessHeap(), 0, file->unix_name);*/ - curr->next = locks; - curr->dos_file = file; - locks = curr; - return TRUE; + return UnlockFile( hFile, overlapped->Offset, overlapped->OffsetHigh, count_low, count_high ); } -static void DOS_RemoveFileLocks(FILE_OBJECT *file) -{ - DWORD processId; - DOS_FILE_LOCK **curr; - DOS_FILE_LOCK *rem; - - processId = GetCurrentProcessId(); - curr = &locks; - while (*curr) { - if ((*curr)->dos_file == file) { - rem = *curr; - *curr = (*curr)->next; -/* HeapFree( GetProcessHeap(), 0, rem->unix_name );*/ - HeapFree( GetProcessHeap(), 0, rem ); - } - else - curr = &(*curr)->next; - } -} - -static BOOL DOS_RemoveLock(FILE_OBJECT *file, struct flock *f) -{ - DWORD processId; - DOS_FILE_LOCK **curr; - DOS_FILE_LOCK *rem; - - processId = GetCurrentProcessId(); - for (curr = &locks; *curr; curr = &(*curr)->next) { - if ((*curr)->processId == processId && - (*curr)->dos_file == file && - (*curr)->base == f->l_start && - (*curr)->len == f->l_len) { - /* this is the same lock */ - rem = *curr; - *curr = (*curr)->next; -/* HeapFree( GetProcessHeap(), 0, rem->unix_name );*/ - HeapFree( GetProcessHeap(), 0, rem ); - return TRUE; - } - } - /* no matching lock found */ - return FALSE; -} - - -/************************************************************************** - * LockFile (KERNEL32.@) - */ -BOOL WINAPI LockFile( - HANDLE hFile,DWORD dwFileOffsetLow,DWORD dwFileOffsetHigh, - DWORD nNumberOfBytesToLockLow,DWORD nNumberOfBytesToLockHigh ) -{ - struct flock f; - FILE_OBJECT *file; - - TRACE("handle %d offsetlow=%ld offsethigh=%ld nbyteslow=%ld nbyteshigh=%ld\n", - hFile, dwFileOffsetLow, dwFileOffsetHigh, - nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh); - - if (dwFileOffsetHigh || nNumberOfBytesToLockHigh) { - FIXME("Unimplemented bytes > 32bits\n"); - return FALSE; - } - - f.l_start = dwFileOffsetLow; - f.l_len = nNumberOfBytesToLockLow; - f.l_whence = SEEK_SET; - f.l_pid = 0; - f.l_type = F_WRLCK; - - if (!(file = FILE_GetFile(hFile,0,NULL))) return FALSE; - - /* shadow locks internally */ - if (!DOS_AddLock(file, &f)) { - SetLastError( ERROR_LOCK_VIOLATION ); - return FALSE; - } - - /* FIXME: Unix locking commented out for now, doesn't work with Excel */ -#ifdef USE_UNIX_LOCKS - if (fcntl(file->unix_handle, F_SETLK, &f) == -1) { - if (errno == EACCES || errno == EAGAIN) { - SetLastError( ERROR_LOCK_VIOLATION ); - } - else { - FILE_SetDosError(); - } - /* remove our internal copy of the lock */ - DOS_RemoveLock(file, &f); - return FALSE; - } -#endif - return TRUE; -} - - -/************************************************************************** - * UnlockFile (KERNEL32.@) - */ -BOOL WINAPI UnlockFile( - HANDLE hFile,DWORD dwFileOffsetLow,DWORD dwFileOffsetHigh, - DWORD nNumberOfBytesToUnlockLow,DWORD nNumberOfBytesToUnlockHigh ) -{ - FILE_OBJECT *file; - struct flock f; - - TRACE("handle %d offsetlow=%ld offsethigh=%ld nbyteslow=%ld nbyteshigh=%ld\n", - hFile, dwFileOffsetLow, dwFileOffsetHigh, - nNumberOfBytesToUnlockLow, nNumberOfBytesToUnlockHigh); - - if (dwFileOffsetHigh || nNumberOfBytesToUnlockHigh) { - WARN("Unimplemented bytes > 32bits\n"); - return FALSE; - } - - f.l_start = dwFileOffsetLow; - f.l_len = nNumberOfBytesToUnlockLow; - f.l_whence = SEEK_SET; - f.l_pid = 0; - f.l_type = F_UNLCK; - - if (!(file = FILE_GetFile(hFile,0,NULL))) return FALSE; - - DOS_RemoveLock(file, &f); /* ok if fails - may be another wine */ - - /* FIXME: Unix locking commented out for now, doesn't work with Excel */ -#ifdef USE_UNIX_LOCKS - if (fcntl(file->unix_handle, F_SETLK, &f) == -1) { - FILE_SetDosError(); - return FALSE; - } -#endif - return TRUE; -} -#endif /************************************************************************** * GetFileAttributesExW (KERNEL32.@) diff --git a/include/winbase.h b/include/winbase.h index fce2e8ad31f..89a1e06169e 100644 --- a/include/winbase.h +++ b/include/winbase.h @@ -293,6 +293,8 @@ typedef struct _PROCESS_HEAP_ENTRY #define INVALID_SET_FILE_POINTER ((DWORD)-1) #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#define LOCKFILE_FAIL_IMMEDIATELY 1 +#define LOCKFILE_EXCLUSIVE_LOCK 2 #define TLS_OUT_OF_INDEXES ((DWORD)0xFFFFFFFF) diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 5555f062f0b..931ae58737b 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -899,10 +899,14 @@ struct lock_file_request unsigned int offset_high; unsigned int count_low; unsigned int count_high; + int shared; + int wait; }; struct lock_file_reply { struct reply_header __header; + obj_handle_t handle; + int overlapped; }; @@ -3551,6 +3555,6 @@ union generic_reply struct get_next_hook_reply get_next_hook_reply; }; -#define SERVER_PROTOCOL_VERSION 99 +#define SERVER_PROTOCOL_VERSION 100 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/fd.c b/server/fd.c index dc6f1601763..af963465226 100644 --- a/server/fd.c +++ b/server/fd.c @@ -1,7 +1,7 @@ /* * Server-side file descriptor management * - * Copyright (C) 2003 Alexandre Julliard + * Copyright (C) 2000, 2003 Alexandre Julliard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,7 +22,9 @@ #include "config.h" #include +#include #include +#include #include #include #include @@ -42,6 +44,11 @@ #include "request.h" #include "console.h" +/* Because of the stupid Posix locking semantics, we need to keep + * track of all file descriptors referencing a given file, and not + * close a single one until all the locks are gone (sigh). + */ + /* file descriptor object */ /* closed_fd is used to keep track of the unix fd belonging to a closed fd object */ @@ -59,6 +66,7 @@ struct fd struct list inode_entry; /* entry in inode fd list */ struct closed_fd *closed; /* structure to store the unix fd at destroy time */ struct object *user; /* object using this file descriptor */ + struct list locks; /* list of locks on this fd */ int unix_fd; /* unix file descriptor */ int poll_index; /* index of fd in poll array */ }; @@ -88,6 +96,7 @@ struct inode dev_t dev; /* device number */ ino_t ino; /* inode number */ struct list open; /* list of open file descriptors */ + struct list locks; /* list of file locks */ struct closed_fd *closed; /* list of file descriptors to close at destroy time */ }; @@ -106,6 +115,42 @@ static const struct object_ops inode_ops = inode_destroy /* destroy */ }; +/* file lock object */ + +struct file_lock +{ + struct object obj; /* object header */ + struct fd *fd; /* fd owning this lock */ + struct list fd_entry; /* entry in list of locks on a given fd */ + struct list inode_entry; /* entry in inode list of locks */ + int shared; /* shared lock? */ + file_pos_t start; /* locked region is interval [start;end) */ + file_pos_t end; + struct process *process; /* process owning this lock */ + struct list proc_entry; /* entry in list of locks owned by the process */ +}; + +static void file_lock_dump( struct object *obj, int verbose ); +static int file_lock_signaled( struct object *obj, struct thread *thread ); + +static const struct object_ops file_lock_ops = +{ + sizeof(struct file_lock), /* size */ + file_lock_dump, /* dump */ + add_queue, /* add_queue */ + remove_queue, /* remove_queue */ + file_lock_signaled, /* signaled */ + no_satisfied, /* satisfied */ + no_get_fd, /* get_fd */ + no_destroy /* destroy */ +}; + + +#define OFF_T_MAX (~((file_pos_t)1 << (8*sizeof(off_t)-1))) +#define FILE_POS_T_MAX (~(file_pos_t)0) + +static file_pos_t max_unix_offset = OFF_T_MAX; + #define DUMP_LONG_LONG(val) do { \ if (sizeof(val) > sizeof(unsigned long) && (val) > ~0UL) \ fprintf( stderr, "%lx%08lx", (unsigned long)((val) >> 32), (unsigned long)(val) ); \ @@ -368,6 +413,18 @@ void main_loop(void) static struct list inode_hash[HASH_SIZE]; +/* close all pending file descriptors in the closed list */ +static void inode_close_pending( struct inode *inode ) +{ + while (inode->closed) + { + struct closed_fd *fd = inode->closed; + inode->closed = fd->next; + close( fd->fd ); + free( fd ); + } +} + static void inode_dump( struct object *obj, int verbose ) { @@ -383,16 +440,11 @@ static void inode_destroy( struct object *obj ) { struct inode *inode = (struct inode *)obj; - assert( !list_head(&inode->open) ); + assert( list_empty(&inode->open) ); + assert( list_empty(&inode->locks) ); list_remove( &inode->entry ); - while (inode->closed) - { - struct closed_fd *fd = inode->closed; - inode->closed = fd->next; - close( fd->fd ); - free( fd ); - } + inode_close_pending( inode ); } /* retrieve the inode object for a given fd, creating it if needed */ @@ -421,6 +473,7 @@ static struct inode *get_inode( dev_t dev, ino_t ino ) inode->ino = ino; inode->closed = NULL; list_init( &inode->open ); + list_init( &inode->locks ); list_add_head( &inode_hash[hash], &inode->entry ); } return inode; @@ -429,8 +482,315 @@ static struct inode *get_inode( dev_t dev, ino_t ino ) /* add fd to the indoe list of file descriptors to close */ static void inode_add_closed_fd( struct inode *inode, struct closed_fd *fd ) { - fd->next = inode->closed; - inode->closed = fd; + if (!list_empty( &inode->locks )) + { + fd->next = inode->closed; + inode->closed = fd; + } + else /* no locks on this inode, we can close the fd right away */ + { + close( fd->fd ); + free( fd ); + } +} + + +/****************************************************************/ +/* file lock functions */ + +static void file_lock_dump( struct object *obj, int verbose ) +{ + struct file_lock *lock = (struct file_lock *)obj; + fprintf( stderr, "Lock %s fd=%p proc=%p start=", + lock->shared ? "shared" : "excl", lock->fd, lock->process ); + DUMP_LONG_LONG( lock->start ); + fprintf( stderr, " end=" ); + DUMP_LONG_LONG( lock->end ); + fprintf( stderr, "\n" ); +} + +static int file_lock_signaled( struct object *obj, struct thread *thread ) +{ + struct file_lock *lock = (struct file_lock *)obj; + /* lock is signaled if it has lost its owner */ + return !lock->process; +} + +/* set (or remove) a Unix lock if possible for the given range */ +static int set_unix_lock( const struct fd *fd, file_pos_t start, file_pos_t end, int type ) +{ + struct flock fl; + + for (;;) + { + if (start == end) return 1; /* can't set zero-byte lock */ + if (start > max_unix_offset) return 1; /* ignore it */ + fl.l_type = type; + fl.l_whence = SEEK_SET; + fl.l_start = start; + if (!end || end > max_unix_offset) fl.l_len = 0; + else fl.l_len = end - start; + if (fcntl( fd->unix_fd, F_SETLK, &fl ) != -1) return 1; + + switch(errno) + { + case EACCES: + case EAGAIN: + set_error( STATUS_FILE_LOCK_CONFLICT ); + return 0; + case EOVERFLOW: + /* this can happen if off_t is 64-bit but the kernel only supports 32-bit */ + /* in that case we shrink the limit and retry */ + if (max_unix_offset > INT_MAX) + { + max_unix_offset = INT_MAX; + break; /* retry */ + } + /* fall through */ + default: + file_set_error(); + return 0; + } + } +} + +/* check if interval [start;end) overlaps the lock */ +inline static int lock_overlaps( struct file_lock *lock, file_pos_t start, file_pos_t end ) +{ + if (lock->end && start >= lock->end) return 0; + if (end && lock->start >= end) return 0; + return 1; +} + +/* remove Unix locks for all bytes in the specified area that are no longer locked */ +static void remove_unix_locks( const struct fd *fd, file_pos_t start, file_pos_t end ) +{ + struct hole + { + struct hole *next; + struct hole *prev; + file_pos_t start; + file_pos_t end; + } *first, *cur, *next, *buffer; + + struct list *ptr; + int count = 0; + + if (!fd->inode) return; + if (start == end || start > max_unix_offset) return; + if (!end || end > max_unix_offset) end = max_unix_offset + 1; + + /* count the number of locks overlapping the specified area */ + + LIST_FOR_EACH( ptr, &fd->inode->locks ) + { + struct file_lock *lock = LIST_ENTRY( ptr, struct file_lock, inode_entry ); + if (lock->start == lock->end) continue; + if (lock_overlaps( lock, start, end )) count++; + } + + if (!count) /* no locks at all, we can unlock everything */ + { + set_unix_lock( fd, start, end, F_UNLCK ); + return; + } + + /* allocate space for the list of holes */ + /* max. number of holes is number of locks + 1 */ + + if (!(buffer = malloc( sizeof(*buffer) * (count+1) ))) return; + first = buffer; + first->next = NULL; + first->prev = NULL; + first->start = start; + first->end = end; + next = first + 1; + + /* build a sorted list of unlocked holes in the specified area */ + + LIST_FOR_EACH( ptr, &fd->inode->locks ) + { + struct file_lock *lock = LIST_ENTRY( ptr, struct file_lock, inode_entry ); + if (lock->start == lock->end) continue; + if (!lock_overlaps( lock, start, end )) continue; + + /* go through all the holes touched by this lock */ + for (cur = first; cur; cur = cur->next) + { + if (cur->end <= lock->start) continue; /* hole is before start of lock */ + if (lock->end && cur->start >= lock->end) break; /* hole is after end of lock */ + + /* now we know that lock is overlapping hole */ + + if (cur->start >= lock->start) /* lock starts before hole, shrink from start */ + { + cur->start = lock->end; + if (cur->start && cur->start < cur->end) break; /* done with this lock */ + /* now hole is empty, remove it */ + if (cur->next) cur->next->prev = cur->prev; + if (cur->prev) cur->prev->next = cur->next; + else if (!(first = cur->next)) goto done; /* no more holes at all */ + } + else if (!lock->end || cur->end <= lock->end) /* lock larger than hole, shrink from end */ + { + cur->end = lock->start; + assert( cur->start < cur->end ); + } + else /* lock is in the middle of hole, split hole in two */ + { + next->prev = cur; + next->next = cur->next; + cur->next = next; + next->start = lock->end; + next->end = cur->end; + cur->end = lock->start; + assert( next->start < next->end ); + assert( cur->end < next->start ); + next++; + break; /* done with this lock */ + } + } + } + + /* clear Unix locks for all the holes */ + + for (cur = first; cur; cur = cur->next) + set_unix_lock( fd, cur->start, cur->end, F_UNLCK ); + + done: + free( buffer ); +} + +/* create a new lock on a fd */ +static struct file_lock *add_lock( struct fd *fd, int shared, file_pos_t start, file_pos_t end ) +{ + struct file_lock *lock; + + if (!fd->inode) /* not a regular file */ + { + set_error( STATUS_INVALID_HANDLE ); + return NULL; + } + + if (!(lock = alloc_object( &file_lock_ops ))) return NULL; + lock->shared = shared; + lock->start = start; + lock->end = end; + lock->fd = fd; + lock->process = current->process; + + /* now try to set a Unix lock */ + if (!set_unix_lock( lock->fd, lock->start, lock->end, lock->shared ? F_RDLCK : F_WRLCK )) + { + release_object( lock ); + return NULL; + } + list_add_head( &fd->locks, &lock->fd_entry ); + list_add_head( &fd->inode->locks, &lock->inode_entry ); + list_add_head( &lock->process->locks, &lock->proc_entry ); + return lock; +} + +/* remove an existing lock */ +static void remove_lock( struct file_lock *lock, int remove_unix ) +{ + struct inode *inode = lock->fd->inode; + + list_remove( &lock->fd_entry ); + list_remove( &lock->inode_entry ); + list_remove( &lock->proc_entry ); + if (remove_unix) remove_unix_locks( lock->fd, lock->start, lock->end ); + if (list_empty( &inode->locks )) inode_close_pending( inode ); + lock->process = NULL; + wake_up( &lock->obj, 0 ); + release_object( lock ); +} + +/* remove all locks owned by a given process */ +void remove_process_locks( struct process *process ) +{ + struct list *ptr; + + while ((ptr = list_head( &process->locks ))) + { + struct file_lock *lock = LIST_ENTRY( ptr, struct file_lock, proc_entry ); + remove_lock( lock, 1 ); /* this removes it from the list */ + } +} + +/* remove all locks on a given fd */ +static void remove_fd_locks( struct fd *fd ) +{ + file_pos_t start = FILE_POS_T_MAX, end = 0; + struct list *ptr; + + while ((ptr = list_head( &fd->locks ))) + { + struct file_lock *lock = LIST_ENTRY( ptr, struct file_lock, fd_entry ); + if (lock->start < start) start = lock->start; + if (!lock->end || lock->end > end) end = lock->end - 1; + remove_lock( lock, 0 ); + } + if (start < end) remove_unix_locks( fd, start, end + 1 ); +} + +/* add a lock on an fd */ +/* returns handle to wait on */ +obj_handle_t lock_fd( struct fd *fd, file_pos_t start, file_pos_t count, int shared, int wait ) +{ + struct list *ptr; + file_pos_t end = start + count; + + /* don't allow wrapping locks */ + if (end && end < start) + { + set_error( STATUS_INVALID_PARAMETER ); + return 0; + } + + /* check if another lock on that file overlaps the area */ + LIST_FOR_EACH( ptr, &fd->inode->locks ) + { + struct file_lock *lock = LIST_ENTRY( ptr, struct file_lock, inode_entry ); + if (!lock_overlaps( lock, start, end )) continue; + if (lock->shared && shared) continue; + /* found one */ + if (!wait) + { + set_error( STATUS_FILE_LOCK_CONFLICT ); + return 0; + } + set_error( STATUS_PENDING ); + return alloc_handle( current->process, lock, SYNCHRONIZE, 0 ); + } + + /* not found, add it */ + if (add_lock( fd, shared, start, end )) return 0; + if (get_error() == STATUS_FILE_LOCK_CONFLICT) + { + /* Unix lock conflict -> tell client to wait and retry */ + if (wait) set_error( STATUS_PENDING ); + } + return 0; +} + +/* remove a lock on an fd */ +void unlock_fd( struct fd *fd, file_pos_t start, file_pos_t count ) +{ + struct list *ptr; + file_pos_t end = start + count; + + /* find an existing lock with the exact same parameters */ + LIST_FOR_EACH( ptr, &fd->locks ) + { + struct file_lock *lock = LIST_ENTRY( ptr, struct file_lock, fd_entry ); + if ((lock->start == start) && (lock->end == end)) + { + remove_lock( lock, 1 ); + return; + } + } + set_error( STATUS_FILE_LOCK_CONFLICT ); } @@ -447,6 +807,7 @@ static void fd_destroy( struct object *obj ) { struct fd *fd = (struct fd *)obj; + remove_fd_locks( fd ); list_remove( &fd->inode_entry ); if (fd->poll_index != -1) remove_poll_user( fd, fd->poll_index ); if (fd->inode) @@ -492,6 +853,7 @@ struct fd *alloc_fd( const struct fd_ops *fd_user_ops, struct object *user ) fd->unix_fd = -1; fd->poll_index = -1; list_init( &fd->inode_entry ); + list_init( &fd->locks ); if ((fd->poll_index = add_poll_user( fd )) == -1) { diff --git a/server/file.c b/server/file.c index ba853e9fe2d..c14c4ee23b2 100644 --- a/server/file.c +++ b/server/file.c @@ -453,6 +453,7 @@ void file_set_error(void) case ESPIPE: set_error( 0xc0010000 | ERROR_SEEK /* FIXME */ ); break; case ENOTEMPTY: set_error( STATUS_DIRECTORY_NOT_EMPTY ); break; case EIO: set_error( STATUS_ACCESS_VIOLATION ); break; + case EOVERFLOW: set_error( STATUS_INVALID_PARAMETER ); break; default: perror("file_set_error"); set_error( ERROR_UNKNOWN /* FIXME */ ); break; } } @@ -581,20 +582,6 @@ static int set_file_time( obj_handle_t handle, time_t access_time, time_t write_ return 0; } -static int file_lock( struct file *file, int offset_high, int offset_low, - int count_high, int count_low ) -{ - /* FIXME: implement this */ - return 1; -} - -static int file_unlock( struct file *file, int offset_high, int offset_low, - int count_high, int count_low ) -{ - /* FIXME: implement this */ - return 1; -} - /* create a file */ DECL_HANDLER(create_file) { @@ -661,11 +648,13 @@ DECL_HANDLER(set_file_time) DECL_HANDLER(lock_file) { struct file *file; + file_pos_t offset = ((file_pos_t)req->offset_high << 32) | req->offset_low; + file_pos_t count = ((file_pos_t)req->count_high << 32) | req->count_low; if ((file = get_file_obj( current->process, req->handle, 0 ))) { - file_lock( file, req->offset_high, req->offset_low, - req->count_high, req->count_low ); + reply->handle = lock_fd( file->fd, offset, count, req->shared, req->wait ); + reply->overlapped = (file->flags & FILE_FLAG_OVERLAPPED) != 0; release_object( file ); } } @@ -674,11 +663,12 @@ DECL_HANDLER(lock_file) DECL_HANDLER(unlock_file) { struct file *file; + file_pos_t offset = ((file_pos_t)req->offset_high << 32) | req->offset_low; + file_pos_t count = ((file_pos_t)req->count_high << 32) | req->count_low; if ((file = get_file_obj( current->process, req->handle, 0 ))) { - file_unlock( file, req->offset_high, req->offset_low, - req->count_high, req->count_low ); + unlock_fd( file->fd, offset, count ); release_object( file ); } } diff --git a/server/file.h b/server/file.h index ff59f37de73..88e2be99af8 100644 --- a/server/file.h +++ b/server/file.h @@ -25,6 +25,8 @@ struct fd; +typedef unsigned __int64 file_pos_t; + /* operations valid on file descriptor objects */ struct fd_ops { @@ -51,6 +53,8 @@ extern int get_unix_fd( struct fd *fd ); extern void fd_poll_event( struct fd *fd, int event ); extern int check_fd_events( struct fd *fd, int events ); extern void set_fd_events( struct fd *fd, int events ); +extern obj_handle_t lock_fd( struct fd *fd, file_pos_t offset, file_pos_t count, int shared, int wait ); +extern void unlock_fd( struct fd *fd, file_pos_t offset, file_pos_t count ); extern int default_fd_add_queue( struct object *obj, struct wait_queue_entry *entry ); extern void default_fd_remove_queue( struct object *obj, struct wait_queue_entry *entry ); diff --git a/server/list.h b/server/list.h index fcf687e3e66..993aaaf2a11 100644 --- a/server/list.h +++ b/server/list.h @@ -80,6 +80,12 @@ inline static struct list *list_tail( struct list *list ) return list_prev( list, list ); } +/* check if a list is empty */ +inline static int list_empty( struct list *list ) +{ + return list->next == list; +} + /* initialize a list */ inline static void list_init( struct list *list ) { diff --git a/server/process.c b/server/process.c index f26b62a47aa..0617bb50b31 100644 --- a/server/process.c +++ b/server/process.c @@ -296,6 +296,7 @@ struct thread *create_process( int fd ) process->exe.namelen = 0; process->exe.filename = NULL; process->group_id = 0; + list_init( &process->locks ); gettimeofday( &process->start_time, NULL ); if ((process->next = first_process) != NULL) process->next->prev = process; diff --git a/server/process.h b/server/process.h index cd65153e0ae..b79982a7153 100644 --- a/server/process.h +++ b/server/process.h @@ -67,6 +67,7 @@ struct process int affinity; /* process affinity mask */ int suspend; /* global process suspend count */ int create_flags; /* process creation flags */ + struct list locks; /* list of file locks owned by the process */ struct console_input*console; /* console input */ enum startup_state startup_state; /* startup state */ struct startup_info *startup_info; /* startup info while init is in progress */ diff --git a/server/protocol.def b/server/protocol.def index 805ad8b6fdd..f1a4edb0b0c 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -681,6 +681,11 @@ enum fd_type unsigned int offset_high; /* offset of start of lock */ unsigned int count_low; /* count of bytes to lock */ unsigned int count_high; /* count of bytes to lock */ + int shared; /* shared or exclusive lock? */ + int wait; /* do we want to wait? */ +@REPLY + obj_handle_t handle; /* handle to wait on */ + int overlapped; /* is it an overlapped I/O handle? */ @END diff --git a/server/trace.c b/server/trace.c index 1e09f62f102..b9c6f1c5c6c 100644 --- a/server/trace.c +++ b/server/trace.c @@ -889,7 +889,15 @@ static void dump_lock_file_request( const struct lock_file_request *req ) fprintf( stderr, " offset_low=%08x,", req->offset_low ); fprintf( stderr, " offset_high=%08x,", req->offset_high ); fprintf( stderr, " count_low=%08x,", req->count_low ); - fprintf( stderr, " count_high=%08x", req->count_high ); + fprintf( stderr, " count_high=%08x,", req->count_high ); + fprintf( stderr, " shared=%d,", req->shared ); + fprintf( stderr, " wait=%d", req->wait ); +} + +static void dump_lock_file_reply( const struct lock_file_reply *req ) +{ + fprintf( stderr, " handle=%p,", req->handle ); + fprintf( stderr, " overlapped=%d", req->overlapped ); } static void dump_unlock_file_request( const struct unlock_file_request *req ) @@ -2632,7 +2640,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { (dump_func)0, (dump_func)0, (dump_func)dump_get_file_info_reply, - (dump_func)0, + (dump_func)dump_lock_file_reply, (dump_func)0, (dump_func)dump_create_pipe_reply, (dump_func)dump_create_socket_reply,