named pipe patch

Dan Kegel dank at kegel.com
Fri Apr 18 16:52:45 CDT 2003


Here's a draft of a patch that tries to fix two problems:
1. can't open a pipe until server has called ConnectNamedPipe
    (on Win32, pipes can be opened immediately after CreateNamedPipe)
2. client loses any data in pipe if server calls
    FlushFileBuffers and then immediately calls DisconnectNamedPipe.
    I work around this by keeping a reference to the server side
    fd in the client pipe user.  FlushFileBuffers still doesn't
    work, but we don't care; this is kind of a unixification of
    named pipe semantics.  It shifts our win32 noncompliance from
    a very touchy spot to a less touchy spot, I hope.

In both cases, I try to solve the problem by passing fd's
inside wineserver.  I have no idea if I'm doing this right,
but it does pass most of my single-process regression tests.
(Overlapped I/O is still broken, and I'm not sure if this
fd passing magic will work if the pipe ends are in different
processes.)

Interestingly, it seems that the DisconnectNamedPipe()/ConnectNamedPipe()
sequence as previously written would have failed if a
unix file descriptor was created or deleted
between pipe disconnection and reconnection.  This is because a
new fd is used for each pipe session, but the old fd number
was never removed from the handle-to-unix-fd cache upon
DisconnectNamedPipe.  My workaround for the data loss problem
exposed this issue.  I "fixed" it by invalidating the cached fd for
the handle passed to DisconnectNamedPipe.  (If there are
any other handles to this object, their fds won't be invalidated.
Shouldn't matter in practice...)

This patch also includes an improved test for named pipes.

I'm posting it here rather than to wine-patches to get feedback
on whether the fd passing I'm doing is sane.

Thanks,
Dan

-- 
Dan Kegel
http://www.kegel.com
http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=78045
-------------- next part --------------
Index: server/object.c
===================================================================
RCS file: /home/wine/wine/server/object.c,v
retrieving revision 1.27
diff -d -u -r1.27 object.c
--- server/object.c	19 Feb 2003 00:33:33 -0000	1.27
+++ server/object.c	18 Apr 2003 17:41:17 -0000
@@ -193,6 +193,7 @@
 struct object *grab_object( void *ptr )
 {
     struct object *obj = (struct object *)ptr;
+    assert( obj );
     assert( obj->refcount < INT_MAX );
     obj->refcount++;
     return obj;
Index: server/file.h
===================================================================
RCS file: /home/wine/wine/server/file.h,v
retrieving revision 1.7
diff -d -u -r1.7 file.h
--- server/file.h	26 Mar 2003 23:41:43 -0000	1.7
+++ server/file.h	18 Apr 2003 17:41:17 -0000
@@ -49,6 +49,7 @@
 extern struct fd *create_anonymous_fd( const struct fd_ops *fd_user_ops,
                                        int unix_fd, struct object *user );
 extern void *get_fd_user( struct fd *fd );
+extern void set_fd_user( struct fd *fd, struct object *user );
 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 );
Index: server/fd.c
===================================================================
RCS file: /home/wine/wine/server/fd.c,v
retrieving revision 1.8
diff -d -u -r1.8 fd.c
--- server/fd.c	26 Mar 2003 01:32:18 -0000	1.8
+++ server/fd.c	18 Apr 2003 17:41:18 -0000
@@ -884,6 +884,12 @@
     return fd->user;
 }
 
+/* change the object that is using an fd */
+void set_fd_user( struct fd *fd, struct object *user )
+{
+    fd->user = user;
+}
+
 /* retrieve the unix fd for an object */
 int get_unix_fd( struct fd *fd )
 {
@@ -993,15 +999,22 @@
     return fd;
 }
 
-/* flush a file buffers */
+/* flush a file's buffers */
 DECL_HANDLER(flush_file)
 {
-    struct fd *fd = get_handle_fd_obj( current->process, req->handle, 0 );
-
-    if (fd)
-    {
-        fd->fd_ops->flush( fd );
-        release_object( fd );
+    struct object *obj = get_handle_obj(current->process, req->handle, 0, NULL);
+    if (!obj) {
+        set_error( STATUS_INVALID_HANDLE );
+    } else {
+        struct fd *fd = get_obj_fd(obj);
+        /* It's ok for named pipes, and possibly other kinds of objects,
+	 * to not have fds associated sometimes, so do nothing if no fd.
+	 */
+        if (fd) {
+	    fd->fd_ops->flush( fd );
+	    release_object( fd );
+	}
+        release_object( obj );
     }
 }
 
Index: server/handle.h
===================================================================
RCS file: /home/wine/wine/server/handle.h,v
retrieving revision 1.12
diff -d -u -r1.12 handle.h
--- server/handle.h	14 Feb 2003 20:27:10 -0000	1.12
+++ server/handle.h	18 Apr 2003 17:41:18 -0000
@@ -39,6 +39,7 @@
 extern struct object *get_handle_obj( struct process *process, obj_handle_t handle,
                                       unsigned int access, const struct object_ops *ops );
 extern int get_handle_unix_fd( struct process *process, obj_handle_t handle, unsigned int access );
+extern void invalidate_handle_unix_fd( struct process *process, obj_handle_t handle );
 extern int set_handle_info( struct process *process, obj_handle_t handle, int mask, int flags, int *fd );
 extern obj_handle_t duplicate_handle( struct process *src, obj_handle_t src_handle, struct process *dst,
                                   unsigned int access, int inherit, int options );
Index: server/handle.c
===================================================================
RCS file: /home/wine/wine/server/handle.c,v
retrieving revision 1.26
diff -d -u -r1.26 handle.c
--- server/handle.c	19 Feb 2003 00:33:33 -0000	1.26
+++ server/handle.c	18 Apr 2003 17:41:19 -0000
@@ -397,6 +397,17 @@
     return entry->fd;
 }
 
+/* invalidate the cached fd for a given handle */
+void invalidate_handle_unix_fd( struct process *process, obj_handle_t handle )
+{
+    struct handle_entry *entry;
+
+    entry = get_handle( process, handle );
+    /*fprintf(stderr, "invalidate_handle_unix_fd: handle %d, clearing client unix process fd %d\n", (int)handle, entry->fd);*/
+    if (entry)
+	entry->fd = -1;
+}
+
 /* find the first inherited handle of the given type */
 /* this is needed for window stations and desktops (don't ask...) */
 obj_handle_t find_inherited_handle( struct process *process, const struct object_ops *ops )
Index: server/named_pipe.c
===================================================================
RCS file: /home/wine/wine/server/named_pipe.c,v
retrieving revision 1.22
diff -d -u -r1.22 named_pipe.c
--- server/named_pipe.c	4 Apr 2003 22:26:34 -0000	1.22
+++ server/named_pipe.c	18 Apr 2003 17:41:19 -0000
@@ -48,12 +48,13 @@
 enum pipe_state
 {
     ps_none,
-    ps_idle_server,
-    ps_wait_open,
-    ps_wait_connect,
+    ps_fresh_server,		/* server after create, before first connect */
+    ps_idle_server,		/* server after disconnect */
+    ps_wait_open,		/* server during ConnectNamedPipe() wait */
+    ps_wait_connect,		/* client during WaitNamedPipe() */
     ps_connected_server,
     ps_connected_client,
-    ps_disconnected
+    ps_disconnected,		/* client after disconnect */
 };
 
 struct named_pipe;
@@ -62,6 +63,7 @@
 {
     struct object       obj;
     struct fd          *fd;
+    struct fd          *fd_for_peer;
     enum pipe_state     state;
     struct pipe_user   *other;
     struct named_pipe  *pipe;
@@ -103,6 +105,7 @@
 static void pipe_user_destroy( struct object *obj);
 
 static int pipe_user_get_poll_events( struct fd *fd );
+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 const struct object_ops pipe_user_ops =
@@ -121,7 +124,7 @@
 {
     pipe_user_get_poll_events,    /* get_poll_events */
     default_poll_event,           /* poll_event */
-    no_flush,                     /* flush */
+    pipe_user_flush,              /* flush */
     pipe_user_get_info,           /* get_file_info */
     no_queue_async                /* queue_async */
 };
@@ -163,6 +166,8 @@
 static struct fd *pipe_user_get_fd( struct object *obj )
 {
     struct pipe_user *user = (struct pipe_user *)obj;
+    if (!user->fd) 
+	return NULL;
     return (struct fd *)grab_object( user->fd );
 }
 
@@ -177,15 +182,20 @@
 
     if(user->other)
     {
-        release_object( user->other->fd );
-        user->other->fd = NULL;
         switch(user->other->state)
         {
         case ps_connected_server:
             user->other->state = ps_idle_server;
+	    release_object( user->other->fd );
+	    user->other->fd = NULL;
             break;
         case ps_connected_client:
             user->other->state = ps_disconnected;
+	    if (user->fd) {
+		user->other->fd_for_peer = user->fd;
+		set_fd_user(user->fd, &user->other->obj);
+		user->fd = NULL;
+	    }
             break;
         default:
             fprintf(stderr,"connected pipe has strange state %d!\n",
@@ -202,6 +212,7 @@
     if (user->thread) release_object(user->thread);
     release_object(user->pipe);
     if (user->fd) release_object( user->fd );
+    if (user->fd_for_peer) release_object(user->fd_for_peer);
 }
 
 static int pipe_user_get_poll_events( struct fd *fd )
@@ -209,6 +220,14 @@
     return POLLIN | POLLOUT;  /* FIXME */
 }
 
+static int pipe_user_flush( struct fd *fd )
+{
+    /* Pretend we work.  We don't, but the fact that we don't
+     * destroy contents on disconnect makes up for it in most cases.
+     */
+    return 0;
+}
+
 static int pipe_user_get_info( struct fd *fd, struct get_file_info_reply *reply, int *flags )
 {
     if (reply)
@@ -273,6 +292,7 @@
         return NULL;
 
     user->fd = NULL;
+    user->fd_for_peer = NULL;
     user->pipe = pipe;
     user->state = ps_none;
     user->other = NULL;
@@ -290,13 +310,14 @@
     return user;
 }
 
-static struct pipe_user *find_partner(struct named_pipe *pipe, enum pipe_state state)
+/* Find a pipe_user in any of the given states */
+static struct pipe_user *_find_partner(struct named_pipe *pipe, int states)
 {
     struct pipe_user *x;
 
     for(x = pipe->users; x; x=x->next)
     {
-        if(x->state==state)
+        if((1 << x->state) & states)
         break;
     }
 
@@ -306,6 +327,12 @@
     return (struct pipe_user *)grab_object( x );
 }
 
+/* Find a pipe_user in the given state */
+static inline struct pipe_user *find_partner(struct named_pipe *pipe, enum pipe_state s)
+{
+	return _find_partner(pipe, 1<<s);
+}
+
 DECL_HANDLER(create_named_pipe)
 {
     struct named_pipe *pipe;
@@ -329,7 +356,14 @@
 
     if(user)
     {
-        user->state = ps_idle_server;
+        int fds[2];
+        user->state = ps_fresh_server;
+        if (!socketpair(PF_UNIX, SOCK_STREAM, 0, fds))
+        {
+	    user->fd = create_anonymous_fd( &pipe_user_fd_ops, fds[1], &user->obj );
+	    user->fd_for_peer = create_anonymous_fd( &pipe_user_fd_ops, fds[0], &user->obj );
+	    /* fprintf(stderr, "create_named_pipe: user %p, created fd %p, created fd_for_peer %p\n", user, user->fd, user->fd_for_peer); */
+        }
         reply->handle = alloc_handle( current->process, user, GENERIC_READ|GENERIC_WRITE, 0 );
         release_object( user );
     }
@@ -349,7 +383,7 @@
         set_error( STATUS_NO_SUCH_FILE );
         return;
     }
-    if (!(partner = find_partner(pipe, ps_wait_open)))
+    if (!(partner = _find_partner(pipe, (1<<ps_wait_open) | (1<<ps_fresh_server))))
     {
         release_object(pipe);
         set_error( STATUS_PIPE_NOT_AVAILABLE );
@@ -359,22 +393,36 @@
     {
         int fds[2];
 
-        if(!socketpair(PF_UNIX, SOCK_STREAM, 0, fds))
-        {
-            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)
-            {
-                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 );
-            }
-        }
-        else file_set_error();
+        if (partner->fd_for_peer != NULL) {
+	    /* Server side is fresh, so it already created the fds.
+	     * Grab the fd it created for us, and set its user (since
+	     * it couldn't know ahead of time).
+	     */
+            user->fd = partner->fd_for_peer;
+            partner->fd_for_peer = NULL;
+	    set_fd_user(user->fd, &user->obj);
+	    /* fprintf(stderr, "open_named_pipe: user %p grabbing fd %p from partner %p\n", user, user->fd, partner); */
+        } else if (!socketpair(PF_UNIX, SOCK_STREAM, 0, fds)) {
+	    /* Server side isn't fresh, so we have to create the fds for 
+	     * both sides.
+	     */
+	    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 );
+	    /* fprintf(stderr, "open_named_pipe: user %p, created fd %p, created fd %p for partner %p\n", user, user->fd, partner->fd, partner); */
+	} else
+	    file_set_error();
 
+	if (user->fd && partner->fd)
+	{
+	    enum pipe_state old_partner_state = partner->state;
+	    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 );
+	    if (old_partner_state == ps_wait_open)
+		notify_waiter(partner,STATUS_SUCCESS);
+	}
         release_object( user );
     }
     release_object( partner );
@@ -389,7 +437,7 @@
     if(!user)
         return;
 
-    if( user->state != ps_idle_server )
+    if( (user->state & (ps_idle_server|ps_fresh_server)) == 0 )
     {
         set_error(STATUS_PORT_ALREADY_SET);
     }
@@ -455,16 +503,28 @@
     if( (user->state == ps_connected_server) &&
         (user->other->state == ps_connected_client) )
     {
-        release_object( user->other->fd );
+	/* This duplicates a fair bit of code from the destroy method */
+	if (user->fd) {
+	    /* fprintf(stderr, "disconnect_named_pipe: user %p passing fd %p to partner %p\n", user, user->fd, user->other); */
+	    user->other->fd_for_peer = user->fd;
+	    set_fd_user(user->fd, &user->other->obj);
+	    user->fd = NULL;
+	}
         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;
         user->other = NULL;
+        user->state = ps_idle_server;
     }
+
+    /* IMPOSSIBLE PART: invalidate the cached unix fd for all
+     * handles that refer to user.
+     * We can only do it for the handle we were passed,
+     * so this will only work if that handle has no duplicate.
+     * FIXME: find out what happens to a handle in win32 when you
+     * disconnect a duplicate of it.
+     */
+    invalidate_handle_unix_fd(current->process, req->handle);
     release_object(user);
 }
 
Index: dlls/kernel/tests/pipe.c
===================================================================
RCS file: /home/wine/wine/dlls/kernel/tests/pipe.c,v
retrieving revision 1.2
diff -d -u -r1.2 pipe.c
--- dlls/kernel/tests/pipe.c	25 Feb 2003 03:56:43 -0000	1.2
+++ dlls/kernel/tests/pipe.c	18 Apr 2003 17:41:20 -0000
@@ -21,23 +21,40 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <time.h>
+#include <assert.h>
+
+#include <wtypes.h>
+#include <windef.h>
+#include <winbase.h>
+#include <winerror.h>
 
 #ifndef STANDALONE
 #include "wine/test.h"
+#define trace2(a,b) trace((a),(b))
 #else
 #include <assert.h>
 #define START_TEST(name) main(int argc, char **argv)
 #define ok(condition, msg) assert(condition)
-#define todo_wine
+#define todo_wine if (0)
+static void trace2(const char *s, const char *t)
+{
+    DWORD cbWritten;
+    char buf[512];
+    sprintf(buf, s, t);
+    WriteFile(GetStdHandle(STD_ERROR_HANDLE), buf, strlen(buf), &cbWritten, NULL);
+}
+static void trace(const char *s)
+{
+    DWORD cbWritten;
+    char buf[512];
+    sprintf(buf, "%s", s);
+    WriteFile(GetStdHandle(STD_ERROR_HANDLE), buf, strlen(buf), &cbWritten, NULL);
+}
 #endif
 
-#include <wtypes.h>
-#include <windef.h>
-#include <winbase.h>
-#include <winerror.h>
-
 #define PIPENAME "\\\\.\\PiPe\\tests_" __FILE__
 
+
 void test_CreateNamedPipeA(void)
 {
     HANDLE hnp;
@@ -45,8 +62,9 @@
     const char obuf[] = "Bit Bucket";
     char ibuf[32];
     DWORD written;
-    DWORD gelesen;
+    DWORD nbytes_read;
 
+    trace("test_CreateNamedPipe starting\n");
     /* Bad parameter checks */
     hnp = CreateNamedPipeA("not a named pipe", 
         PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_WAIT, 
@@ -86,10 +104,8 @@
         /* lpSecurityAttrib */ NULL);
     ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
 
-    hFile = CreateFileA(PIPENAME, GENERIC_READ|GENERIC_WRITE, 0, 
-            NULL, OPEN_EXISTING, 0, 0);
-    todo_wine
-    {
+    hFile = CreateFileA(PIPENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);
+    todo_wine {
         ok(hFile != INVALID_HANDLE_VALUE, "CreateFile failed");
     }
 
@@ -101,15 +117,15 @@
 	memset(ibuf, 0, sizeof(ibuf));
 	ok(WriteFile(hnp, obuf, sizeof(obuf), &written, NULL), "WriteFile");
 	ok(written == sizeof(obuf), "write file len");
-	ok(ReadFile(hFile, ibuf, sizeof(obuf), &gelesen, NULL), "ReadFile");
-	ok(gelesen == sizeof(obuf), "read file len");
+	ok(ReadFile(hFile, ibuf, sizeof(obuf), &nbytes_read, NULL), "ReadFile");
+	ok(nbytes_read == sizeof(obuf), "read file len");
 	ok(memcmp(obuf, ibuf, written) == 0, "content check");
 
 	memset(ibuf, 0, sizeof(ibuf));
 	ok(WriteFile(hFile, obuf, sizeof(obuf), &written, NULL), "WriteFile");
 	ok(written == sizeof(obuf), "write file len");
-	ok(ReadFile(hnp, ibuf, sizeof(obuf), &gelesen, NULL), "ReadFile");
-	ok(gelesen == sizeof(obuf), "read file len");
+	ok(ReadFile(hnp, ibuf, sizeof(obuf), &nbytes_read, NULL), "ReadFile");
+	ok(nbytes_read == sizeof(obuf), "read file len");
 	ok(memcmp(obuf, ibuf, written) == 0, "content check");
 
 	/* Picky conformance tests */
@@ -148,9 +164,460 @@
     }
 
     ok(CloseHandle(hnp), "CloseHandle");
+
+    trace("test_CreateNamedPipe returning\n");
+}
+
+void test_CreateNamedPipe_instances_must_match(void)
+{
+    HANDLE hnp, hnp2;
+
+    /* Check no mismatch */
+    hnp = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 2,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+    ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+    hnp2 = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 2,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+    ok(hnp2 != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+    ok(CloseHandle(hnp), "CloseHandle");
+    ok(CloseHandle(hnp2), "CloseHandle");
+
+    /* Check nMaxInstances */
+    hnp = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 1,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+    ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+    todo_wine {
+        hnp2 = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT,
+            /* nMaxInstances */ 1,
+            /* nOutBufSize */ 1024,
+            /* nInBufSize */ 1024,
+            /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+            /* lpSecurityAttrib */ NULL);
+        ok(hnp2 == INVALID_HANDLE_VALUE
+            && GetLastError() == ERROR_PIPE_BUSY, "nMaxInstances not obeyed");
+    }
+
+    ok(CloseHandle(hnp), "CloseHandle");
+
+    /* Check PIPE_ACCESS_* */
+    hnp = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 2,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+    ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+    todo_wine {
+        hnp2 = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_WAIT,
+            /* nMaxInstances */ 1,
+            /* nOutBufSize */ 1024,
+            /* nInBufSize */ 1024,
+            /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+            /* lpSecurityAttrib */ NULL);
+        ok(hnp2 == INVALID_HANDLE_VALUE
+            && GetLastError() == ERROR_ACCESS_DENIED, "PIPE_ACCESS_* mismatch allowed");
+    }
+
+    /* etc, etc */
+}
+
+/** implementation of alarm() */
+static DWORD CALLBACK alarmThreadMain(LPVOID arg)
+{
+    DWORD timeout = (DWORD) arg;
+    trace("alarmThreadMain\n");
+    Sleep(timeout);
+    ok(FALSE, "alarm");
+    ExitProcess(1);
+    return 1;
+}
+
+HANDLE hnp = INVALID_HANDLE_VALUE;
+
+/** Trivial byte echo server - disconnects after each session */
+static DWORD CALLBACK serverThreadMain1(LPVOID arg)
+{
+    int i;
+
+    trace("serverThreadMain1 start\n");
+    /* Set up a simple echo server */
+    hnp = CreateNamedPipeA(PIPENAME "serverThreadMain1", PIPE_ACCESS_DUPLEX,
+        PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 1,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+
+    ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+    for (i = 0; ; i++) {
+        char buf[512];
+        DWORD written;
+        DWORD nbytes_read;
+        DWORD success;
+
+        /* Wait for client to connect */
+        trace("Server calling ConnectNamedPipe...\n");
+        ok(ConnectNamedPipe(hnp, NULL)
+            || GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe");
+        trace("ConnectNamedPipe returned.\n");
+
+        /* Echo bytes once */
+        memset(buf, 0, sizeof(buf));
+
+        trace("Server reading...\n");
+        success = ReadFile(hnp, buf, sizeof(buf), &nbytes_read, NULL);
+        trace("Server done reading.\n");
+        ok(success, "ReadFile");
+	trace2("Server read '%s'\n", buf);
+        ok(memcmp(buf, "client->server", 14) == 0, "content check");
+
+        trace("Server writing...\n");
+	sprintf(buf, "server->client i %d  ", i);
+        ok(WriteFile(hnp, buf, nbytes_read, &written, NULL), "WriteFile");
+        trace("Server done writing.\n");
+        ok(written == nbytes_read, "write file len");
+
+        /* finish this connection, wait for next one */
+        trace("Server flushing.\n");
+	todo_wine {
+		ok(FlushFileBuffers(hnp), "FlushFileBuffers");
+	}
+        trace("Server disconnecting.\n");
+        ok(DisconnectNamedPipe(hnp), "DisconnectNamedPipe");
+    }
+}
+
+/** Trivial byte echo server - closes after each connection */
+static DWORD CALLBACK serverThreadMain2(LPVOID arg)
+{
+    int i;
+    HANDLE hnpNext = 0;
+
+    trace("serverThreadMain2\n");
+    /* Set up a simple echo server */
+    hnp = CreateNamedPipeA(PIPENAME "serverThreadMain2", PIPE_ACCESS_DUPLEX,
+        PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 2,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+    ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+    for (i = 0; ; i++) {
+        char buf[512];
+        DWORD written;
+        DWORD nbytes_read;
+        DWORD success;
+
+        /* Wait for client to connect */
+        trace("Server calling ConnectNamedPipe...\n");
+        ok(ConnectNamedPipe(hnp, NULL)
+            || GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe");
+        trace("ConnectNamedPipe returned.\n");
+
+        /* Echo bytes once */
+        memset(buf, 0, sizeof(buf));
+
+        trace("Server reading...\n");
+        success = ReadFile(hnp, buf, sizeof(buf), &nbytes_read, NULL);
+        trace("Server done reading.\n");
+        ok(success, "ReadFile");
+
+        trace("Server writing...\n");
+        ok(WriteFile(hnp, buf, nbytes_read, &written, NULL), "WriteFile");
+        trace("Server done writing.\n");
+        ok(written == nbytes_read, "write file len");
+
+        /* finish this connection, wait for next one */
+        trace("Server flushing.\n");
+	todo_wine { ok( FlushFileBuffers(hnp), "FlushFileBuffers"); } 
+        trace("Server disconnecting.\n");
+        ok(DisconnectNamedPipe(hnp), "DisconnectNamedPipe");
+
+        /* Set up next echo server */
+        hnpNext =
+            CreateNamedPipeA(PIPENAME "serverThreadMain2", PIPE_ACCESS_DUPLEX,
+            PIPE_TYPE_BYTE | PIPE_WAIT,
+            /* nMaxInstances */ 2,
+            /* nOutBufSize */ 1024,
+            /* nInBufSize */ 1024,
+            /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+            /* lpSecurityAttrib */ NULL);
+
+        ok(hnpNext != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+        ok(CloseHandle(hnp), "CloseHandle");
+        hnp = hnpNext;
+    }
+}
+
+/** Trivial byte echo server - uses overlapped named pipe calls */
+static DWORD CALLBACK serverThreadMain3(LPVOID arg)
+{
+    int i;
+    HANDLE hEvent;
+
+    trace("serverThreadMain3\n");
+    /* Set up a simple echo server */
+    hnp = CreateNamedPipeA(PIPENAME "serverThreadMain3", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+        PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 1,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+    ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+    hEvent = CreateEvent(NULL,  // security attribute
+        TRUE,                   // manual reset event 
+        FALSE,                  // initial state 
+        NULL);                  // name
+    ok(hEvent != NULL, "CreateEvent");
+
+    for (i = 0; ; i++) {
+        char buf[512];
+        DWORD written;
+        DWORD nbytes_read;
+        DWORD dummy;
+        DWORD success;
+        OVERLAPPED oOverlap;
+        int letWFSOEwait = (i & 2);
+        int letGORwait = (i & 1);
+        DWORD err;
+
+        memset(&oOverlap, 0, sizeof(oOverlap));
+        oOverlap.hEvent = hEvent;
+
+        /* Wait for client to connect */
+        trace("Server calling overlapped ConnectNamedPipe...\n");
+        success = ConnectNamedPipe(hnp, &oOverlap);
+        err = GetLastError();
+        ok(success || err == ERROR_IO_PENDING
+            || err == ERROR_PIPE_CONNECTED, "overlapped ConnectNamedPipe");
+        trace("overlapped ConnectNamedPipe returned.\n");
+        if (!success && (err == ERROR_IO_PENDING) && letWFSOEwait)
+            ok(WaitForSingleObjectEx(hEvent, INFINITE, TRUE) == 0, "wait ConnectNamedPipe");
+        success = GetOverlappedResult(hnp, &oOverlap, &dummy, letGORwait);
+        if (!letGORwait && !letWFSOEwait && !success) {
+            ok(GetLastError() == ERROR_IO_INCOMPLETE, "GetOverlappedResult");
+            success = GetOverlappedResult(hnp, &oOverlap, &dummy, TRUE);
+        }
+        ok(success, "GetOverlappedResult ConnectNamedPipe");
+        trace("overlapped ConnectNamedPipe operation complete.\n");
+
+        /* Echo bytes once */
+        memset(buf, 0, sizeof(buf));
+
+        trace("Server reading...\n");
+        success = ReadFile(hnp, buf, sizeof(buf), NULL, &oOverlap);
+        trace("Server ReadFile returned...\n");
+        err = GetLastError();
+        ok(success || err == ERROR_IO_PENDING, "overlapped ReadFile");
+        trace("overlapped ReadFile returned.\n");
+        if (!success && (err == ERROR_IO_PENDING) && letWFSOEwait)
+            ok(WaitForSingleObjectEx(hEvent, INFINITE, TRUE) == 0, "wait ReadFile");
+        success = GetOverlappedResult(hnp, &oOverlap, &nbytes_read, letGORwait);
+        if (!letGORwait && !letWFSOEwait && !success) {
+            ok(GetLastError() == ERROR_IO_INCOMPLETE, "GetOverlappedResult");
+            success = GetOverlappedResult(hnp, &oOverlap, &nbytes_read, TRUE);
+        }
+        trace("Server done reading.\n");
+        ok(success, "overlapped ReadFile");
+
+        trace("Server writing...\n");
+        success = WriteFile(hnp, buf, nbytes_read, NULL, &oOverlap);
+        trace("Server WriteFile returned...\n");
+        err = GetLastError();
+        ok(success || err == ERROR_IO_PENDING, "overlapped WriteFile");
+        trace("overlapped WriteFile returned.\n");
+        if (!success && (err == ERROR_IO_PENDING) && letWFSOEwait)
+            ok(WaitForSingleObjectEx(hEvent, INFINITE, TRUE) == 0, "wait WriteFile");
+        success = GetOverlappedResult(hnp, &oOverlap, &written, letGORwait);
+        if (!letGORwait && !letWFSOEwait && !success) {
+            ok(GetLastError() == ERROR_IO_INCOMPLETE, "GetOverlappedResult");
+            success = GetOverlappedResult(hnp, &oOverlap, &written, TRUE);
+        }
+        trace("Server done writing.\n");
+        ok(success, "overlapped WriteFile");
+        ok(written == nbytes_read, "write file len");
+
+        /* finish this connection, wait for next one */
+        trace("Server flushing.\n");
+	todo_wine { ok( FlushFileBuffers(hnp), "FlushFileBuffers"); } 
+        trace("Server disconnecting.\n");
+        ok(DisconnectNamedPipe(hnp), "DisconnectNamedPipe");
+    }
+}
+
+static void exercizeServer(const char *pipename, HANDLE serverThread)
+{
+    int i;
+    DWORD success;
+
+    trace("exercizeServer starting\n");
+    for (i = 0; i < 8; i++) {
+        HANDLE hFile;
+        char obuf[32];
+        char ibuf[32];
+        DWORD written;
+        DWORD nbytes_read;
+        int loop;
+
+        for (loop = 0; loop < 3; loop++) {
+            DWORD err;
+            trace("Client connecting...\n");
+            /* Connect to the server */
+            hFile = CreateFileA(pipename, GENERIC_READ | GENERIC_WRITE, 0,
+                NULL, OPEN_EXISTING, 0, 0);
+            if (hFile != INVALID_HANDLE_VALUE)
+                break;
+            err = GetLastError();
+            if (loop == 0)
+                ok(err == ERROR_PIPE_BUSY || err == ERROR_FILE_NOT_FOUND, "connecting to pipe");
+            else
+                ok(err == ERROR_PIPE_BUSY, "connecting to pipe");
+            trace("connect failed, retrying\n");
+            Sleep(200);
+        }
+        ok(hFile != INVALID_HANDLE_VALUE, "client opening named pipe");
+
+        /* Make sure it can echo */
+        memset(ibuf, 0, sizeof(ibuf));
+	sprintf(obuf, "client->server i %d  ", i);
+	trace2("Client writing '%s'...\n", obuf);
+        ok(WriteFile(hFile, obuf, sizeof(obuf), &written, NULL), "WriteFile to client end of pipe");
+        ok(written == sizeof(obuf), "write file len");
+        trace("Client reading...\n");
+        success = ReadFile(hFile, ibuf, sizeof(ibuf), &nbytes_read, NULL);
+	if (!success)
+		printf("ReadFile(%p, %p, %d,,0) failed, err %ld\n", hFile, ibuf, sizeof(ibuf), GetLastError());
+        trace2("Client read '%s'\n", ibuf);
+	ok(success, "ReadFile from client end of pipe");
+        ok(nbytes_read == sizeof(obuf), "read file len");
+        /*ok(memcmp(obuf, ibuf, written) == 0, "content check");*/
+
+        trace("Client closing...\n");
+        ok(CloseHandle(hFile), "CloseHandle");
+    }
+
+    ok(TerminateThread(serverThread, 0), "TerminateThread");
+    CloseHandle(hnp);
+    trace("exercizeServer returning\n");
+}
+
+void test_NamedPipe_2(void)
+{
+    HANDLE serverThread;
+    DWORD serverThreadId;
+    HANDLE alarmThread;
+    DWORD alarmThreadId;
+
+    trace("test_NamedPipe_2 starting\n");
+    /* Set up a ten second timeout */
+    alarmThread = CreateThread(NULL, 0, alarmThreadMain, (void *) 10000, 0, &alarmThreadId);
+
+    /* The servers we're about to exercize do try to clean up carefully,
+     * but to reduce the change of a test failure due to a pipe handle
+     * leak in the test code, we'll use a different pipe name for each server.
+     */
+
+    trace("Try server #1\n");
+    serverThread = CreateThread(NULL, 0, serverThreadMain1, 0, 0, &serverThreadId);
+    ok(serverThread != INVALID_HANDLE_VALUE, "CreateThread");
+    exercizeServer(PIPENAME "serverThreadMain1", serverThread);
+
+    trace("Try server #2\n");
+    serverThread = CreateThread(NULL, 0, serverThreadMain2, 0, 0, &serverThreadId);
+    ok(serverThread != INVALID_HANDLE_VALUE, "CreateThread");
+    exercizeServer(PIPENAME "serverThreadMain2", serverThread);
+
+    todo_wine {
+	trace("Try server #3\n");
+	serverThread = CreateThread(NULL, 0, serverThreadMain3, 0, 0, &serverThreadId);
+	ok(serverThread != INVALID_HANDLE_VALUE, "CreateThread");
+	exercizeServer(PIPENAME "serverThreadMain3", serverThread);
+
+	ok(TerminateThread(alarmThread, 0), "TerminateThread");
+	trace("test_NamedPipe_2 returning\n");
+    }
+}
+
+void test_DisconnectNamedPipe(void)
+{
+    HANDLE hnp;
+    HANDLE hFile;
+    const char obuf[] = "Bit Bucket";
+    char ibuf[32];
+    DWORD written;
+    DWORD nbytes_read;
+
+    hnp = CreateNamedPipeA(PIPENAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT,
+        /* nMaxInstances */ 1,
+        /* nOutBufSize */ 1024,
+        /* nInBufSize */ 1024,
+        /* nDefaultWait */ NMPWAIT_USE_DEFAULT_WAIT,
+        /* lpSecurityAttrib */ NULL);
+    ok(hnp != INVALID_HANDLE_VALUE, "CreateNamedPipe failed");
+
+    ok(WriteFile(hnp, obuf, sizeof(obuf), &written, NULL) == 0
+        && GetLastError() == ERROR_PIPE_LISTENING, "WriteFile to not-yet-connected pipe");
+    ok(ReadFile(hnp, ibuf, sizeof(ibuf), &nbytes_read, NULL) == 0
+        && GetLastError() == ERROR_PIPE_LISTENING, "ReadFile from not-yet-connected pipe");
+
+    hFile = CreateFileA(PIPENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);
+    todo_wine {
+        ok(hFile != INVALID_HANDLE_VALUE, "CreateFile failed");
+    }
+
+    /* don't try to do i/o if one side couldn't be opened, as it hangs */
+    if (hFile != INVALID_HANDLE_VALUE) {
+
+        /* see what happens if server calls DisconnectNamedPipe
+         * when there are bytes in the pipe
+         */
+
+        ok(WriteFile(hFile, obuf, sizeof(obuf), &written, NULL), "WriteFile");
+        ok(written == sizeof(obuf), "write file len");
+        ok(DisconnectNamedPipe(hnp), "DisconnectNamedPipe while messages waiting");
+        ok(WriteFile(hFile, obuf, sizeof(obuf), &written, NULL) == 0
+            && GetLastError() == ERROR_PIPE_NOT_CONNECTED, "WriteFile to disconnected pipe");
+        ok(ReadFile(hnp, ibuf, sizeof(ibuf), &nbytes_read, NULL) == 0
+            && GetLastError() == ERROR_PIPE_NOT_CONNECTED,
+            "ReadFile from disconnected pipe with bytes waiting");
+        ok(CloseHandle(hFile), "CloseHandle");
+    }
+
+    ok(CloseHandle(hnp), "CloseHandle");
+
 }
 
 START_TEST(pipe)
 {
+    trace("test 1 of 4:\n");
     test_CreateNamedPipeA();
+    trace("test 2 of 4:\n");
+    test_CreateNamedPipe_instances_must_match();
+    trace("test 3 of 4:\n");
+    test_NamedPipe_2();
+    trace("test 4 of 4:\n");
+    test_DisconnectNamedPipe();
+    trace("all tests done\n");
 }


More information about the wine-devel mailing list