On Tuesday 15 April 2003 12:56 pm, Bill Medland wrote:
On April 15, 2003 10:12 am, Dan Kegel wrote:
Mike isn't doing flushing code for this version of the named pipe implementation, as it's not clear it's possible without a rewrite. He's replacing the whole thing.
- Dan
Oh.
Well in that case I'll just forget it and leave the patch in place on my machine and hope that the whole thing is fixed before our company really needs it.
No cause for despair! IIRC the patch was just rejected because it used wineserver calls for the actual pipe i/o, which was considered too inefficient, even for wine, by Alexandre ;) Alexandre suggested using connectionless sockets (I'm not sure if he means unix domain or inet sockets?). Far be it from me to have an opinion on things I do not understand, but if I /were/ to do so, I'd say that I don't see why connectionful unix domain sockets ought to be insufficient ;)
Anyhow, I vaguely recall that Mike replied that it would take some time, but that perhaps Alexandre's idea was workable... am I getting this right? In case it will help illuminate some idea-emotocon's above brighter heads than mine, here is my shot at converting Mike's patch (which was posted in an almost unmentionable format) into a unified diff against CVS wine, courtesy of winetreediff: -- diff -ur --minimal --exclude-from=/home/greg/bin/winetreediff_excl ../wine/files/file.c ./files/file.c --- ../wine/files/file.c 2003-03-17 23:04:34.000000000 -0600 +++ ./files/file.c 2003-04-09 19:49:12.000000000 -0500 @@ -318,6 +318,27 @@
/*********************************************************************** + * FILE_GetTypeFlags + */ +BOOL WINAPI FILE_GetTypeFlags( HANDLE hFile, enum fd_type *type, int* flags ) +{ + NTSTATUS ret; + SERVER_START_REQ( get_file_info ) + { + req->handle = hFile; + ret = wine_server_call_err( req ); + if( !ret ) + { + if(type) *type = reply->fd_type; + if(flags) *flags = reply->fd_flags; + } + } + SERVER_END_REQ; + return !ret; +} + + +/*********************************************************************** * FILE_GetUnixHandleType * * Retrieve the Unix handle corresponding to a file handle. @@ -1645,10 +1666,52 @@ }
/*********************************************************************** + * FILE_NamedPipeReadService (INTERNAL) + */ +static void FILE_NamedPipeReadService(async_private *ovp) +{ + async_fileio *fileio = (async_fileio*) ovp; + LPOVERLAPPED lpOverlapped = fileio->lpOverlapped; + int result, r; + int already = lpOverlapped->InternalHigh; + LPVOID p = &fileio->buffer[already]; + + TRACE("%p %p\n", lpOverlapped, fileio->buffer ); + + /* read pipe data from server */ + SERVER_START_REQ( read_named_pipe ) + { + req->handle = ovp->handle; + req->count = fileio->count - already; + req->id = (ovp->fd == -1) ? 0 : ovp->fd; + wine_server_set_reply( req, p, fileio->count - already); + r = wine_server_call_err( req ); + if( !r ) + { + result = wine_server_reply_size( reply ); + ovp->fd = reply->id; + } + } + SERVER_END_REQ; + + if( r ) + goto end; + + lpOverlapped->InternalHigh += result; + TRACE("read %d more bytes %ld/%d so far\n",result, + lpOverlapped->InternalHigh,fileio->count); + + if(lpOverlapped->InternalHigh >= fileio->count ) + r = STATUS_SUCCESS; + else + r = STATUS_PENDING; + +end: + lpOverlapped->Internal = r; +} + +/*********************************************************************** * FILE_AsyncReadService (INTERNAL) - * - * This function is called while the client is waiting on the - * server, so we can't make any server calls here. */ static void FILE_AsyncReadService(async_private *ovp) { @@ -1661,6 +1724,8 @@
/* check to see if the data is ready (non-blocking) */
+ if ( ovp->fd < 0 ) + ovp->fd = FILE_GetUnixHandle ( fileio->async.handle, GENERIC_READ ); if ( fileio->fd_type == FD_TYPE_SOCKET ) result = read (ovp->fd, &fileio->buffer[already], fileio->count - already); else @@ -1708,12 +1773,9 @@ static BOOL FILE_ReadFileEx(HANDLE hFile, LPVOID buffer, DWORD bytesToRead, LPOVERLAPPED overlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, - HANDLE hEvent) + HANDLE hEvent, enum fd_type type) { async_fileio *ovp; - int fd; - int flags; - enum fd_type type;
TRACE("file %p to buf %p num %ld %p func %p\n", hFile, buffer, bytesToRead, overlapped, lpCompletionRoutine); @@ -1725,26 +1787,22 @@ return FALSE; }
- fd = FILE_GetUnixHandleType ( hFile, GENERIC_READ, &type, &flags); - if ( fd < 0 ) - { - WARN ( "Couldn't get FD\n" ); - return FALSE; - } - ovp = (async_fileio*) HeapAlloc(GetProcessHeap(), 0, sizeof (async_fileio)); if(!ovp) { TRACE("HeapAlloc Failed\n"); SetLastError(ERROR_NOT_ENOUGH_MEMORY); - goto error; + return FALSE; }
ovp->async.ops = ( lpCompletionRoutine ? &fileio_async_ops : &fileio_nocomp_async_ops ); ovp->async.handle = hFile; - ovp->async.fd = fd; + ovp->async.fd = -1; /* get it later */ ovp->async.type = ASYNC_TYPE_READ; - ovp->async.func = FILE_AsyncReadService; + if( type == FD_TYPE_NAMED_PIPE ) + ovp->async.func = FILE_NamedPipeReadService; + else + ovp->async.func = FILE_AsyncReadService; ovp->async.event = hEvent; ovp->lpOverlapped = overlapped; ovp->count = bytesToRead; @@ -1753,11 +1811,6 @@ ovp->fd_type = type;
return !register_new_async (&ovp->async); - -error: - close (fd); - return FALSE; - }
/*********************************************************************** @@ -1767,21 +1820,28 @@ LPOVERLAPPED overlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) { + enum fd_type type; + + if( !FILE_GetTypeFlags( hFile, &type, NULL )) + return FALSE; overlapped->InternalHigh = 0; - return FILE_ReadFileEx(hFile,buffer,bytesToRead,overlapped,lpCompletionRoutine, INVALID_HANDLE_VALUE); + return FILE_ReadFileEx(hFile,buffer,bytesToRead,overlapped,lpCompletionRoutine, INVALID_HANDLE_VALUE, type); }
static BOOL FILE_TimeoutRead(HANDLE hFile, LPVOID buffer, DWORD bytesToRead, LPDWORD bytesRead) { OVERLAPPED ov; BOOL r = FALSE; + enum fd_type type;
TRACE("%p %p %ld %p\n", hFile, buffer, bytesToRead, bytesRead );
+ if( !FILE_GetTypeFlags( hFile, &type, NULL )) + return FALSE; ZeroMemory(&ov, sizeof (OVERLAPPED)); if(STATUS_SUCCESS==NtCreateEvent(&ov.hEvent, SYNCHRONIZE, NULL, 0, 0)) { - if(FILE_ReadFileEx(hFile, buffer, bytesToRead, &ov, NULL, ov.hEvent)) + if(FILE_ReadFileEx(hFile, buffer, bytesToRead, &ov, NULL, ov.hEvent, type)) { r = GetOverlappedResult(hFile, &ov, bytesRead, TRUE); } @@ -1796,7 +1856,7 @@ BOOL WINAPI ReadFile( HANDLE hFile, LPVOID buffer, DWORD bytesToRead, LPDWORD bytesRead, LPOVERLAPPED overlapped ) { - int unix_handle, result, flags; + int unix_handle, result, flags = 0; enum fd_type type;
TRACE("%p %p %ld %p %p\n", hFile, buffer, bytesToRead, @@ -1805,23 +1865,20 @@ if (bytesRead) *bytesRead = 0; /* Do this before anything else */ if (!bytesToRead) return TRUE;
- unix_handle = FILE_GetUnixHandleType( hFile, GENERIC_READ, &type, &flags ); + if( !FILE_GetTypeFlags( hFile, &type, &flags )) + return FALSE;
if (flags & FD_FLAG_OVERLAPPED) { - if (unix_handle == -1) return FALSE; if ( (overlapped==NULL) || NtResetEvent( overlapped->hEvent, NULL ) ) { TRACE("Overlapped not specified or invalid event flag\n"); - close(unix_handle); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
- close(unix_handle); overlapped->InternalHigh = 0; - - if(!FILE_ReadFileEx(hFile, buffer, bytesToRead, overlapped, NULL, overlapped->hEvent)) + if(!FILE_ReadFileEx(hFile, buffer, bytesToRead, overlapped, NULL, overlapped->hEvent, type)) return FALSE;
if ( !GetOverlappedResult (hFile, overlapped, bytesRead, FALSE) ) @@ -1834,10 +1891,8 @@ return TRUE; } if (flags & FD_FLAG_TIMEOUT) - { - close(unix_handle); return FILE_TimeoutRead(hFile, buffer, bytesToRead, bytesRead); - } + switch(type) { case FD_TYPE_SMB: @@ -1848,7 +1903,6 @@
case FD_TYPE_DEFAULT: /* normal unix files */ - if (unix_handle == -1) return FALSE; if (overlapped) { DWORD highOffset = overlapped->OffsetHigh; @@ -1856,17 +1910,19 @@ &highOffset, FILE_BEGIN)) && (GetLastError() != NO_ERROR) ) { - close(unix_handle); return FALSE; } } break;
default: - if (unix_handle == -1) - return FALSE; + break; }
+ unix_handle = FILE_GetUnixHandle ( hFile, GENERIC_READ ); + if (unix_handle == -1) + return FALSE; + if(overlapped) { off_t offset = OVERLAPPED_OFFSET(overlapped); @@ -1894,6 +1950,54 @@
/*********************************************************************** + * FILE_NamedPipeWrite (INTERNAL) + * + * hack: use fd member to store write ID. + */ +static void FILE_NamedPipeWrite(async_private *ovp) +{ + async_fileio *fileio = (async_fileio *) ovp; + LPOVERLAPPED lpOverlapped = fileio->lpOverlapped; + int count, already = lpOverlapped->InternalHigh; + LPVOID p = &fileio->buffer[already]; + NTSTATUS r; + + /* write some data (non-blocking) */ + count = fileio->count - already; + + TRACE("ovp %p writing %d bytes of %p\n",lpOverlapped,count,fileio->buffer ); + + SERVER_START_REQ( write_named_pipe ) + { + req->handle = ovp->handle; + req->count = count; + req->id = (ovp->fd == -1) ? 0 : ovp->fd; + wine_server_add_data( req, p, count); + r = wine_server_call_err( req ); + if( !r ) + ovp->fd = reply->id; + } + SERVER_END_REQ; + + TRACE("r = %08lx\n",r); + + if( r ) + goto end; + + lpOverlapped->InternalHigh += count; + + TRACE("wrote %d more bytes %ld/%d so far\n",count,lpOverlapped->InternalHigh,fileio->count); + + if(lpOverlapped->InternalHigh < fileio->count) + r = STATUS_PENDING; + else + r = STATUS_SUCCESS; + +end: + lpOverlapped->Internal = r; +} + +/*********************************************************************** * FILE_AsyncWriteService (INTERNAL) * * This function is called while the client is waiting on the @@ -1910,6 +2014,8 @@
/* write some data (non-blocking) */
+ if ( ovp->fd < 0 ) + ovp->fd = FILE_GetUnixHandle ( fileio->async.handle, GENERIC_WRITE ); if ( fileio->fd_type == FD_TYPE_SOCKET ) result = write(ovp->fd, &fileio->buffer[already], fileio->count - already); else @@ -1952,12 +2058,9 @@ static BOOL FILE_WriteFileEx(HANDLE hFile, LPCVOID buffer, DWORD bytesToWrite, LPOVERLAPPED overlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, - HANDLE hEvent) + HANDLE hEvent, enum fd_type type) { async_fileio *ovp; - int fd; - int flags; - enum fd_type type;
TRACE("file %p to buf %p num %ld %p func %p handle %p\n", hFile, buffer, bytesToWrite, overlapped, lpCompletionRoutine, hEvent); @@ -1968,26 +2071,22 @@ return FALSE; }
- fd = FILE_GetUnixHandleType ( hFile, GENERIC_WRITE, &type, &flags ); - if ( fd < 0 ) - { - TRACE( "Couldn't get FD\n" ); - return FALSE; - } - ovp = (async_fileio*) HeapAlloc(GetProcessHeap(), 0, sizeof (async_fileio)); if(!ovp) { TRACE("HeapAlloc Failed\n"); SetLastError(ERROR_NOT_ENOUGH_MEMORY); - goto error; + return FALSE; }
ovp->async.ops = ( lpCompletionRoutine ? &fileio_async_ops : &fileio_nocomp_async_ops ); ovp->async.handle = hFile; - ovp->async.fd = fd; + ovp->async.fd = -1; /* get it later */ ovp->async.type = ASYNC_TYPE_WRITE; - ovp->async.func = FILE_AsyncWriteService; + if( type == FD_TYPE_NAMED_PIPE ) + ovp->async.func = FILE_NamedPipeWrite; + else + ovp->async.func = FILE_AsyncWriteService; ovp->lpOverlapped = overlapped; ovp->async.event = hEvent; ovp->buffer = (LPVOID) buffer; @@ -1996,10 +2095,6 @@ ovp->fd_type = type;
return !register_new_async (&ovp->async); - -error: - close (fd); - return FALSE; }
/*********************************************************************** @@ -2009,9 +2104,35 @@ LPOVERLAPPED overlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) { + enum fd_type type; + + if( !FILE_GetTypeFlags( hFile, &type, NULL )) + return FALSE; overlapped->InternalHigh = 0;
- return FILE_WriteFileEx(hFile, buffer, bytesToWrite, overlapped, lpCompletionRoutine, INVALID_HANDLE_VALUE); + return FILE_WriteFileEx(hFile, buffer, bytesToWrite, overlapped, lpCompletionRoutine, INVALID_HANDLE_VALUE, type); +} + +static BOOL FILE_TimeoutWrite(HANDLE hFile, LPCVOID buffer, DWORD bytesToWrite, LPDWORD bytesWritten) +{ + OVERLAPPED ov; + BOOL r = FALSE; + enum fd_type type; + + TRACE("%p %p %ld %p\n", hFile, buffer, bytesToWrite, bytesWritten ); + + if( !FILE_GetTypeFlags( hFile, &type, NULL )) + return FALSE; + ZeroMemory(&ov, sizeof (OVERLAPPED)); + if(STATUS_SUCCESS==NtCreateEvent(&ov.hEvent, SYNCHRONIZE, NULL, 0, 0)) + { + if(FILE_WriteFileEx(hFile, buffer, bytesToWrite, &ov, NULL, ov.hEvent, type)) + { + r = GetOverlappedResult(hFile, &ov, bytesWritten, TRUE); + } + } + CloseHandle(ov.hEvent); + return r; }
/*********************************************************************** @@ -2029,23 +2150,21 @@ if (bytesWritten) *bytesWritten = 0; /* Do this before anything else */ if (!bytesToWrite) return TRUE;
- unix_handle = FILE_GetUnixHandleType( hFile, GENERIC_WRITE, &type, &flags ); + if( !FILE_GetTypeFlags( hFile, &type, &flags )) + return FALSE;
if (flags & FD_FLAG_OVERLAPPED) { - if (unix_handle == -1) return FALSE; if ( (overlapped==NULL) || NtResetEvent( overlapped->hEvent, NULL ) ) { TRACE("Overlapped not specified or invalid event flag\n"); - close(unix_handle); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
- close(unix_handle); overlapped->InternalHigh = 0;
- if(!FILE_WriteFileEx(hFile, buffer, bytesToWrite, overlapped, NULL, overlapped->hEvent)) + if(!FILE_WriteFileEx(hFile, buffer, bytesToWrite, overlapped, NULL, overlapped->hEvent, type)) return FALSE;
if ( !GetOverlappedResult (hFile, overlapped, bytesWritten, FALSE) ) @@ -2058,6 +2177,9 @@ return TRUE; }
+ if (flags & FD_FLAG_TIMEOUT) + return FILE_TimeoutWrite(hFile, buffer, bytesToWrite, bytesWritten); + switch(type) { case FD_TYPE_CONSOLE: @@ -2066,33 +2188,29 @@ return FILE_WriteConsole(hFile, buffer, bytesToWrite, bytesWritten, NULL);
case FD_TYPE_DEFAULT: - if (unix_handle == -1) return FALSE; - if(overlapped) { DWORD highOffset = overlapped->OffsetHigh; if ( (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, overlapped->Offset, &highOffset, FILE_BEGIN)) && (GetLastError() != NO_ERROR) ) - { - close(unix_handle); return FALSE; - } } break;
default: - if (unix_handle == -1) - return FALSE; if (overlapped) { - close(unix_handle); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } break; }
+ unix_handle = FILE_GetUnixHandle ( hFile, GENERIC_WRITE ); + if (unix_handle == -1) + return FALSE; + if(overlapped) { off_t offset = OVERLAPPED_OFFSET(overlapped); diff -ur --minimal --exclude-from=/home/greg/bin/winetreediff_excl ../wine/include/wine/server_protocol.h ./include/wine/server_protocol.h --- ../wine/include/wine/server_protocol.h 2003-04-05 22:13:00.000000000 -0600 +++ ./include/wine/server_protocol.h 2003-04-09 18:57:15.000000000 -0500 @@ -817,12 +817,14 @@ FD_TYPE_DEFAULT, FD_TYPE_CONSOLE, FD_TYPE_SOCKET, - FD_TYPE_SMB + FD_TYPE_SMB, + FD_TYPE_NAMED_PIPE }; #define FD_FLAG_OVERLAPPED 0x01 #define FD_FLAG_TIMEOUT 0x02 #define FD_FLAG_RECV_SHUTDOWN 0x04 #define FD_FLAG_SEND_SHUTDOWN 0x08 +#define FD_FLAG_NO_FD 0x10
struct set_file_pointer_request @@ -898,6 +900,8 @@ int index_high; int index_low; unsigned int serial; + int fd_type; + int fd_flags; };
@@ -2441,6 +2445,33 @@
+struct read_named_pipe_request +{ + struct request_header __header; + obj_handle_t handle; + unsigned int id; + int count; +}; +struct read_named_pipe_reply +{ + struct reply_header __header; + unsigned int id; +}; + + +struct write_named_pipe_request +{ + struct request_header __header; + obj_handle_t handle; + unsigned int id; + int count; +}; +struct write_named_pipe_reply +{ + struct reply_header __header; + unsigned int id; +}; + struct wait_named_pipe_request { struct request_header __header; @@ -3178,6 +3209,8 @@ REQ_create_named_pipe, REQ_open_named_pipe, REQ_connect_named_pipe, + REQ_read_named_pipe, + REQ_write_named_pipe, REQ_wait_named_pipe, REQ_disconnect_named_pipe, REQ_get_named_pipe_info, @@ -3360,6 +3393,8 @@ struct create_named_pipe_request create_named_pipe_request; struct open_named_pipe_request open_named_pipe_request; struct connect_named_pipe_request connect_named_pipe_request; + struct read_named_pipe_request read_named_pipe_request; + struct write_named_pipe_request write_named_pipe_request; struct wait_named_pipe_request wait_named_pipe_request; struct disconnect_named_pipe_request disconnect_named_pipe_request; struct get_named_pipe_info_request get_named_pipe_info_request; @@ -3540,6 +3575,8 @@ struct create_named_pipe_reply create_named_pipe_reply; struct open_named_pipe_reply open_named_pipe_reply; struct connect_named_pipe_reply connect_named_pipe_reply; + struct read_named_pipe_reply read_named_pipe_reply; + struct write_named_pipe_reply write_named_pipe_reply; struct wait_named_pipe_reply wait_named_pipe_reply; struct disconnect_named_pipe_reply disconnect_named_pipe_reply; struct get_named_pipe_info_reply get_named_pipe_info_reply; @@ -3581,6 +3618,6 @@ struct get_next_hook_reply get_next_hook_reply; };
-#define SERVER_PROTOCOL_VERSION 105 +#define SERVER_PROTOCOL_VERSION 106
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff -ur --minimal --exclude-from=/home/greg/bin/winetreediff_excl ../wine/server/fd.c ./server/fd.c --- ../wine/server/fd.c 2003-03-25 19:32:18.000000000 -0600 +++ ./server/fd.c 2003-04-09 17:26:22.000000000 -0500 @@ -1044,8 +1044,7 @@
if (fd) { - int flags; - fd->fd_ops->get_file_info( fd, reply, &flags ); + reply->fd_type = fd->fd_ops->get_file_info( fd, reply, &reply->fd_flags ); release_object( fd ); } } diff -ur --minimal --exclude-from=/home/greg/bin/winetreediff_excl ../wine/server/named_pipe.c ./server/named_pipe.c --- ../wine/server/named_pipe.c 2003-04-05 22:13:01.000000000 -0600 +++ ./server/named_pipe.c 2003-04-10 18:13:06.000000000 -0500 @@ -2,7 +2,7 @@ * Server-side pipe management * * Copyright (C) 1998 Alexandre Julliard - * Copyright (C) 2001 Mike McCormack + * Copyright (C) 2001, 2003 Mike McCormack * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,6 +18,24 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * This implemention now handles reading writing and buffering of pipe data + * internally. This is necessary for two reasons: + * - named pipes have a message mode which cannot be implemented on top + * of unix pipes without race conditions or threads locking the server. + * - named pipes can work across a network via the SMB protocol + * + * The implementation works by allocating a message of a fixed size, then + * allowing a thread to write into it. When all writing for a message is + * complete, the message is sent to its destination. This means that: + * - only complete messages are read from a message queue + * - if one thread has problems during a write, other threads can continue + * to write to a pipe + * - writing a single message can be done in multiple server calls if + * the message does not completely fit into the server buffer + * - a queue for incomplete outgoing messages (user->in) is kept + * + * 23 Feb 2003 mjm + * * TODO: * improve error handling */ @@ -27,7 +45,7 @@
#include <assert.h> #include <fcntl.h> -#include <string.h> +//#include <string.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> @@ -39,16 +57,21 @@ #include <unistd.h>
#include "winbase.h" +#include "ntstatus.h"
#include "file.h" #include "handle.h" #include "thread.h" #include "request.h" +#include "async.h" + +#define FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000
enum pipe_state { ps_none, ps_idle_server, + ps_wait_only, ps_wait_open, ps_wait_connect, ps_connected_server, @@ -56,30 +79,61 @@ ps_disconnected };
+#define PIPE_MIN_BUFFER 0x100 +#define PIPE_MAX_BUFFER 0x10000 + struct named_pipe;
+/* each message is a size followed by data */ +struct pipe_msg +{ + unsigned int length; + unsigned int offset; + struct pipe_msg *next; + struct pipe_msg *prev; + unsigned char data[1]; +}; + +struct msg_queue +{ + int length; /* total size of all messages in the queue */ + int maxsize; /* try not to add more data than this */ + struct pipe_msg *first; /* first message the queue */ + struct pipe_msg *last; /* last message in the queue */ +}; + struct pipe_user { struct object obj; struct fd *fd; enum pipe_state state; - struct pipe_user *other; - struct named_pipe *pipe; - struct pipe_user *next; - struct pipe_user *prev; - struct thread *thread; + struct pipe_user *other; /* the other end */ + struct named_pipe *pipe; /* description of this pipe end */ + + struct async_queue read_q; /* queues of threads waiting for us */ + struct async_queue write_q; + /* struct async_queue wait_q; */ + + struct thread *thread; /* the thread that owns this pipe end */ void *func; void *overlapped; + struct msg_queue in; /* messages that are being written */ + struct msg_queue out; /* messages waitiing to be read */ + + struct pipe_user *next; /* doubly linked list of pipe users */ + struct pipe_user *prev; };
struct named_pipe { struct object obj; /* object header */ + unsigned int openmode; unsigned int pipemode; unsigned int maxinstances; unsigned int outsize; unsigned int insize; unsigned int timeout; + unsigned int instances; struct pipe_user *users; };
@@ -99,33 +153,47 @@ };
static void pipe_user_dump( struct object *obj, int verbose ); -static struct fd *pipe_user_get_fd( struct object *obj ); static void pipe_user_destroy( struct object *obj); - -static int pipe_user_get_poll_events( struct fd *fd ); -static int pipe_user_get_info( struct fd *fd, struct get_file_info_reply *reply, int *flags ); +static struct fd *pipe_user_get_fd( struct object *obj );
static const struct object_ops pipe_user_ops = { - sizeof(struct pipe_user), /* size */ - pipe_user_dump, /* dump */ - default_fd_add_queue, /* add_queue */ - default_fd_remove_queue, /* remove_queue */ - default_fd_signaled, /* signaled */ - no_satisfied, /* satisfied */ - pipe_user_get_fd, /* get_fd */ - pipe_user_destroy /* destroy */ + sizeof(struct pipe_user), /* size */ + pipe_user_dump, /* dump */ + add_queue, /* add_queue */ + remove_queue, /* remove_queue */ + NULL, /* signaled */ + NULL, /* satisfied */ + pipe_user_get_fd, /* get_fd */ + pipe_user_destroy /* destroy */ };
+static int pipe_user_flush( struct fd *fd ); +static int pipe_user_get_info( struct fd *fd, struct get_file_info_reply *reply, int *flags ); +static void pipe_user_queue_async( struct fd *fd, void *ptr, unsigned int status, int type, int count ); + static const struct fd_ops pipe_user_fd_ops = { - pipe_user_get_poll_events, /* get_poll_events */ - default_poll_event, /* poll_event */ - no_flush, /* flush */ - pipe_user_get_info, /* get_file_info */ - no_queue_async /* queue_async */ + NULL, /* get_poll_events */ + NULL, /* poll_event */ + pipe_user_flush, /* flush */ + pipe_user_get_info, /* get_file_info */ + pipe_user_queue_async /* queue_async */ };
+static struct fd *pipe_user_get_fd( struct object *obj ) +{ + struct pipe_user *pipe_user = (struct pipe_user *)obj; + assert( obj->ops == &pipe_user_ops ); + return (struct fd *)grab_object( pipe_user->fd ); +} + +static int pipe_user_flush( struct fd *fd ) +{ + /* need to sleep until write all the pending data is written */ + return 0; +} + static void named_pipe_dump( struct object *obj, int verbose ) { struct named_pipe *pipe = (struct named_pipe *)obj; @@ -160,10 +228,220 @@ user->overlapped=NULL; }
-static struct fd *pipe_user_get_fd( struct object *obj ) +/* the following functions implement a message FIFO */ + +static void add_message( struct msg_queue *q, struct pipe_msg *msg) { - struct pipe_user *user = (struct pipe_user *)obj; - return (struct fd *)grab_object( user->fd ); + /* add a message at the end of the queue */ + q->length += msg->length; + msg->prev = q->last; + if( q->last ) + q->last->next = msg; + else + q->first = msg; + q->last = msg; +} + +/* remove a message from a queue and optionally free it */ +static void remove_message( struct msg_queue *q, struct pipe_msg *msg, int del) +{ + q->length -= msg->length; + if( msg->next ) + msg->next->prev = msg->prev; + else + q->last = msg->prev; + if( msg->prev ) + msg->prev->next = msg->next; + else + q->first = msg->next; + msg->next = msg->prev = NULL; + + /* if there's no messages, the length should be zero */ + assert( q->first || !q->length ); + + if( del ) + free( msg ); +} + +static void init_message_queue( struct msg_queue *q, int max ) +{ + q->first = q->last = NULL; + q->length = 0; + q->maxsize = max; +} + +static void delete_message_queue( struct msg_queue *q ) +{ + while( q->first ) + { + struct pipe_msg * t = q->first; + remove_message( q, t, 1 ); + } + assert( q->length == 0); + q->maxsize = 0; +} + +static struct pipe_msg *create_new_message( struct msg_queue *q, int size ) +{ + struct pipe_msg *msg; + + msg = malloc( sizeof *msg + size ); + msg->length = size; + msg->offset = 0; + add_message( q, msg ); + return msg; +} + +static struct pipe_msg *find_message( struct msg_queue *q, int id) +{ + struct pipe_msg *msg; + + for( msg = q->first; msg; msg = msg->next ) + if( id == (int) msg ) + return msg; + set_error( STATUS_INVALID_PARAMETER ); + return NULL; +} + +static struct pipe_msg *find_new_message( struct msg_queue *q ) +{ + struct pipe_msg *msg; + + for( msg = q->first; msg; msg = msg->next ) + if( msg->offset == 0 ) + return msg; + return NULL; +} + +/* This function collects data sent from one thread into a message. + When the message is complete, the function returns non-zero. */ +static int pipe_build_msg( struct pipe_msg *msg ) +{ + const void *data = get_req_data(); + int count = get_req_data_size(); + + /* trying to write more data than we were told about - + this shouldn't happen... if it does somebody stuffed up, so + to make sure we don't get in trouble, throw away the extra data */ + if( count > (msg->length - msg->offset) ) + { + fprintf(stderr,"Throwing away extra in pipe write!\n"); + count = (msg->length - msg->offset); + } + + /* copy the data to the message */ + memcpy( &msg->data[msg->offset], data, count ); + msg->offset += count; + + /* check if this message is complete */ + if( msg->offset != msg->length ) + return 0; + + msg->offset = 0; + return 1; +} + +static int pipe_get_message( struct pipe_msg *msg, int count ) +{ + void *out; + + if( count > (msg->length - msg->offset) ) + count = (msg->length - msg->offset) ; + + if( count > get_reply_max_size() ) + count = get_reply_max_size(); + + out = set_reply_data_size( count ); + memcpy( out, &msg->data[msg->offset], count); + msg->offset += count; + + return (msg->offset == msg->length); +} + +static int pipe_user_check_actions( struct pipe_user *user ) +{ + int done = 0; + + if(IS_READY(user->read_q)) + { + if( find_new_message( &user->out ) ) + { + async_notify(user->read_q.head,STATUS_ALERTED); + done = 1; + } + if( ( user->state != ps_connected_server ) && + ( user->state != ps_connected_client) ) + { + async_notify(user->read_q.head,STATUS_PIPE_CLOSING); + done = 1; + } + } + + if(IS_READY(user->write_q) && ( user->in.length < user->in.maxsize ) ) + { + async_notify(user->write_q.head,STATUS_ALERTED); + done = 1; + } + + /*if(IS_READY(user->wait_q) && (POLLIN & event) ) + { + async_notify(user->wait_q.head,STATUS_ALERTED); + done = 1; + } */ + + return done; +} + +static void pipe_user_queue_async(struct fd *fd, void *ptr, unsigned int status, int type, int count) +{ + struct pipe_user *user = get_fd_user( fd ); + struct async_queue *q; + struct async *async; + int timeout; + + assert(user->obj.ops == &pipe_user_ops); + + switch(type) + { + case ASYNC_TYPE_READ: + q = &user->read_q; + timeout = user->pipe->timeout; + break; + /* case ASYNC_TYPE_WAIT: + q = &user->wait_q; + timeout = 0; + break; */ + case ASYNC_TYPE_WRITE: + q = &user->write_q; + timeout = user->pipe->timeout; + break; + default: + set_error(STATUS_INVALID_PARAMETER); + return; + } + + async = find_async ( q, current, ptr ); + + if ( status == STATUS_PENDING ) + { + if ( !async ) + async = create_async ( &user->obj, current, ptr ); + if ( !async ) + return; + + async->status = STATUS_PENDING; + if(!async->q) + { + async_add_timeout(async,timeout); + async_insert(q, async); + } + + /* Check if the new pending request can be served immediately */ + if( pipe_user_check_actions( user ) ) + return; + } + else if ( async ) destroy_async ( async ); + else set_error ( STATUS_INVALID_PARAMETER ); }
static void pipe_user_destroy( struct object *obj) @@ -177,8 +455,6 @@
if(user->other) { - release_object( user->other->fd ); - user->other->fd = NULL; switch(user->other->state) { case ps_connected_server: @@ -195,22 +471,25 @@ user->other = NULL; }
+ delete_message_queue( &user->in ); + delete_message_queue( &user->out ); + + destroy_async_queue(&user->read_q); + destroy_async_queue(&user->write_q); + /* destroy_async_queue(&user->wait_q); */ + if (user->fd) release_object( user->fd ); + /* remove user from pipe's user list */ if (user->next) user->next->prev = user->prev; if (user->prev) user->prev->next = user->next; else user->pipe->users = user->next; if (user->thread) release_object(user->thread); release_object(user->pipe); - if (user->fd) release_object( user->fd ); -} - -static int pipe_user_get_poll_events( struct fd *fd ) -{ - return POLLIN | POLLOUT; /* FIXME */ }
static int pipe_user_get_info( struct fd *fd, struct get_file_info_reply *reply, int *flags ) { + /* struct pipe_user * user = get_fd_user( fd );*/ if (reply) { reply->type = FILE_TYPE_PIPE; @@ -224,8 +503,8 @@ reply->index_low = 0; reply->serial = 0; } - *flags = 0; - return FD_TYPE_DEFAULT; + *flags = FD_FLAG_TIMEOUT; + return FD_TYPE_NAMED_PIPE; }
static struct named_pipe *create_named_pipe( const WCHAR *name, size_t len ) @@ -243,28 +522,13 @@ return pipe; }
-static struct named_pipe *open_named_pipe( const WCHAR *name, size_t len ) -{ - struct object *obj; - - if ((obj = find_object( sync_namespace, name, len ))) - { - if (obj->ops == &named_pipe_ops) return (struct named_pipe *)obj; - release_object( obj ); - set_error( STATUS_OBJECT_TYPE_MISMATCH ); - } - else set_error( STATUS_OBJECT_NAME_NOT_FOUND ); - - return NULL; -} - static struct pipe_user *get_pipe_user_obj( struct process *process, obj_handle_t handle, unsigned int access ) { return (struct pipe_user *)get_handle_obj( process, handle, access, &pipe_user_ops ); }
-static struct pipe_user *create_pipe_user( struct named_pipe *pipe ) +static struct pipe_user *create_pipe_user( struct named_pipe *pipe, int fd ) { struct pipe_user *user;
@@ -272,7 +536,13 @@ if(!user) return NULL;
- user->fd = NULL; + user->fd = alloc_fd( &pipe_user_fd_ops, &user->obj ); + if (!user->fd) + { + release_object( user ); + return NULL; + } + user->pipe = pipe; user->state = ps_none; user->other = NULL; @@ -285,6 +555,13 @@ user->prev = NULL; pipe->users = user;
+ init_message_queue( &user->in, pipe->insize ); + init_message_queue( &user->out, pipe->outsize ); + + init_async_queue( &user->read_q ); + init_async_queue( &user->write_q ); + /* init_async_queue( &user->wait_q ); */ + grab_object(pipe);
return user; @@ -306,12 +583,48 @@ return (struct pipe_user *)grab_object( x ); }
+static void do_connect_pipeends( struct pipe_user *client, struct pipe_user *server ) +{ + struct pipe_msg **pmsg; + + server->state = ps_connected_server; + server->other = client; + client->state = ps_connected_client; + client->other = server; + notify_waiter( server,STATUS_SUCCESS ); + + /* send any waiting messages to the server */ + pmsg = &client->in.first; + while( *pmsg ) + { + struct pipe_msg *msg = *pmsg; + if( msg->offset == 0 ) + { + remove_message( &client->in, msg, 0 ); + add_message( &server->out, msg ); + } + else + pmsg = &msg->next; + } + + /* the server may want to read, the client may want to write */ + pipe_user_check_actions( server ); + pipe_user_check_actions( client ); +} + DECL_HANDLER(create_named_pipe) { struct named_pipe *pipe; struct pipe_user *user;
reply->handle = 0; + + if( req->maxinstances > PIPE_UNLIMITED_INSTANCES ) + { + set_error( STATUS_INVALID_PARAMETER ); + return; + } + pipe = create_named_pipe( get_req_data(), get_req_data_size() ); if(!pipe) return; @@ -322,11 +635,26 @@ pipe->outsize = req->outsize; pipe->maxinstances = req->maxinstances; pipe->timeout = req->timeout; + pipe->openmode = req->openmode; pipe->pipemode = req->pipemode; + pipe->instances = 0; + } + else if( ( req->openmode & FILE_FLAG_FIRST_PIPE_INSTANCE ) || + ( req->openmode != pipe->openmode ) || + ( req->timeout != pipe->timeout ) || + ( ( req->pipemode & PIPE_READMODE_MESSAGE ) & + !( req->openmode & PIPE_TYPE_MESSAGE ) ) || + ( ( pipe->maxinstances != PIPE_UNLIMITED_INSTANCES) && + ( pipe->instances >= pipe->maxinstances ) ) + ) + { + set_error( STATUS_ACCESS_DENIED ); + goto end; }
- user = create_pipe_user( pipe ); + pipe->instances ++;
+ user = create_pipe_user (pipe, -1); if(user) { user->state = ps_idle_server; @@ -334,51 +662,49 @@ release_object( user ); }
+end: release_object( pipe ); }
DECL_HANDLER(open_named_pipe) { - struct pipe_user *user, *partner; struct named_pipe *pipe;
reply->handle = 0; - - if (!(pipe = open_named_pipe( get_req_data(), get_req_data_size() ))) - { - set_error( STATUS_NO_SUCH_FILE ); - return; - } - if (!(partner = find_partner(pipe, ps_wait_open))) - { - release_object(pipe); - set_error( STATUS_PIPE_NOT_AVAILABLE ); + pipe = create_named_pipe( get_req_data(), get_req_data_size() ); + if(!pipe) return; - } - if ((user = create_pipe_user( pipe ))) - { - int fds[2];
- if(!socketpair(PF_UNIX, SOCK_STREAM, 0, fds)) + if (get_error() == STATUS_OBJECT_NAME_COLLISION) + { + struct pipe_user *user = create_pipe_user (pipe, -1); + if( user ) { - user->fd = create_anonymous_fd( &pipe_user_fd_ops, fds[1], &user->obj ); - partner->fd = create_anonymous_fd( &pipe_user_fd_ops, fds[0], &partner->obj ); - if (user->fd && partner->fd) + struct pipe_user *partner; + reply->handle = alloc_handle( current->process, user, req->access, 0); + //user->fd = create_anonymous_fd( &pipe_user_fd_ops, fds[1], &user->obj ); + //partner->fd = create_anonymous_fd( &pipe_user_fd_ops, fds[0], &partner->obj ); + //if (user->fd && partner->fd + if ((partner = find_partner(pipe, ps_wait_open))) { - notify_waiter(partner,STATUS_SUCCESS); - partner->state = ps_connected_server; - partner->other = user; - user->state = ps_connected_client; - user->other = partner; - reply->handle = alloc_handle( current->process, user, req->access, 0 ); + do_connect_pipeends( user, partner ); + release_object( partner ); } + else + user->state = ps_wait_connect; + release_object(user); + } + else + { + set_error(STATUS_NO_MEMORY); } - else file_set_error(); - - release_object( user ); } - release_object( partner ); - release_object( pipe ); + else + { + set_error(STATUS_NO_SUCH_FILE); + } + + release_object(pipe); }
DECL_HANDLER(connect_named_pipe) @@ -401,10 +727,18 @@ user->overlapped = req->overlapped;
/* notify all waiters that a pipe just became available */ - while( (partner = find_partner(user->pipe,ps_wait_connect)) ) + while( (partner = find_partner(user->pipe,ps_wait_only)) ) { notify_waiter(partner,STATUS_SUCCESS); release_object(partner); + release_object(partner); + } + + /* connect any CreateFile calls waiting for a pipe */ + while( (partner = find_partner(user->pipe,ps_wait_connect)) ) + { + do_connect_pipeends( partner, user ); + release_object(partner); } }
@@ -414,35 +748,58 @@ DECL_HANDLER(wait_named_pipe) { struct named_pipe *pipe; - struct pipe_user *partner;
- if (!(pipe = open_named_pipe( get_req_data(), get_req_data_size() ))) - { - set_error( STATUS_PIPE_NOT_AVAILABLE ); - return; - } - if( (partner = find_partner(pipe,ps_wait_open)) ) - { - /* this should use notify_waiter, - but no pipe_user object exists now... */ - thread_queue_apc(current,NULL,req->func, - APC_ASYNC, 1, req->overlapped, STATUS_SUCCESS, NULL); - release_object(partner); - } - else + // if (!(pipe = open_named_pipe( get_req_data(), get_req_data_size() ))) + // { + // set_error( STATUS_PIPE_NOT_AVAILABLE ); + // return; + // } + // if( (partner = find_partner(pipe,ps_wait_open)) ) + // { + // /* this should use notify_waiter, + // but no pipe_user object exists now... */ + // thread_queue_apc(current,NULL,req->func, + // APC_ASYNC, 1, req->overlapped, STATUS_SUCCESS, NULL); + // release_object(partner); + // } + // else + pipe = create_named_pipe( get_req_data(), get_req_data_size() ); + if( pipe ) { - struct pipe_user *user; + /* only wait if the pipe already exists */ + if(get_error() == STATUS_OBJECT_NAME_COLLISION) + { + struct pipe_user *partner;
- if( (user = create_pipe_user( pipe )) ) + set_error(STATUS_SUCCESS); + if( (partner = find_partner(pipe,ps_wait_open)) ) + { + /* this should use notify_waiter, + but no pipe_user object exists now... */ + thread_queue_apc(current,NULL,req->func, + APC_ASYNC,1,2,req->overlapped,STATUS_SUCCESS); + release_object(partner); + } + else + { + struct pipe_user *user; + + if( (user = create_pipe_user (pipe, -1)) ) + { + user->state = ps_wait_only; + user->thread = (struct thread *)grab_object(current); + user->func = req->func; + user->overlapped = req->overlapped; + /* don't release it */ + } + } + } + else { - user->state = ps_wait_connect; - user->thread = (struct thread *)grab_object(current); - user->func = req->func; - user->overlapped = req->overlapped; - /* don't release it */ + set_error(STATUS_PIPE_NOT_AVAILABLE); } + release_object(pipe); } - release_object(pipe); }
DECL_HANDLER(disconnect_named_pipe) @@ -455,15 +812,17 @@ if( (user->state == ps_connected_server) && (user->other->state == ps_connected_client) ) { - release_object( user->other->fd ); - user->other->fd = NULL; user->other->state = ps_disconnected; - user->other->other = NULL; - - release_object( user->fd ); - user->fd = NULL; user->state = ps_idle_server; + + pipe_user_check_actions( user ); + pipe_user_check_actions( user->other ); + + user->other->other = NULL; user->other = NULL; + + user->pipe->instances --; + assert( user->pipe->instances >= 0 ); } release_object(user); } @@ -483,3 +842,109 @@
release_object(user); } + +DECL_HANDLER(read_named_pipe) +{ + struct pipe_user *user; + struct pipe_msg *msg; + + reply->id = 0; + + user = get_pipe_user_obj(current->process, req->handle, 0); + if(!user) + return; + + /* make sure we're connected */ + switch( user->state ) + { + case ps_connected_server: + case ps_connected_client: + case ps_wait_connect: + break; + case ps_disconnected: + set_error( STATUS_PIPE_DISCONNECTED ); + goto end; + case ps_wait_open: + set_error( STATUS_PIPE_LISTENING ); + goto end; + default: + set_error( STATUS_PIPE_NOT_AVAILABLE ); + goto end; + } + + /* find the message identified by id or a new one */ + if( req->id ) + msg = find_message( &user->out, req->id ); + else + msg = find_new_message( &user->out ); + + /* send a message, when finished, delete it */ + if( msg ) + { + if( pipe_get_message( msg, req->count ) ) + { + remove_message( &user->out, msg, 1 ); + pipe_user_check_actions( user ); + } + else + reply->id = (int) msg; + } + +end: + release_object(user); +} + +DECL_HANDLER(write_named_pipe) +{ + struct pipe_user *user; + struct pipe_msg *msg; + + reply->id = 0; + + user = get_pipe_user_obj(current->process, req->handle, 0); + if(!user) + return; + + /* make sure we're connected first */ + switch( user->state ) + { + case ps_connected_server: + case ps_connected_client: + case ps_wait_connect: + break; + case ps_disconnected: + set_error( STATUS_PIPE_DISCONNECTED ); + goto end; + case ps_wait_open: + set_error( STATUS_PIPE_LISTENING ); + goto end; + default: + set_error( STATUS_PIPE_NOT_AVAILABLE ); + goto end; + } + + /* find the message identified by id or a new one */ + if( req->id ) + msg = find_message( &user->in, req->id ); + else + msg = create_new_message( &user->in, req->count ); + + /* Cook a message. If it is ready, send it */ + if( pipe_build_msg( msg ) ) + { + if( user->other ) + { + remove_message( &user->in, msg, 0 ); + add_message( &user->other->out, msg ); + /* server should read, another thread may want to write */ + pipe_user_check_actions( user->other ); + pipe_user_check_actions( user ); + } + } + else + reply->id = (int) msg; + +end: + release_object(user); +} + diff -ur --minimal --exclude-from=/home/greg/bin/winetreediff_excl ../wine/server/request.h ./server/request.h --- ../wine/server/request.h 2003-03-26 17:41:43.000000000 -0600 +++ ./server/request.h 2003-04-09 18:57:15.000000000 -0500 @@ -239,6 +239,8 @@ DECL_HANDLER(create_named_pipe); DECL_HANDLER(open_named_pipe); DECL_HANDLER(connect_named_pipe); +DECL_HANDLER(read_named_pipe); +DECL_HANDLER(write_named_pipe); DECL_HANDLER(wait_named_pipe); DECL_HANDLER(disconnect_named_pipe); DECL_HANDLER(get_named_pipe_info); @@ -420,6 +422,8 @@ (req_handler)req_create_named_pipe, (req_handler)req_open_named_pipe, (req_handler)req_connect_named_pipe, + (req_handler)req_read_named_pipe, + (req_handler)req_write_named_pipe, (req_handler)req_wait_named_pipe, (req_handler)req_disconnect_named_pipe, (req_handler)req_get_named_pipe_info, diff -ur --minimal --exclude-from=/home/greg/bin/winetreediff_excl ../wine/server/trace.c ./server/trace.c --- ../wine/server/trace.c 2003-04-05 22:13:01.000000000 -0600 +++ ./server/trace.c 2003-04-09 18:57:15.000000000 -0500 @@ -893,7 +893,9 @@ fprintf( stderr, " links=%d,", req->links ); fprintf( stderr, " index_high=%d,", req->index_high ); fprintf( stderr, " index_low=%d,", req->index_low ); - fprintf( stderr, " serial=%08x", req->serial ); + fprintf( stderr, " serial=%08x,", req->serial ); + fprintf( stderr, " fd_type=%d,", req->fd_type ); + fprintf( stderr, " fd_flags=%d", req->fd_flags ); }
static void dump_lock_file_request( const struct lock_file_request *req ) @@ -1979,6 +1981,30 @@ fprintf( stderr, " func=%p", req->func ); }
+static void dump_read_named_pipe_request( const struct read_named_pipe_request *req ) +{ + fprintf( stderr, " handle=%p,", req->handle ); + fprintf( stderr, " id=%08x,", req->id ); + fprintf( stderr, " count=%d", req->count ); +} + +static void dump_read_named_pipe_reply( const struct read_named_pipe_reply *req ) +{ + fprintf( stderr, " id=%08x", req->id ); +} + +static void dump_write_named_pipe_request( const struct write_named_pipe_request *req ) +{ + fprintf( stderr, " handle=%p,", req->handle ); + fprintf( stderr, " id=%08x,", req->id ); + fprintf( stderr, " count=%d", req->count ); +} + +static void dump_write_named_pipe_reply( const struct write_named_pipe_reply *req ) +{ + fprintf( stderr, " id=%08x", req->id ); +} + static void dump_wait_named_pipe_request( const struct wait_named_pipe_request *req ) { fprintf( stderr, " timeout=%08x,", req->timeout ); @@ -2576,6 +2602,8 @@ (dump_func)dump_create_named_pipe_request, (dump_func)dump_open_named_pipe_request, (dump_func)dump_connect_named_pipe_request, + (dump_func)dump_read_named_pipe_request, + (dump_func)dump_write_named_pipe_request, (dump_func)dump_wait_named_pipe_request, (dump_func)dump_disconnect_named_pipe_request, (dump_func)dump_get_named_pipe_info_request, @@ -2754,6 +2782,8 @@ (dump_func)dump_create_named_pipe_reply, (dump_func)dump_open_named_pipe_reply, (dump_func)0, + (dump_func)dump_read_named_pipe_reply, + (dump_func)dump_write_named_pipe_reply, (dump_func)0, (dump_func)0, (dump_func)dump_get_named_pipe_info_reply, @@ -2932,6 +2962,8 @@ "create_named_pipe", "open_named_pipe", "connect_named_pipe", + "read_named_pipe", + "write_named_pipe", "wait_named_pipe", "disconnect_named_pipe", "get_named_pipe_info",