[Libguestfs] [PATCH nbdkit 9/9] server: Port to Windows.

Richard W.M. Jones
2020-08-18T10:50:13Z

This is a partial port of nbdkit to Windows using native APIs.  On
Linux we can use mingw-w64 and Wine to cross-compile and run the
server.  There are many missing or partially implemented things in
this port, see TODO for a mostly complete list.
---
 include/nbdkit-common.h         |  10 ++
 configure.ac                    |  23 +++-
 common/utils/Makefile.am        |  31 +++++
 server/internal.h               |  10 +-
 common/utils/windows-compat.h   | 133 +++++++++++++++++++
 server/background.c             |  16 +++
 server/captive.c                |  20 ++-
 server/connections.c            |  26 +++-
 server/crypto.c                 |  11 +-
 server/main.c                   |  43 ++++++-
 server/plugins.c                |   3 +
 server/public.c                 |  63 ++++++++-
 server/quit.c                   |  34 +++++
 server/signals.c                |  13 ++
 server/socket-activation.c      |  12 ++
 server/sockets.c                |  81 +++++++++++-
 server/usergroup.c              |  25 +++-
 common/utils/utils.c            |  36 +++++-
 common/utils/windows-compat.c   | 221 ++++++++++++++++++++++++++++++++
 common/utils/windows-errors.txt | 105 +++++++++++++++
 .gitignore                      |   1 +
 README                          |  49 +++++++
 TODO                            |  35 ++++-
 23 files changed, 979 insertions(+), 22 deletions(-)

diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h
index 8684b95a..991bcd22 100644
--- a/include/nbdkit-common.h
+++ b/include/nbdkit-common.h
@@ -40,7 +40,13 @@
 #include <stdarg.h>
 #include <stdint.h>
 #include <errno.h>
+
+#if !defined(_WIN32) && !defined(__MINGW32__) && \
+    !defined(__CYGWIN__) && !defined(_MSC_VER)
 #include <sys/socket.h>
+#else
+#include <ws2tcpip.h>
+#endif

 #include <nbdkit-version.h>

@@ -55,7 +61,11 @@ extern "C" {
 #define ATTRIBUTE_FORMAT_PRINTF(fmtpos, argpos)
 #endif

+#ifdef WIN32
+#define NBDKIT_DLLEXPORT __declspec(dllexport)
+#else
 #define NBDKIT_DLLEXPORT
+#endif

 #define NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS     0
 #define NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS    1
diff --git a/configure.ac b/configure.ac
index bd9bd65f..5e18ba4a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -45,7 +45,7 @@ AC_USE_SYSTEM_EXTENSIONS
 dnl NB: Do not [quote] this parameter.
 AM_INIT_AUTOMAKE(foreign)
 AC_PROG_LIBTOOL
-LT_INIT
+LT_INIT([win32-dll])

 dnl List of plugins and filters.
 lang_plugins="\
@@ -313,11 +313,21 @@ AC_CHECK_HEADERS([\
 	alloca.h \
 	byteswap.h \
 	endian.h \
+	grp.h \
+	netdb.h \
+	netinet/in.h \
+	netinet/tcp.h \
+	pwd.h \
+	termios.h \
 	stdatomic.h \
+	syslog.h \
 	sys/endian.h \
 	sys/mman.h \
 	sys/prctl.h \
-	sys/procctl.h])
+	sys/procctl.h \
+	sys/socket.h \
+	sys/un.h \
+	sys/wait.h])

 AC_CHECK_HEADERS([linux/vm_sockets.h], [], [], [#include <sys/socket.h>])

@@ -332,6 +342,7 @@ AC_CHECK_FUNCS([\
 	mlockall \
 	munlock \
 	open_memstream \
+	pipe \
 	pipe2 \
 	ppoll \
 	posix_fadvise])
@@ -468,6 +479,7 @@ AC_MSG_CHECKING([if the target is Windows])
 AS_CASE([$host_os],
     [mingw*|msys*|cygwin*], [
         is_windows=yes
+        LIBS="$LIBS -lmsvcrt -lkernel32 -luser32"
         NO_UNDEFINED_ON_WINDOWS="-no-undefined"
         LINK_LIBNBDKIT_ON_WINDOWS='$(top_builddir)/server/libnbdkit.la'
     ],
@@ -478,12 +490,15 @@ AC_SUBST([NO_UNDEFINED_ON_WINDOWS])
 AC_SUBST([LINK_LIBNBDKIT_ON_WINDOWS])
 AM_CONDITIONAL([IS_WINDOWS],[test "x$is_windows" = "xyes"])

-dnl For Windows, look for the mc/windmc utility.
-dnl XXX Do we need to check for mc.exe as well?
 AS_IF([test "x$is_windows" = "xyes"],[
+    dnl For Windows, look for the mc/windmc utility.
+    dnl XXX Do we need to check for mc.exe as well?
     AC_CHECK_TOOLS([MC],[windmc mc],[no])
     AS_IF([test "x$MC" = "xno"],
           [AC_MSG_ERROR([mc/windmc utility must be available when compiling for
Windows])])
+
+    dnl On Windows we require winsock2.
+    AC_CHECK_LIB([ws2_32], [socket])
 ])

 AC_SEARCH_LIBS([getaddrinfo], [network socket])
diff --git a/common/utils/Makefile.am b/common/utils/Makefile.am
index a621790a..3175e37d 100644
--- a/common/utils/Makefile.am
+++ b/common/utils/Makefile.am
@@ -43,6 +43,9 @@ libutils_la_SOURCES = \
 	utils.h \
 	vector.c \
 	vector.h \
+	windows-compat.h \
+	windows-compat.c \
+	windows-errors.c \
 	$(NULL)
 libutils_la_CPPFLAGS = \
 	-I$(top_srcdir)/include \
@@ -55,6 +58,34 @@ libutils_la_LIBADD = \
 	$(PTHREAD_LIBS) \
 	$(NULL)

+# Generate the code to map Winsock errors to errno codes.
+BUILT_SOURCES = windows-errors.c
+windows-errors.c: windows-errors.txt
+	@rm -f $@ $@-t
+	@echo '/* Generated from windows-errors.txt */' > $@-t
+	@echo '#include <nbdkit-plugin.h>' >> $@-t
+	@echo '#ifdef WIN32' >> $@-t
+	@echo '#include <winsock2.h>' >> $@-t
+	@echo '#include <ws2tcpip.h>' >> $@-t
+	@echo '#include <windows.h>' >> $@-t
+	@echo '#include <errno.h>' >> $@-t
+	@echo 'int' >> $@-t
+	@echo 'translate_winsock_error (const char *fn, int err) {' >>
$@-t
+# Always log the original error.
+	@echo '    nbdkit_debug ("%s: winsock error %d", fn, err);'
>> $@-t
+	@echo '    switch (err) {' >> $@-t
+	@$(SED) -e '/^#/d' \
+	       -e '/^$$/d' \
+	       -e 's/\(.*\)[[:space:]][[:space:]]*\(.*\)/#if defined(\1)
\&\& defined(\2)\n    case \1: return \2;\n#endif/' \
+	< $< >> $@-t
+	@echo '    default:' >> $@-t
+	@echo '    return err > 10000 && err < 10025 ? err - 10000 :
EINVAL;' >> $@-t
+	@echo '    }' >> $@-t
+	@echo '}' >> $@-t
+	@echo '#endif /* WIN32 */' >> $@-t
+	mv $@-t $@
+	chmod -w $@
+
 # Unit tests.

 TESTS = test-quotes test-vector
diff --git a/server/internal.h b/server/internal.h
index d043225a..d04a32cf 100644
--- a/server/internal.h
+++ b/server/internal.h
@@ -36,9 +36,12 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdarg.h>
-#include <sys/socket.h>
 #include <pthread.h>

+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
 #define NBDKIT_API_VERSION 2
 #define NBDKIT_INTERNAL
 #include "nbdkit-plugin.h"
@@ -47,6 +50,7 @@
 #include "nbd-protocol.h"
 #include "unix-path-max.h"
 #include "vector.h"
+#include "windows-compat.h"

 /* Define unlikely macro, but only for GCC.  These are used to move
  * debug and error handling code out of hot paths.
@@ -147,7 +151,11 @@ extern struct backend *top;

 /* quit.c */
 extern volatile int quit;
+#ifndef WIN32
 extern int quit_fd;
+#else
+extern HANDLE quit_fd;
+#endif
 extern void set_up_quit_pipe (void);
 extern void close_quit_pipe (void);
 extern void handle_quit (int sig);
diff --git a/common/utils/windows-compat.h b/common/utils/windows-compat.h
new file mode 100644
index 00000000..74241a19
--- /dev/null
+++ b/common/utils/windows-compat.h
@@ -0,0 +1,133 @@
+/* nbdkit
+ * Copyright (C) 2020 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS
IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef NBDKIT_WINDOWS_COMPAT_H
+#define NBDKIT_WINDOWS_COMPAT_H
+
+#ifdef WIN32
+
+#include <config.h>
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <windows.h>
+
+#include <errno.h>
+
+/* Windows doesn't have O_CLOEXEC, but it also doesn't have file
+ * descriptors that can be inherited across exec.  Similarly for
+ * O_NOCTTY.
+ */
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+#ifndef O_NOCTTY
+#define O_NOCTTY 0
+#endif
+
+/* AI_ADDRCONFIG is not available on Windows.  It enables a rather
+ * obscure feature of getaddrinfo to do with IPv6.
+ */
+#ifndef AI_ADDRCONFIG
+#define AI_ADDRCONFIG 0
+#endif
+
+/* Windows <errno.h> lacks certain errnos, so replace them here as
+ * best we can.
+ */
+#ifndef EBADMSG
+#define EBADMSG EPROTO
+#endif
+#ifndef ESHUTDOWN
+#define ESHUTDOWN ECONNABORTED
+#endif
+
+/* This generated function translates Winsock errors into errno codes. */
+extern int translate_winsock_error (const char *fn, int err);
+
+/* Add wrappers around the Winsock syscalls that nbdkit uses. */
+extern int win_accept (int fd, struct sockaddr *addr, socklen_t *len);
+extern int win_bind (int fd, const struct sockaddr *addr, socklen_t len);
+extern int win_closesocket (int fd);
+extern int win_getpeername (int fd, struct sockaddr *addr, socklen_t *len);
+extern int win_listen (int fd, int backlog);
+extern int win_getsockopt (int fd, int level, int optname,
+                           void *optval, socklen_t *optlen);
+extern int win_recv (int fd, void *buf, size_t len, int flags);
+extern int win_setsockopt (int fd, int level, int optname,
+                           const void *optval, socklen_t optlen);
+extern int win_socket (int domain, int type, int protocol);
+extern int win_send (int fd, const void *buf, size_t len, int flags);
+
+#define accept win_accept
+#define bind win_bind
+#define closesocket win_closesocket
+#define getpeername win_getpeername
+#define listen win_listen
+#define getsockopt win_getsockopt
+#define recv win_recv
+#define setsockopt win_setsockopt
+#define socket win_socket
+#define send win_send
+
+/* Windows has strange names for these functions. */
+#define dup _dup
+#define dup2 _dup2
+
+/* Unfortunately quite commonly used at the moment.  Make it a common
+ * macro so we can easily find places which need porting.
+ *
+ * Note: Don't use this for things which can never work on Windows
+ * (eg. Unix socket support).  Those should just give regular errors.
+ */
+#define NOT_IMPLEMENTED_ON_WINDOWS(feature)                             \
+  do {                                                                  \
+    fprintf (stderr, "nbdkit: %s is not implemented for Windows.\n",
feature); \
+    fprintf (stderr, "You can help by contributing to the Windows port,
see\n"); \
+    fprintf (stderr, "nbdkit README in the source for how to
contribute.\n"); \
+    exit (EXIT_FAILURE);                                                \
+  } while (0)
+
+#else /* !WIN32 */
+
+/* Windows doesn't have a generic function for closing anything,
+ * instead you have to call closesocket on a SOCKET object.  We would
+ * like to #define close to point to the Windows alternative above,
+ * but that's not possible because it breaks things like
+ * backend->close.  So instead the server code must call closesocket()
+ * on anything that might be a socket.
+ */
+#define closesocket close
+
+#endif /* !WIN32 */
+
+#endif /* NBDKIT_WINDOWS_COMPAT_H */
diff --git a/server/background.c b/server/background.c
index 72ab1ef6..e3507d5c 100644
--- a/server/background.c
+++ b/server/background.c
@@ -44,6 +44,8 @@
 /* True if we forked into the background (used to control log messages). */
 bool forked_into_background;

+#ifndef WIN32
+
 /* Run as a background process.  If foreground is set (ie. -f or
  * equivalent) then this does nothing.  Otherwise it forks into the
  * background and sets forked_into_background.
@@ -79,3 +81,17 @@ fork_into_background (void)
   forked_into_background = true;
   debug ("forked into background (new pid = %d)", getpid ());
 }
+
+#else /* WIN32 */
+
+void
+fork_into_background (void)
+{
+  if (foreground)
+    return;
+
+  fprintf (stderr, "nbdkit: You must use the -f option on
Windows.\n");
+  NOT_IMPLEMENTED_ON_WINDOWS ("daemonizing");
+}
+
+#endif /* WIN32 */
diff --git a/server/captive.c b/server/captive.c
index a8947d7c..19e50b07 100644
--- a/server/captive.c
+++ b/server/captive.c
@@ -38,14 +38,19 @@
 #include <string.h>
 #include <unistd.h>
 #include <sys/types.h>
-#include <sys/wait.h>
 #include <signal.h>
 #include <assert.h>

+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
 #include "utils.h"

 #include "internal.h"

+#ifndef WIN32
+
 /* Handle the --run option.  If run is NULL, does nothing.  If run is
  * not NULL then run nbdkit as a captive subprocess of the command.
  */
@@ -208,3 +213,16 @@ run_command (void)

   debug ("forked into background (new pid = %d)", getpid ());
 }
+
+#else /* WIN32 */
+
+void
+run_command (void)
+{
+  if (!run)
+    return;
+
+  NOT_IMPLEMENTED_ON_WINDOWS ("--run");
+}
+
+#endif /* WIN32 */
diff --git a/server/connections.c b/server/connections.c
index a3dd4ca7..96b72257 100644
--- a/server/connections.c
+++ b/server/connections.c
@@ -38,10 +38,13 @@
 #include <inttypes.h>
 #include <string.h>
 #include <unistd.h>
-#include <sys/socket.h>
 #include <fcntl.h>
 #include <assert.h>

+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
 #include "internal.h"
 #include "utils.h"

@@ -268,6 +271,7 @@ new_connection (int sockin, int sockout, int nworkers)
       goto error2;
     }
 #else
+#ifdef HAVE_PIPE
     /* If we were fully parallel, then this function could be
      * accepting connections in one thread while another thread could
      * be in a plugin trying to fork.  But plugins.c forced
@@ -296,16 +300,23 @@ new_connection (int sockin, int sockout, int nworkers)
       goto error2;
     }
     unlock_request ();
+#else /* !HAVE_PIPE2 && !HAVE_PIPE */
+    /* Windows has neither pipe2 nor pipe. XXX */
+#endif
 #endif
   }

   conn->sockin = sockin;
   conn->sockout = sockout;
   conn->recv = raw_recv;
+#ifndef WIN32
   if (getsockopt (sockout, SOL_SOCKET, SO_TYPE, &opt, &optlen) == 0)
     conn->send = raw_send_socket;
   else
     conn->send = raw_send_other;
+#else
+  conn->send = raw_send_socket;
+#endif
   conn->close = raw_close;

   threadlocal_set_conn (conn);
@@ -439,7 +450,16 @@ raw_recv (void *vbuf, size_t len)
   bool first_read = true;

   while (len > 0) {
+    /* On Unix we want to use read(2) here because that allows us to
+     * read from non-sockets (think: nbdkit -s).  In particular this
+     * makes fuzzing possible.  However this is not possible on
+     * Windows where we must use recv.
+     */
+#ifndef WIN32
     r = read (sock, buf, len);
+#else
+    r = recv (sock, buf, len, 0);
+#endif
     if (r == -1) {
       if (errno == EINTR || errno == EAGAIN)
         continue;
@@ -469,7 +489,7 @@ raw_close (void)
   GET_CONN;

   if (conn->sockin >= 0)
-    close (conn->sockin);
+    closesocket (conn->sockin);
   if (conn->sockout >= 0 && conn->sockin != conn->sockout)
-    close (conn->sockout);
+    closesocket (conn->sockout);
 }
diff --git a/server/crypto.c b/server/crypto.c
index 0d3d4e8c..a3f8682f 100644
--- a/server/crypto.c
+++ b/server/crypto.c
@@ -161,7 +161,12 @@ start_certificates (void)
     const char *home;
     CLEANUP_FREE char *path = NULL;

-    if (geteuid () != 0) {
+#ifndef WIN32
+#define RUNNING_AS_NON_ROOT_FOR_CERTIFICATES_DIR (geteuid () != 0)
+#else
+#define RUNNING_AS_NON_ROOT_FOR_CERTIFICATES_DIR 0
+#endif
+    if (RUNNING_AS_NON_ROOT_FOR_CERTIFICATES_DIR) {
       home = getenv ("HOME");
       if (home) {
         if (asprintf (&path, "%s/.pki/%s", home, PACKAGE_NAME) ==
-1) {
@@ -407,9 +412,9 @@ crypto_close (void)
   gnutls_bye (session, GNUTLS_SHUT_RDWR);

   if (sockin >= 0)
-    close (sockin);
+    closesocket (sockin);
   if (sockout >= 0 && sockin != sockout)
-    close (sockout);
+    closesocket (sockout);

   gnutls_deinit (session);
   conn->crypto_session = NULL;
diff --git a/server/main.c b/server/main.c
index f6ffd9b2..d0becaf1 100644
--- a/server/main.c
+++ b/server/main.c
@@ -44,12 +44,15 @@
 #include <assert.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/socket.h>

 #ifdef HAVE_SYS_MMAN_H
 #include <sys/mman.h>
 #endif

+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
 #ifdef HAVE_LINUX_VM_SOCKETS_H
 #include <linux/vm_sockets.h>
 #endif
@@ -79,6 +82,7 @@ static void write_pidfile (void);
 static bool is_config_key (const char *key, size_t len);
 static void error_if_stdio_closed (void);
 static void switch_stdio (void);
+static void winsock_init (void);

 struct debug_flag *debug_flags; /* -D */
 bool exit_with_parent;          /* --exit-with-parent */
@@ -190,6 +194,7 @@ nbdkit_main (int argc, char *argv[])
   const char *magic_config_key;

   error_if_stdio_closed ();
+  winsock_init ();

 #if !ENABLE_LIBFUZZER
   threadlocal_init ();
@@ -730,6 +735,8 @@ nbdkit_main (int argc, char *argv[])
   return EXIT_SUCCESS;
 }

+#ifndef WIN32
+
 /* Implementation of '-U -' */
 static char *
 make_random_fifo (void)
@@ -762,6 +769,16 @@ make_random_fifo (void)
   return sock;
 }

+#else /* WIN32 */
+
+static char *
+make_random_fifo (void)
+{
+  NOT_IMPLEMENTED_ON_WINDOWS ("-U -");
+}
+
+#endif /* WIN32 */
+
 static struct backend *
 open_plugin_so (size_t i, const char *name, int short_name)
 {
@@ -1003,6 +1020,7 @@ is_config_key (const char *key, size_t len)
 static void
 error_if_stdio_closed (void)
 {
+#ifdef F_GETFL
   if (fcntl (STDERR_FILENO, F_GETFL) == -1) {
     /* Nowhere we can report the error. Oh well. */
     exit (EXIT_FAILURE);
@@ -1012,6 +1030,7 @@ error_if_stdio_closed (void)
     perror ("expecting stdin/stdout to be opened");
     exit (EXIT_FAILURE);
   }
+#endif
 }

 /* Sanitize stdin/stdout to /dev/null, after saving the originals
@@ -1026,6 +1045,7 @@ error_if_stdio_closed (void)
 static void
 switch_stdio (void)
 {
+#if defined(F_DUPFD_CLOEXEC) || defined(F_DUPFD)
   fflush (stdin);
   fflush (NULL);
   if (listen_stdin || run) {
@@ -1043,6 +1063,8 @@ switch_stdio (void)
       exit (EXIT_FAILURE);
     }
   }
+#endif
+#ifndef WIN32
   close (STDIN_FILENO);
   close (STDOUT_FILENO);
   if (open ("/dev/null", O_RDONLY) != STDIN_FILENO ||
@@ -1050,4 +1072,23 @@ switch_stdio (void)
     perror ("open");
     exit (EXIT_FAILURE);
   }
+#endif
+}
+
+/* On Windows the Winsock library must be initialized early.
+ * https://docs.microsoft.com/en-us/windows/win32/winsock/initializing-winsock
+ */
+static void
+winsock_init (void)
+{
+#ifdef WIN32
+  WSADATA wsaData;
+  int result;
+
+  result = WSAStartup (MAKEWORD (2, 2), &wsaData);
+  if (result != 0) {
+    fprintf (stderr, "WSAStartup failed: %d\n", result);
+    exit (EXIT_FAILURE);
+  }
+#endif
 }
diff --git a/server/plugins.c b/server/plugins.c
index 218764da..736154b8 100644
--- a/server/plugins.c
+++ b/server/plugins.c
@@ -39,7 +39,10 @@
 #include <inttypes.h>
 #include <assert.h>
 #include <errno.h>
+
+#ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
+#endif

 #include "internal.h"
 #include "minmax.h"
diff --git a/server/public.c b/server/public.c
index b25842f9..98086d72 100644
--- a/server/public.c
+++ b/server/public.c
@@ -45,10 +45,21 @@
 #include <string.h>
 #include <unistd.h>
 #include <limits.h>
-#include <termios.h>
 #include <errno.h>
 #include <signal.h>
+
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
+#endif
+
+#ifdef WIN32
+/* For nanosleep on Windows. */
+#include <pthread_time.h>
+#endif

 #include "ascii-ctype.h"
 #include "ascii-string.h"
@@ -468,6 +479,8 @@ nbdkit_read_password (const char *value, char **password)
   return 0;
 }

+#ifndef WIN32
+
 typedef struct termios echo_mode;

 static void
@@ -487,6 +500,37 @@ echo_restore (const echo_mode *old_mode)
   tcsetattr (STDIN_FILENO, TCSAFLUSH, old_mode);
 }

+#else /* WIN32 */
+
+/* Windows implementation of tty echo off based on this:
+ * https://stackoverflow.com/a/1455007
+ */
+typedef DWORD echo_mode;
+
+static void
+echo_off (echo_mode *old_mode)
+{
+  HANDLE h_stdin;
+  DWORD mode;
+
+  h_stdin = GetStdHandle (STD_INPUT_HANDLE);
+  GetConsoleMode (h_stdin, old_mode);
+  mode = *old_mode;
+  mode &= ~ENABLE_ECHO_INPUT;
+  SetConsoleMode (h_stdin, mode);
+}
+
+static void
+echo_restore (const echo_mode *old_mode)
+{
+  HANDLE h_stdin;
+
+  h_stdin = GetStdHandle (STD_INPUT_HANDLE);
+  SetConsoleMode (h_stdin, *old_mode);
+}
+
+#endif /* WIN32 */
+
 static int
 read_password_interactive (char **password)
 {
@@ -546,6 +590,8 @@ read_password_interactive (char **password)
   return 0;
 }

+#ifndef WIN32
+
 static int
 read_password_from_fd (const char *what, int fd, char **password)
 {
@@ -593,6 +639,21 @@ read_password_from_fd (const char *what, int fd, char
**password)
   return 0;
 }

+#else /* WIN32 */
+
+/* As far as I know this will never be possible on Windows, so it's a
+ * simple error.
+ */
+static int
+read_password_from_fd (const char *what, int fd, char **password)
+{
+  nbdkit_error ("not possible to read passwords from file descriptors
"
+                "under Windows");
+  return -1;
+}
+
+#endif /* WIN32 */
+
 int
 nbdkit_nanosleep (unsigned sec, unsigned nsec)
 {
diff --git a/server/quit.c b/server/quit.c
index 13fef437..21263fdb 100644
--- a/server/quit.c
+++ b/server/quit.c
@@ -48,8 +48,15 @@
  * a race.
  */
 volatile int quit;
+
+#ifndef WIN32
 int quit_fd;
 static int write_quit_fd;
+#else
+HANDLE quit_fd;
+#endif
+
+#ifndef WIN32

 void
 set_up_quit_pipe (void)
@@ -99,6 +106,33 @@ set_quit (void)
 #pragma GCC diagnostic pop
 }

+#else /* WIN32 */
+
+/* Pipes don't work well with WaitForMultipleObjectsEx in Windows.  In
+ * any case, an Event is a better match with what we are trying to do
+ * here.
+ */
+void
+set_up_quit_pipe (void)
+{
+  quit_fd = CreateEventA (NULL, FALSE, FALSE, NULL);
+}
+
+void
+close_quit_pipe (void)
+{
+  CloseHandle (quit_fd);
+}
+
+void
+set_quit (void)
+{
+  quit = 1;
+  SetEvent (quit_fd);
+}
+
+#endif /* WIN32 */
+
 void
 handle_quit (int sig)
 {
diff --git a/server/signals.c b/server/signals.c
index d7dc17d0..f463ccd8 100644
--- a/server/signals.c
+++ b/server/signals.c
@@ -40,6 +40,8 @@

 #include "internal.h"

+#ifndef WIN32
+
 /* Set up signal handlers. */
 void
 set_up_signals (void)
@@ -59,3 +61,14 @@ set_up_signals (void)
   sa.sa_handler = SIG_IGN;
   sigaction (SIGPIPE, &sa, NULL);
 }
+
+#else /* WIN32 */
+
+void
+set_up_signals (void)
+{
+  signal (SIGINT, handle_quit);
+  signal (SIGTERM, handle_quit);
+}
+
+#endif /* WIN32 */
diff --git a/server/socket-activation.c b/server/socket-activation.c
index f273f8cc..a49e1cc0 100644
--- a/server/socket-activation.c
+++ b/server/socket-activation.c
@@ -42,6 +42,8 @@

 #include "internal.h"

+#ifndef WIN32
+
 /* Handle socket activation.  This is controlled through special
  * environment variables inherited by nbdkit.  Returns 0 if no socket
  * activation.  Otherwise returns the number of FDs.  See also
@@ -105,3 +107,13 @@ get_socket_activation (void)

   return nr_fds;
 }
+
+#else /* WIN32 */
+
+unsigned int
+get_socket_activation (void)
+{
+  return 0;
+}
+
+#endif /* WIN32 */
diff --git a/server/sockets.c b/server/sockets.c
index 8da331da..4fcf3529 100644
--- a/server/sockets.c
+++ b/server/sockets.c
@@ -41,11 +41,26 @@
 #include <errno.h>
 #include <assert.h>
 #include <sys/types.h>
+
+#ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
+#endif
+
+#ifdef HAVE_SYS_UN_H
 #include <sys/un.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
 #include <netinet/in.h>
+#endif
+
+#ifdef HAVE_NETINET_TCP_H
 #include <netinet/tcp.h>
+#endif
+
+#ifdef HAVE_NETDB_H
 #include <netdb.h>
+#endif

 #ifdef HAVE_LINUX_VM_SOCKETS_H
 #include <linux/vm_sockets.h>
@@ -94,6 +109,8 @@ clear_selinux_label (void)
 #endif
 }

+#ifndef WIN32
+
 void
 bind_unix_socket (sockets *socks)
 {
@@ -149,6 +166,16 @@ bind_unix_socket (sockets *socks)
   debug ("bound to unix socket %s", unixsocket);
 }

+#else /* WIN32 */
+
+void
+bind_unix_socket (sockets *socks)
+{
+  NOT_IMPLEMENTED_ON_WINDOWS ("-U");
+}
+
+#endif /* WIN32 */
+
 void
 bind_tcpip_socket (sockets *socks)
 {
@@ -207,7 +234,7 @@ bind_tcpip_socket (sockets *socks)
     if (bind (sock, a->ai_addr, a->ai_addrlen) == -1) {
       if (errno == EADDRINUSE) {
         addr_in_use = true;
-        close (sock);
+        closesocket (sock);
         continue;
       }
       perror ("bind");
@@ -402,7 +429,7 @@ accept_connection (int listen_sock)
   pthread_attr_destroy (&attrs);
   if (unlikely (err != 0)) {
     fprintf (stderr, "%s: pthread_create: %s\n", program_name,
strerror (err));
-    close (thread_data->sock);
+    closesocket (thread_data->sock);
     free (thread_data);
     return;
   }
@@ -412,6 +439,8 @@ accept_connection (int listen_sock)
    */
 }

+#ifndef WIN32
+
 /* Check the list of sockets plus quit_fd until a POLLIN event occurs
  * on any of them.
  *
@@ -465,6 +494,52 @@ check_sockets_and_quit_fd (const sockets *socks)
   }
 }

+#else /* WIN32 */
+
+static void
+check_sockets_and_quit_fd (const sockets *socks)
+{
+  const size_t nr_socks = socks->size;
+  size_t i;
+  HANDLE h, handles[nr_socks+1];
+  DWORD r;
+
+  for (i = 0; i < nr_socks; ++i) {
+    h = WSACreateEvent ();
+    WSAEventSelect (_get_osfhandle (socks->ptr[i]), h,
+                    FD_ACCEPT|FD_READ|FD_CLOSE);
+    handles[i] = h;
+  }
+  handles[nr_socks] = quit_fd;
+
+  r = WaitForMultipleObjectsEx ((DWORD) (nr_socks+1), handles,
+                                FALSE, INFINITE, TRUE);
+  debug ("WaitForMultipleObjectsEx returned %d", (int) r);
+  if (r == WAIT_FAILED) {
+    fprintf (stderr, "%s: WaitForMultipleObjectsEx: error %lu\n",
+             program_name, GetLastError ());
+    exit (EXIT_FAILURE);
+  }
+
+  for (i = 0; i < nr_socks; ++i) {
+    WSAEventSelect (_get_osfhandle (socks->ptr[i]), NULL, 0);
+    WSACloseEvent (handles[i]);
+  }
+
+  if (r == WAIT_OBJECT_0 + nr_socks) /* quit_fd signalled. */
+    return;
+
+  if (r >= WAIT_OBJECT_0 && r < WAIT_OBJECT_0 + nr_socks) {
+    i = r - WAIT_OBJECT_0;
+    accept_connection (socks->ptr[i]);
+    return;
+  }
+
+  debug ("WaitForMultipleObjectsEx: unexpected return value: %lu\n",
r);
+}
+
+#endif /* WIN32 */
+
 void
 accept_incoming_connections (const sockets *socks)
 {
@@ -488,6 +563,6 @@ accept_incoming_connections (const sockets *socks)
   pthread_mutex_unlock (&count_mutex);

   for (i = 0; i < socks->size; ++i)
-    close (socks->ptr[i]);
+    closesocket (socks->ptr[i]);
   free (socks->ptr);
 }
diff --git a/server/usergroup.c b/server/usergroup.c
index 11bafceb..1bede73f 100644
--- a/server/usergroup.c
+++ b/server/usergroup.c
@@ -37,13 +37,21 @@
 #include <stdarg.h>
 #include <string.h>
 #include <unistd.h>
-#include <pwd.h>
-#include <grp.h>
 #include <errno.h>
 #include <sys/types.h>

+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
 #include "internal.h"

+#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
+
 static uid_t parseuser (const char *);
 static gid_t parsegroup (const char *);

@@ -138,3 +146,16 @@ parsegroup (const char *id)

   return grp->gr_gid;
 }
+
+#else /* a platform like Windows which lacks pwd/grp functions */
+
+void
+change_user (void)
+{
+  if (!user && !group)
+    return;
+
+  NOT_IMPLEMENTED_ON_WINDOWS ("--user/--group");
+}
+
+#endif
diff --git a/common/utils/utils.c b/common/utils/utils.c
index 0da54726..49204532 100644
--- a/common/utils/utils.c
+++ b/common/utils/utils.c
@@ -36,12 +36,20 @@
 #include <stdlib.h>
 #include <fcntl.h>
 #include <unistd.h>
-#include <sys/socket.h>
 #include <sys/types.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
 #include <sys/wait.h>
+#endif

 #include <nbdkit-plugin.h>

+#ifndef WIN32
+
 /* Convert exit status to nbd_error.  If the exit status was nonzero
  * or another failure then -1 is returned.
  */
@@ -67,6 +75,10 @@ exit_status_to_nbd_error (int status, const char *cmd)
   return 0;
 }

+#endif /* !WIN32 */
+
+#ifndef WIN32
+
 /* Set the FD_CLOEXEC flag on the given fd, if it is non-negative.
  * On failure, close fd and return -1; on success, return fd.
  *
@@ -107,6 +119,18 @@ set_cloexec (int fd)
 #endif
 }

+#else /* WIN32 */
+
+int
+set_cloexec (int fd)
+{
+  return fd;
+}
+
+#endif /* WIN32 */
+
+#ifndef WIN32
+
 /* Set the O_NONBLOCK flag on the given fd, if it is non-negative.
  * On failure, close fd and return -1; on success, return fd.
  */
@@ -129,3 +153,13 @@ set_nonblock (int fd)
   }
   return fd;
 }
+
+#else /* WIN32 */
+
+int
+set_nonblock (int fd)
+{
+  return fd;
+}
+
+#endif /* WIN32 */
diff --git a/common/utils/windows-compat.c b/common/utils/windows-compat.c
new file mode 100644
index 00000000..355d14f0
--- /dev/null
+++ b/common/utils/windows-compat.c
@@ -0,0 +1,221 @@
+/* nbdkit
+ * Copyright (C) 2020 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS
IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#ifdef WIN32
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <windows.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "windows-compat.h"
+
+#undef accept
+#undef bind
+#undef closesocket
+#undef getpeername
+#undef listen
+#undef getsockopt
+#undef recv
+#undef setsockopt
+#undef socket
+#undef send
+
+#define GET_SOCKET_FROM_FD(fd)     \
+  SOCKET sk = _get_osfhandle (fd); \
+  if (sk == INVALID_SOCKET) {      \
+    errno = EBADF;                 \
+    return -1;                     \
+  }
+
+/* Sockets are non-blocking by default.  Make them blocking.  This
+ * introduces a bunch of caveats, see:
+ * http://www.sockets.com/winsock.htm#Overview_BlockingNonBlocking
+ */
+static int
+set_blocking (SOCKET sk)
+{
+  u_long arg = 0;
+
+  if (ioctlsocket (sk, FIONBIO, &arg) < 0) {
+    errno = translate_winsock_error ("ioctlsocket", WSAGetLastError
());
+    return -1;
+  }
+  return 0;
+}
+
+int
+win_accept (int fd, struct sockaddr *addr, socklen_t *len)
+{
+  SOCKET new_sk;
+  GET_SOCKET_FROM_FD (fd);
+
+  new_sk = accept (sk, addr, len);
+  if (new_sk == INVALID_SOCKET) {
+    errno = translate_winsock_error ("accept", WSAGetLastError ());
+    return -1;
+  }
+  if (set_blocking (new_sk) == -1) return -1;
+  return _open_osfhandle ((intptr_t) new_sk, O_RDWR|O_BINARY);
+}
+
+int
+win_bind (int fd, const struct sockaddr *addr, socklen_t len)
+{
+  GET_SOCKET_FROM_FD (fd);
+
+  if (bind (sk, addr, len) < 0) {
+    errno = translate_winsock_error ("bind", WSAGetLastError ());
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+win_closesocket (int fd)
+{
+  GET_SOCKET_FROM_FD (fd);
+
+  if (closesocket (sk) < 0) {
+    errno = translate_winsock_error ("closesocket", WSAGetLastError
());
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+win_getpeername (int fd, struct sockaddr *addr, socklen_t *len)
+{
+  GET_SOCKET_FROM_FD (fd);
+
+  if (getpeername (sk, addr, len) < 0) {
+    errno = translate_winsock_error ("getpeername", WSAGetLastError
());
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+win_listen (int fd, int backlog)
+{
+  GET_SOCKET_FROM_FD (fd);
+
+  if (listen (sk, backlog) < 0) {
+    errno = translate_winsock_error ("listen", WSAGetLastError ());
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+win_getsockopt (int fd, int level, int optname,
+                void *optval, socklen_t *optlen)
+{
+  GET_SOCKET_FROM_FD (fd);
+
+  if (getsockopt (sk, level, optname, optval, optlen) < 0) {
+    errno = translate_winsock_error ("getsockopt", WSAGetLastError
());
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+win_recv (int fd, void *buf, size_t len, int flags)
+{
+  int r;
+  GET_SOCKET_FROM_FD (fd);
+
+  r = recv (sk, buf, len, flags);
+  if (r < 0) {
+    errno = translate_winsock_error ("recv", WSAGetLastError ());
+    return -1;
+  }
+
+  return r;
+}
+
+int
+win_setsockopt (int fd, int level, int optname,
+                const void *optval, socklen_t optlen)
+{
+  GET_SOCKET_FROM_FD (fd);
+
+  if (setsockopt (sk, level, optname, optval, optlen) < 0) {
+    errno = translate_winsock_error ("setsockopt", WSAGetLastError
());
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+win_socket (int domain, int type, int protocol)
+{
+  SOCKET sk;
+
+  sk = WSASocket (domain, type, protocol, NULL, 0, 0);
+  if (sk == INVALID_SOCKET) {
+    errno = translate_winsock_error ("socket", WSAGetLastError ());
+    return -1;
+  }
+
+  if (set_blocking (sk) == -1) return -1;
+  return _open_osfhandle ((intptr_t) sk, O_RDWR|O_BINARY);
+}
+
+int
+win_send (int fd, const void *buf, size_t len, int flags)
+{
+  int r;
+  GET_SOCKET_FROM_FD (fd);
+
+  r = send (sk, buf, len, flags);
+  if (r < 0) {
+    errno = translate_winsock_error ("send", WSAGetLastError ());
+    return -1;
+  }
+
+  return r;
+}
+
+#endif /* WIN32 */
diff --git a/common/utils/windows-errors.txt b/common/utils/windows-errors.txt
new file mode 100644
index 00000000..1a252abe
--- /dev/null
+++ b/common/utils/windows-errors.txt
@@ -0,0 +1,105 @@
+# Winsock error to errno code mapping.
+# Copyright (C) 2020 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS
IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+# The main reference is:
+#
https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
+# This was originally written by hand, but I also referenced libvirt's
+# and Gnulib's choices of mappings.
+
+WSA_INVALID_HANDLE	EBADF
+WSA_NOT_ENOUGH_MEMORY	ENOMEM
+WSA_INVALID_PARAMETER	EINVAL
+WSA_OPERATION_ABORTED	ECONNABORTED
+
+# These two are only kind of correct.
+WSA_IO_INCOMPLETE	EWOULDBLOCK
+WSA_IO_PENDING		EWOULDBLOCK
+
+WSAEINTR		EINTR
+WSAEBADF		EBADF
+WSAEACCES		EACCES
+WSAEFAULT		EFAULT
+WSAEINVAL		EINVAL
+WSAEMFILE		EMFILE
+WSAEWOULDBLOCK		EWOULDBLOCK
+WSAEINPROGRESS		EINPROGRESS
+WSAEALREADY		EALREADY
+WSAENOTSOCK		ENOTSOCK
+WSAEDESTADDRREQ		EDESTADDRREQ
+WSAEMSGSIZE		EMSGSIZE
+WSAEPROTOTYPE		EPROTOTYPE
+WSAENOPROTOOPT		ENOPROTOOPT
+WSAEPROTONOSUPPORT	EPROTONOSUPPORT
+WSAESOCKTNOSUPPORT	ESOCKTNOSUPPORT
+WSAEOPNOTSUPP		EOPNOTSUPP
+WSAEPFNOSUPPORT		EPFNOSUPPORT
+WSAEAFNOSUPPORT		EAFNOSUPPORT
+WSAEADDRINUSE		EADDRINUSE
+WSAEADDRNOTAVAIL	EADDRNOTAVAIL
+WSAENETDOWN		ENETDOWN
+WSAENETUNREACH		ENETUNREACH
+WSAENETRESET		ENETRESET
+WSAECONNABORTED		ECONNABORTED
+WSAECONNRESET		ECONNRESET
+WSAENOBUFS		ENOBUFS
+WSAEISCONN		EISCONN
+WSAENOTCONN		ENOTCONN
+WSAESHUTDOWN		ESHUTDOWN
+WSAETOOMANYREFS		ETOOMANYREFS
+WSAETIMEDOUT		ETIMEDOUT
+WSAECONNREFUSED		ECONNREFUSED
+WSAELOOP		ELOOP
+WSAENAMETOOLONG		ENAMETOOLONG
+WSAEHOSTDOWN		EHOSTDOWN
+WSAEHOSTUNREACH		EHOSTUNREACH
+WSAENOTEMPTY		ENOTEMPTY
+
+# This really means "too many processes" but this is the closest I
could find.
+WSAEPROCLIM		EMFILE
+
+WSAEUSERS		EUSERS
+WSAEDQUOT		EDQUOT
+WSAESTALE		ESTALE
+WSAEREMOTE		EREMOTE
+
+# The next three are respectively: Didn't call WSAStartup, Winsock
+# version is unsupported, and WSAStartup failed.
+WSASYSNOTREADY		EINVAL
+WSAVERNOTSUPPORTED	EINVAL
+WSANOTINITIALISED	EINVAL
+
+WSAEDISCON		ESHUTDOWN
+WSAENOMORE		ESHUTDOWN
+WSAECANCELLED		ECANCELED
+
+# There are a bunch more after this but they all seem pretty obscure.
+# Unknown errors are mapped to EIO and a debug message is printed so
+# we have the original error.
diff --git a/.gitignore b/.gitignore
index 792b73c6..3d04f927 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,7 @@ plugins/*/*.3
 /common/replacements/win32/nbdkit-cat.rc
 /common/utils/test-quotes
 /common/utils/test-vector
+/common/utils/windows-errors.c
 /compile
 /config.cache
 /config.guess
diff --git a/README b/README
index 0e295146..707a8672 100644
--- a/README
+++ b/README
@@ -46,6 +46,9 @@ Linux, FreeBSD, OpenBSD or Haiku and:

  - GNU make

+(For Windows support, see the separate section at the end of this
+document.)
+
 Although it is possible to build without it, it’s recommended to
 enable TLS (authentication and encryption) support for which you will
 need:
@@ -300,3 +303,49 @@ Test coverage
 Open your browser and examine the coverage/ directory.  At the time of
 writing (2020-04) test coverage of the server is reasonable, but
 things are much worse for certain plugins and filters.
+
+WINDOWS
+======+
+Experimentally, the server can be compiled on Windows or
+cross-compiled from Linux using mingw-w64.  Only a small subset of
+features are available.  To find out what is missing read the TODO
+"Windows port".
+
+For the rest of this section we talk about cross-compiling for Windows
+using Linux and mingw-w64.  At a minimum you will need:
+
+    mingw-w64 GCC
+    mingw-w64 dlfcn
+    mingw-w64 winpthreads
+    mingw-w64 gnutls  (optional, but highly recommended)
+    mingw-w64 libxml2 (optional, but highly recommended)
+    wine              (if you want to run it on Linux)
+
+Other mingw-w64 libraries may be installed which will add
+functionality (see full list of requirements above), but you may end
+up hitting areas we have not compiled or tested before.
+
+To cross compile do:
+
+    mingw64-configure
+    mingw64-make
+
+It is expected to fail, but check that it gets as far as building
+server/nbdkit.exe.  You can test if the server is working by doing:
+
+    wine server/nbdkit.exe --dump-config
+
+Now try to build plugins and filters (many will not compile):
+
+    mingw64-make -k
+
+To see which ones were compiled:
+
+    find -name '*.dll'
+
+You can run them under Wine without installing using eg:
+
+    wine server/nbdkit.exe -f -v \
+                           plugins/memory/.libs/nbdkit-memory-plugin.dll \
+                           size=1G
diff --git a/TODO b/TODO
index 89b45c72..c3314d37 100644
--- a/TODO
+++ b/TODO
@@ -300,8 +300,6 @@ Build-related
   bash-completion and ocaml add-ons into their system-wide home do
   not play nicely with --prefix builds for a non-root user.

-* Port to Windows.
-
 * Right now, 'make check' builds keys with an expiration of 1 year
   only if they don't exist, and we leave the keys around except under
   'make distclean'.  This leads to testsuite failures when
@@ -310,6 +308,39 @@ Build-related
   scripts, but tweak the scripts themselves to be a no-op unless the
   keys don't exist or have expired.

+Windows port
+------------
+
+Currently many features are missing, including:
+
+* Daemonization.  This is not really applicable for Windows where you
+  would instead want to run nbdkit as a service using something like
+  SRVANY.  You must use the -f option or one of the other options that
+  implies -f.
+
+* These options are all unimplemented:
+  --group, --log=syslog, --pidfile, --run, --selinux-label, --single
+  --swap, --unix, --user, --vsock
+
+* For possible Unix domain socket support in future see:
+  https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
+
+* The file plugin.  The current file plugin is essentially POSIX-only.
+  We would like to eventually write an alternate file plugin which
+  uses Windows APIs.
+
+* Many other plugins and filters.
+
+* Short names for plugins and filters don't work at the moment.
+
+* The ./nbdkit wrapper in the top directory is not built yet.
+
+* errno_is_preserved should use GetLastError and/or WSAGetLastError
+  but currently does neither so errors from plugins are probably wrong
+  in many cases.
+
+* Most tests will fail because of the missing features above.
+
 V3 plugin protocol
 ------------------

--
2.27.0