ntdll: Reimplement NtReadFile.
Always try a read() before queuing an async I/O. Handle timeout waits for synchronous I/O entirely on the client side. Queue the final APC as a proper user APC.
This commit is contained in:
parent
56852a5eac
commit
539d5863e6
|
@ -79,6 +79,7 @@
|
|||
|
||||
#include "winternl.h"
|
||||
#include "winioctl.h"
|
||||
#include "ddk/ntddser.h"
|
||||
|
||||
WINE_DEFAULT_DEBUG_CHANNEL(ntdll);
|
||||
|
||||
|
@ -372,7 +373,7 @@ static void WINAPI FILE_AsyncReadService(void *user, PIO_STATUS_BLOCK iosb, ULON
|
|||
}
|
||||
else if (result == 0)
|
||||
{
|
||||
status = fileio->already ? STATUS_SUCCESS : STATUS_END_OF_FILE;
|
||||
status = fileio->already ? STATUS_SUCCESS : STATUS_PIPE_BROKEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -404,6 +405,100 @@ static void WINAPI FILE_AsyncReadService(void *user, PIO_STATUS_BLOCK iosb, ULON
|
|||
}
|
||||
}
|
||||
|
||||
struct io_timeouts
|
||||
{
|
||||
int interval; /* max interval between two bytes */
|
||||
int total; /* total timeout for the whole operation */
|
||||
int end_time; /* absolute time of end of operation */
|
||||
};
|
||||
|
||||
/* retrieve the I/O timeouts to use for a given handle */
|
||||
static NTSTATUS get_io_timeouts( HANDLE handle, enum server_fd_type type, ULONG count, BOOL is_read,
|
||||
struct io_timeouts *timeouts )
|
||||
{
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
|
||||
timeouts->interval = timeouts->total = -1;
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case FD_TYPE_SERIAL:
|
||||
{
|
||||
/* GetCommTimeouts */
|
||||
SERIAL_TIMEOUTS st;
|
||||
IO_STATUS_BLOCK io;
|
||||
|
||||
status = NtDeviceIoControlFile( handle, NULL, NULL, NULL, &io,
|
||||
IOCTL_SERIAL_GET_TIMEOUTS, NULL, 0, &st, sizeof(st) );
|
||||
if (status) break;
|
||||
|
||||
if (is_read)
|
||||
{
|
||||
if (st.ReadIntervalTimeout)
|
||||
timeouts->interval = st.ReadIntervalTimeout;
|
||||
|
||||
if (st.ReadTotalTimeoutMultiplier || st.ReadTotalTimeoutConstant)
|
||||
{
|
||||
timeouts->total = st.ReadTotalTimeoutConstant;
|
||||
if (st.ReadTotalTimeoutMultiplier != MAXDWORD)
|
||||
timeouts->total += count * st.ReadTotalTimeoutMultiplier;
|
||||
}
|
||||
else if (st.ReadIntervalTimeout == MAXDWORD)
|
||||
timeouts->interval = 0;
|
||||
}
|
||||
else /* write */
|
||||
{
|
||||
if (st.WriteTotalTimeoutMultiplier || st.WriteTotalTimeoutConstant)
|
||||
{
|
||||
timeouts->total = st.WriteTotalTimeoutConstant;
|
||||
if (st.WriteTotalTimeoutMultiplier != MAXDWORD)
|
||||
timeouts->total += count * st.WriteTotalTimeoutMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FD_TYPE_MAILSLOT:
|
||||
if (is_read)
|
||||
{
|
||||
timeouts->interval = 0; /* return as soon as we got something */
|
||||
SERVER_START_REQ( set_mailslot_info )
|
||||
{
|
||||
req->handle = handle;
|
||||
req->flags = 0;
|
||||
if (!(status = wine_server_call( req ))) timeouts->total = reply->read_timeout;
|
||||
}
|
||||
SERVER_END_REQ;
|
||||
}
|
||||
break;
|
||||
case FD_TYPE_SOCKET:
|
||||
case FD_TYPE_PIPE:
|
||||
if (is_read) timeouts->interval = 0; /* return as soon as we got something */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (timeouts->total != -1) timeouts->end_time = NtGetTickCount() + timeouts->total;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/* retrieve the timeout for the next wait, in milliseconds */
|
||||
static inline int get_next_io_timeout( struct io_timeouts *timeouts, ULONG already )
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
if (timeouts->total != -1)
|
||||
{
|
||||
ret = timeouts->end_time - NtGetTickCount();
|
||||
if (ret < 0) ret = 0;
|
||||
}
|
||||
if (already && timeouts->interval != -1)
|
||||
{
|
||||
if (ret == -1 || ret > timeouts->interval) ret = timeouts->interval;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* NtReadFile [NTDLL.@]
|
||||
|
@ -432,8 +527,10 @@ NTSTATUS WINAPI NtReadFile(HANDLE hFile, HANDLE hEvent,
|
|||
PIO_STATUS_BLOCK io_status, void* buffer, ULONG length,
|
||||
PLARGE_INTEGER offset, PULONG key)
|
||||
{
|
||||
int result, unix_handle, needs_close, flags;
|
||||
int result, unix_handle, needs_close, flags, timeout_init_done = 0;
|
||||
struct io_timeouts timeouts;
|
||||
NTSTATUS status;
|
||||
ULONG total = 0;
|
||||
enum server_fd_type type;
|
||||
|
||||
TRACE("(%p,%p,%p,%p,%p,%p,0x%08x,%p,%p),partial stub!\n",
|
||||
|
@ -445,135 +542,126 @@ NTSTATUS WINAPI NtReadFile(HANDLE hFile, HANDLE hEvent,
|
|||
&needs_close, &type, &flags );
|
||||
if (status) return status;
|
||||
|
||||
if (type == FD_TYPE_FILE && offset)
|
||||
if (type == FD_TYPE_FILE && offset && offset->QuadPart != (LONGLONG)-2 /* FILE_USE_FILE_POINTER_POSITION */ )
|
||||
{
|
||||
/* async I/O doesn't make sense on regular files */
|
||||
if (flags & FD_FLAG_OVERLAPPED)
|
||||
{
|
||||
while ((result = pread( unix_handle, buffer, length, offset->QuadPart )) == -1)
|
||||
{
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EAGAIN || errno == ESPIPE)
|
||||
if (errno != EINTR)
|
||||
{
|
||||
status = STATUS_PENDING;
|
||||
break;
|
||||
}
|
||||
result = 0;
|
||||
status = FILE_GetNtStatus();
|
||||
goto done;
|
||||
}
|
||||
if (result >= 0)
|
||||
}
|
||||
if (!(flags & FD_FLAG_OVERLAPPED)) /* update file pointer position */
|
||||
lseek( unix_handle, offset->QuadPart + result, SEEK_SET );
|
||||
|
||||
total = result;
|
||||
status = total ? STATUS_SUCCESS : STATUS_END_OF_FILE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
io_status->Information = result;
|
||||
status = result ? STATUS_SUCCESS : STATUS_END_OF_FILE;
|
||||
if (hEvent) NtSetEvent( hEvent, NULL );
|
||||
if ((result = read( unix_handle, (char *)buffer + total, length - total )) >= 0)
|
||||
{
|
||||
total += result;
|
||||
if (!result || total == length)
|
||||
{
|
||||
if (total)
|
||||
status = STATUS_SUCCESS;
|
||||
else
|
||||
status = (type == FD_TYPE_FILE) ? STATUS_END_OF_FILE : STATUS_PIPE_BROKEN;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lseek( unix_handle, offset->QuadPart, SEEK_SET ) == (off_t)-1)
|
||||
if (errno == EINTR) continue;
|
||||
if (errno != EAGAIN)
|
||||
{
|
||||
status = FILE_GetNtStatus();
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FD_FLAG_RECV_SHUTDOWN)
|
||||
if (flags & FD_FLAG_OVERLAPPED)
|
||||
{
|
||||
status = STATUS_PIPE_DISCONNECTED;
|
||||
async_fileio *fileio;
|
||||
|
||||
if (total && (flags & FD_FLAG_AVAILABLE))
|
||||
{
|
||||
status = STATUS_SUCCESS;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (flags & FD_FLAG_TIMEOUT)
|
||||
{
|
||||
if (hEvent)
|
||||
{
|
||||
/* this shouldn't happen, but check it */
|
||||
FIXME("NIY-hEvent\n");
|
||||
status = STATUS_NOT_IMPLEMENTED;
|
||||
goto done;
|
||||
}
|
||||
status = NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, NULL, 0, 0);
|
||||
if (status) goto done;
|
||||
}
|
||||
|
||||
if (flags & (FD_FLAG_OVERLAPPED|FD_FLAG_TIMEOUT))
|
||||
{
|
||||
async_fileio* fileio;
|
||||
|
||||
if (!(fileio = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(async_fileio))))
|
||||
{
|
||||
if (flags & FD_FLAG_TIMEOUT) NtClose(hEvent);
|
||||
status = STATUS_NO_MEMORY;
|
||||
goto done;
|
||||
}
|
||||
fileio->handle = hFile;
|
||||
fileio->already = 0;
|
||||
fileio->already = total;
|
||||
fileio->count = length;
|
||||
fileio->apc = apc;
|
||||
fileio->apc_user = apc_user;
|
||||
fileio->buffer = buffer;
|
||||
fileio->queue_apc_on_error = 0;
|
||||
fileio->queue_apc_on_error = 1;
|
||||
fileio->avail_mode = (flags & FD_FLAG_AVAILABLE);
|
||||
fileio->event = hEvent;
|
||||
|
||||
io_status->u.Status = STATUS_PENDING;
|
||||
status = fileio_queue_async(fileio, io_status, TRUE);
|
||||
if (status != STATUS_PENDING)
|
||||
{
|
||||
if (flags & FD_FLAG_TIMEOUT) NtClose(hEvent);
|
||||
goto done;
|
||||
}
|
||||
if (needs_close) close( unix_handle );
|
||||
if (flags & FD_FLAG_TIMEOUT)
|
||||
else /* synchronous read, wait for the fd to become ready */
|
||||
{
|
||||
while (NtWaitForSingleObject(hEvent, TRUE, NULL) == STATUS_USER_APC) /* nothing */ ;
|
||||
NtClose(hEvent);
|
||||
}
|
||||
else
|
||||
struct pollfd pfd;
|
||||
int ret, timeout;
|
||||
|
||||
if (!timeout_init_done)
|
||||
{
|
||||
LARGE_INTEGER timeout;
|
||||
|
||||
/* let some APC be run, this will read some already pending data */
|
||||
timeout.u.LowPart = timeout.u.HighPart = 0;
|
||||
status = NtDelayExecution( TRUE, &timeout );
|
||||
/* the apc didn't run and therefore the completion routine now
|
||||
* needs to be sent errors.
|
||||
* Note that there is no race between setting this flag and
|
||||
* returning errors because apc's are run only during alertable
|
||||
* waits */
|
||||
if (status != STATUS_USER_APC)
|
||||
fileio->queue_apc_on_error = 1;
|
||||
}
|
||||
TRACE("= 0x%08x\n", io_status->u.Status);
|
||||
return io_status->u.Status;
|
||||
timeout_init_done = 1;
|
||||
if ((status = get_io_timeouts( hFile, type, length, TRUE, &timeouts )))
|
||||
goto done;
|
||||
if (hEvent) NtResetEvent( hEvent, NULL );
|
||||
}
|
||||
timeout = get_next_io_timeout( &timeouts, total );
|
||||
|
||||
/* code for synchronous reads */
|
||||
pfd.fd = unix_handle;
|
||||
pfd.events = POLLIN;
|
||||
|
||||
if (!timeout || !(ret = poll( &pfd, 1, timeout )))
|
||||
{
|
||||
if (total) /* return with what we got so far */
|
||||
status = STATUS_SUCCESS;
|
||||
while ((result = read( unix_handle, buffer, length )) == -1)
|
||||
{
|
||||
if ((errno == EAGAIN) || (errno == EINTR)) continue;
|
||||
status = FILE_GetNtStatus();
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
io_status->Information = result;
|
||||
if (status == STATUS_SUCCESS && io_status->Information == 0)
|
||||
{
|
||||
struct stat st;
|
||||
if (fstat( unix_handle, &st ) != -1 && S_ISSOCK( st.st_mode ))
|
||||
status = STATUS_PIPE_BROKEN;
|
||||
else
|
||||
status = STATUS_END_OF_FILE;
|
||||
status = (type == FD_TYPE_MAILSLOT) ? STATUS_IO_TIMEOUT : STATUS_TIMEOUT;
|
||||
goto done;
|
||||
}
|
||||
if (ret == -1 && errno != EINTR)
|
||||
{
|
||||
status = FILE_GetNtStatus();
|
||||
goto done;
|
||||
}
|
||||
/* will now restart the read */
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (needs_close) close( unix_handle );
|
||||
TRACE("= 0x%08x (%lu)\n", status, io_status->Information);
|
||||
if (status != STATUS_PENDING) io_status->u.Status = status;
|
||||
if (apc && !status) apc( apc_user, io_status, 0 );
|
||||
if (status == STATUS_SUCCESS)
|
||||
{
|
||||
io_status->u.Status = status;
|
||||
io_status->Information = total;
|
||||
TRACE("= SUCCESS (%u)\n", total);
|
||||
if (hEvent) NtSetEvent( hEvent, NULL );
|
||||
if (apc) NtQueueApcThread( GetCurrentThread(), (PNTAPCFUNC)apc,
|
||||
(ULONG_PTR)apc_user, (ULONG_PTR)io_status, 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE("= 0x%08x\n", status);
|
||||
if (status != STATUS_PENDING && hEvent) NtResetEvent( hEvent, NULL );
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue