diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec
index a36457a8e4491b55bd849ad786fa7690bbcfdfeb..2914d24674f69d1dac437a4dc119c57818bbad63 100644
--- a/dlls/ntdll/ntdll.spec
+++ b/dlls/ntdll/ntdll.spec
@@ -1041,6 +1041,7 @@ debug_channels (atom cdrom console debug delayhlp dll dosfs dosmem file fixup
 
 # Server interface
 @ cdecl -norelay wine_server_call(ptr) wine_server_call
+@ cdecl wine_server_handle_to_fd(long long ptr ptr ptr) wine_server_handle_to_fd
 
 # Codepages
 @ cdecl __wine_init_codepages(ptr ptr) __wine_init_codepages
diff --git a/files/file.c b/files/file.c
index 0bc85acd25bbfa873b3aead358dc9a31e8bd1dfe..87953dc246f24c567ba0fbd1172092919fd768f6 100644
--- a/files/file.c
+++ b/files/file.c
@@ -227,36 +227,12 @@ HANDLE FILE_DupUnixHandle( int fd, DWORD access, BOOL inherit )
  * Retrieve the Unix handle corresponding to a file handle.
  * Returns -1 on failure.
  */
-static int FILE_GetUnixHandleType( HANDLE handle, DWORD access, enum fd_type *type, DWORD *flags )
+static int FILE_GetUnixHandleType( HANDLE handle, DWORD access, enum fd_type *type, int *flags )
 {
     int ret, fd = -1;
 
-    do
-    {
-        SERVER_START_REQ( get_handle_fd )
-        {
-            req->handle = handle;
-            req->access = access;
-            if (!(ret = wine_server_call_err( req )))
-            {
-                fd = reply->fd;
-            }
-            if (type) *type = reply->type;
-            if (flags) *flags = reply->flags;
-        }
-        SERVER_END_REQ;
-        if (ret) return -1;
-
-        if (fd == -1)  /* it wasn't in the cache, get it from the server */
-            fd = wine_server_recv_fd( handle );
-
-    } while (fd == -2);  /* -2 means race condition, so restart from scratch */
-
-    if (fd != -1)
-    {
-        if ((fd = dup(fd)) == -1)
-            SetLastError( ERROR_TOO_MANY_OPEN_FILES );
-    }
+    ret = wine_server_handle_to_fd( handle, access, &fd, type, flags );
+    if (ret) SetLastError( RtlNtStatusToDosError(ret) );
     return fd;
 }
 
@@ -1467,9 +1443,8 @@ static BOOL FILE_TimeoutRead(HANDLE hFile, LPVOID buffer, DWORD bytesToRead, LPD
 BOOL WINAPI ReadFile( HANDLE hFile, LPVOID buffer, DWORD bytesToRead,
                         LPDWORD bytesRead, LPOVERLAPPED overlapped )
 {
-    int unix_handle, result;
+    int unix_handle, result, flags;
     enum fd_type type;
-    DWORD flags;
 
     TRACE("%d %p %ld %p %p\n", hFile, buffer, bytesToRead, 
           bytesRead, overlapped );
@@ -1693,9 +1668,8 @@ BOOL WINAPI WriteFileEx(HANDLE hFile, LPCVOID buffer, DWORD bytesToWrite,
 BOOL WINAPI WriteFile( HANDLE hFile, LPCVOID buffer, DWORD bytesToWrite,
                          LPDWORD bytesWritten, LPOVERLAPPED overlapped )
 {
-    int unix_handle, result;
+    int unix_handle, result, flags;
     enum fd_type type;
-    DWORD flags;
 
     TRACE("%d %p %ld %p %p\n", hFile, buffer, bytesToWrite, 
           bytesWritten, overlapped );
diff --git a/include/wine/server.h b/include/wine/server.h
index 1280a62a5091b56cb1db8aa15342ffb74f9dcb9e..c8fc370d2bc8399a47fc808b5d1e69788ffc4e1c 100644
--- a/include/wine/server.h
+++ b/include/wine/server.h
@@ -51,7 +51,8 @@ struct __server_request_info
 
 extern unsigned int wine_server_call( void *req_ptr );
 extern void wine_server_send_fd( int fd );
-extern int wine_server_recv_fd( handle_t handle );
+extern int wine_server_handle_to_fd( handle_t handle, unsigned int access, int *unix_fd,
+                                     enum fd_type *type, int *flags );
 
 /* do a server call and set the last error code */
 inline static unsigned int wine_server_call_err( void *req_ptr )
diff --git a/scheduler/client.c b/scheduler/client.c
index 0da699a23e57ec6522316bbd76bb89963e131af1..c8523eeb408ca1bf2ea89a99ec9f534ec6112bca 100644
--- a/scheduler/client.c
+++ b/scheduler/client.c
@@ -330,23 +330,17 @@ static int receive_fd( handle_t *handle )
 
 
 /***********************************************************************
- *           wine_server_recv_fd
+ *           store_cached_fd
  *
- * Receive a file descriptor passed from the server.
- * The file descriptor must not be closed.
- * Return -2 if a race condition stole our file descriptor.
+ * Store the cached fd value for a given handle back into the server.
+ * Returns the new fd, which can be different if there was already an
+ * fd in the cache for that handle.
  */
-int wine_server_recv_fd( handle_t handle )
+inline static int store_cached_fd( int fd, handle_t handle )
 {
-    handle_t fd_handle;
-
-    int fd = receive_fd( &fd_handle );
-
-    /* now store it in the server fd cache for this handle */
-
     SERVER_START_REQ( set_handle_info )
     {
-        req->handle = fd_handle;
+        req->handle = handle;
         req->flags  = 0;
         req->mask   = 0;
         req->fd     = fd;
@@ -366,12 +360,55 @@ int wine_server_recv_fd( handle_t handle )
         }
     }
     SERVER_END_REQ;
-
-    if (handle != fd_handle) fd = -2;  /* not the one we expected */
     return fd;
 }
 
 
+/***********************************************************************
+ *           wine_server_handle_to_fd   (NTDLL.@)
+ *
+ * Retrieve the Unix fd corresponding to a file handle.
+ */
+int wine_server_handle_to_fd( handle_t handle, unsigned int access, int *unix_fd,
+                              enum fd_type *type, int *flags )
+{
+    handle_t fd_handle;
+    int ret, fd = -1;
+
+    *unix_fd = -1;
+    for (;;)
+    {
+        SERVER_START_REQ( get_handle_fd )
+        {
+            req->handle = handle;
+            req->access = access;
+            if (!(ret = wine_server_call( req ))) fd = reply->fd;
+            if (type) *type = reply->type;
+            if (flags) *flags = reply->flags;
+        }
+        SERVER_END_REQ;
+        if (ret) return ret;
+
+        if (fd != -1) break;
+
+        /* it wasn't in the cache, get it from the server */
+        fd = receive_fd( &fd_handle );
+        /* and store it back into the cache */
+        fd = store_cached_fd( fd, fd_handle );
+
+        if (fd_handle == handle) break;
+        /* if we received a different handle this means there was
+         * a race with another thread; we restart everything from
+         * scratch in this case.
+         */
+    }
+
+    if ((fd != -1) && ((fd = dup(fd)) == -1)) return STATUS_TOO_MANY_OPENED_FILES;
+    *unix_fd = fd;
+    return STATUS_SUCCESS;
+}
+
+
 /***********************************************************************
  *           get_config_dir
  *