Hi,
I noticed a handful of differences in the "lifecycle" of named pipes between Windows and Wine, and I figured I'd post about them here. I wrote a standalone one-file C++ program that demonstrates the differences I found. (In that test, the pipe handles are always overlapped and PIPE_WAIT.) I tested using wine-1.9.12.
The differences were:
1. With Windows, there can be multiple concurrent ConnectNamedPipe operations on a pipe. With Wine, the second ConnectNamedPipe call fails with ERROR_INVALID_HANDLE.
2. With Windows, DisconnectNamedPipe aborts ConnectNamedPipe operations with the error ERROR_PIPE_NOT_CONNECTED. The DisconnectNamedPipe call succeeds, and the pipe is no longer listening. With Wine, the DisconnectNamedPipe call itself fails with ERROR_PIPE_LISTENING, and the pipe is still listening.
3. With Windows, if the server breaks the pipe by closing its handle, the client can finish reading the remaining data in the pipe. Once the data is gone, reading from a broken pipe fails with ERROR_BROKEN_PIPE. Writing to a broken pipe fails with ERROR_NO_DATA. With Wine, once the server breaks the pipe, the client cannot read the remaining data, and I/O on a broken pipe fails with ERROR_PIPE_NOT_CONNECTED. (Exception: ReadFile on a server pipe still returns ERROR_BROKEN_PIPE.)
4. Calling ConnectNamedPipe on a broken server pipe fails with ERROR_NO_DATA on Windows, but ERROR_NO_DATA_DETECTED on Wine.
My test program is at:
https://gist.github.com/rprichard/8dd8ca134b39534b7da2733994aa07ba. It passes on XP, Vista, Win7, and Win10, with the exception of testChangeMaxInstancesAfterClosingLastServerHandle, which passes on Vista and up. A couple of the more interesting tests are testDisconnectAtStartup and testReadFromBrokenClientPipe:
testDisconnectAtStartup:
// Calling DisconnectNamedPipe on a new pipe successfully transitions it to a
// "Not Connected" state. The second DisconnectNamedPipe call fails, and the
// client can't connect.
void testDisconnectAtStartup() {
const HANDLE server = assertValid(openServer(name(0)));
CHECK_SUCCESS(DisconnectNamedPipe(server));
CHECK(createFile(name(0)) == INVALID_HANDLE_VALUE);
CHECK_FAILURE(DisconnectNamedPipe(server), ERROR_PIPE_NOT_CONNECTED);
CHECK(createFile(name(0)) == INVALID_HANDLE_VALUE);
CHECK_SUCCESS(CloseHandle(server));
}
// If a client pipe is broken, rather than disconnected, the remaining data can
// still be read.
void testReadFromBrokenClientPipe() {
DWORD actual = 0;
Over over;
char buf[2] = {};
const HANDLE server = assertValid(openServer(name(0)));
const HANDLE client = assertValid(createFile(name(0)));
CHECK_SUCCESS(WriteFile(server, buf, 1, &actual, &over));
CHECK_SUCCESS(CloseHandle(server));
CHECK_SUCCESS(ReadFile(client, buf, 2, &actual, &over));
CHECK(actual == 1);
CHECK_FAILURE(ReadFile(client, buf, 2, &actual, &over), ERROR_BROKEN_PIPE);
CHECK_SUCCESS(CloseHandle(client));
}
I know that item #3 above breaks my winpty project (in principle), where a pipe server signals EOF by closing a pipe without disconnecting it. The winpty client can read all the output before hitting the broken pipe. I suspect winpty may not run on Wine anyway, though.
-Ryan