[Libguestfs] [PATCH nbdkit incomplete 4/5] server: Port to Windows using mingw-w64.

Richard W.M. Jones rjones at redhat.com
Mon Jan 14 12:18:33 UTC 2019


This allows nbdkit to be cross-compiled for Windows (from Linux).  On
Fedora[1] you would use the commands below:

  sudo dnf install mingw64-gcc mingw64-dlfcn \
                   mingw64-gnutls mingw64-xz mingw64-zlib
  mingw64-configure --disable-plugins
  make

This results in a Windows binary which can be run on Windows or on
Linux under Wine.

It also probably allows nbdkit to be compiled on Windows natively
using the mingw-w64 port of GCC, but I did not test that.

There are several limitations compared to running nbdkit on a real
server, see ‘TODO’ for the complete list.

[1] Note that mingw64-configure is a Fedora-specific script which runs
./configure with extra parameters to make cross-compilation for
Windows work.  You can add other configure parameters to the command
or if you're not using Fedora then use ‘./configure --host=...’
instead.
---
 configure.ac                   |  21 ++-
 common/include/byte-swapping.h |   7 +
 include/nbdkit-common.h        |  24 +--
 include/nbdkit-plugin.h        |   2 +-
 server/internal.h              |  47 ++++++
 server/crypto.c                |   7 +-
 server/log-syslog.c            |   5 +
 server/main.c                  | 259 ++++++++++++++++++++++++---------
 server/quit.c                  |  56 +++++++
 server/sockets.c               |  80 +++++++++-
 server/utils.c                 |  80 ++++++++--
 .gitignore                     |   4 +
 TODO                           |  21 +++
 server/Makefile.am             |  17 ++-
 server/nbdkit.def              |  47 ++++++
 server/nbdkit.syms             |   2 +
 tests/Makefile.am              |  48 +++++-
 17 files changed, 626 insertions(+), 101 deletions(-)

diff --git a/configure.ac b/configure.ac
index 8c2d9c0..f618324 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,6 +105,14 @@ AS_IF([test "x$is_windows" = "xyes"],[
     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 look for dlltool.
+    AC_CHECK_TOOLS([DLLTOOL],[dlltool],[no])
+    AS_IF([test "x$DLLTOOL" = "xno"],
+          [AC_MSG_ERROR([dlltool utility must be available when compiling for Windows])])
+
+    dnl On Windows we require winsock2.
+    AC_CHECK_LIB([ws2_32], [socket])
 ])
 
 dnl On Haiku we must use BSD-compatibility headers to get the endian
@@ -199,10 +207,21 @@ AC_CHECK_HEADERS([\
 	alloca.h \
 	byteswap.h \
 	endian.h \
+	grp.h \
+	netdb.h \
+	poll.h \
+	pwd.h \
 	selinux/selinux.h \
+	syslog.h \
 	sys/endian.h \
 	sys/prctl.h \
-	sys/procctl.h])
+	sys/procctl.h \
+	sys/socket.h \
+	sys/un.h \
+	sys/wait.h \
+	termios.h \
+	windows.h \
+	ws2tcpip.h])
 
 dnl Check for functions in libc, all optional.
 AC_CHECK_FUNCS([\
diff --git a/common/include/byte-swapping.h b/common/include/byte-swapping.h
index c0831ec..a7ec78b 100644
--- a/common/include/byte-swapping.h
+++ b/common/include/byte-swapping.h
@@ -59,6 +59,13 @@
 #include <sys/endian.h>
 #endif
 
+#ifdef WIN32
+#include <stdlib.h>
+#define __bswap_16 _byteswap_ushort
+#define __bswap_32 _byteswap_ulong
+#define __bswap_64 _byteswap_uint64
+#endif
+
 #ifndef htobe32
 # if __BYTE_ORDER == __LITTLE_ENDIAN
 #  define htobe16(x) __bswap_16 (x)
diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h
index cb9954e..7d68cd4 100644
--- a/include/nbdkit-common.h
+++ b/include/nbdkit-common.h
@@ -53,6 +53,12 @@ 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
 #define NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS        2
@@ -65,16 +71,16 @@ extern "C" {
 #define NBDKIT_FUA_EMULATE    1
 #define NBDKIT_FUA_NATIVE     2
 
-extern void nbdkit_error (const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF (1, 2);
-extern void nbdkit_verror (const char *msg, va_list args);
-extern void nbdkit_debug (const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF (1, 2);
-extern void nbdkit_vdebug (const char *msg, va_list args);
+extern NBDKIT_DLLEXPORT void nbdkit_error (const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF (1, 2);
+extern NBDKIT_DLLEXPORT void nbdkit_verror (const char *msg, va_list args);
+extern NBDKIT_DLLEXPORT void nbdkit_debug (const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF (1, 2);
+extern NBDKIT_DLLEXPORT void nbdkit_vdebug (const char *msg, va_list args);
 
-extern char *nbdkit_absolute_path (const char *path);
-extern int64_t nbdkit_parse_size (const char *str);
-extern int nbdkit_parse_bool (const char *str);
-extern int nbdkit_read_password (const char *value, char **password);
-extern char *nbdkit_realpath (const char *path);
+extern NBDKIT_DLLEXPORT char *nbdkit_absolute_path (const char *path);
+extern NBDKIT_DLLEXPORT int64_t nbdkit_parse_size (const char *str);
+extern NBDKIT_DLLEXPORT int nbdkit_parse_bool (const char *str);
+extern NBDKIT_DLLEXPORT int nbdkit_read_password (const char *value, char **password);
+extern NBDKIT_DLLEXPORT char *nbdkit_realpath (const char *path);
 
 /* A static non-NULL pointer which can be used when you don't need a
  * per-connection handle.
diff --git a/include/nbdkit-plugin.h b/include/nbdkit-plugin.h
index d43b2f5..55ccc23 100644
--- a/include/nbdkit-plugin.h
+++ b/include/nbdkit-plugin.h
@@ -127,7 +127,7 @@ struct nbdkit_plugin {
   int (*can_multi_conn) (void *handle);
 };
 
-extern void nbdkit_set_error (int err);
+extern NBDKIT_DLLEXPORT void nbdkit_set_error (int err);
 
 #define NBDKIT_REGISTER_PLUGIN(plugin)                                  \
   NBDKIT_CXX_LANG_C                                                     \
diff --git a/server/internal.h b/server/internal.h
index 21bb4fa..24464b9 100644
--- a/server/internal.h
+++ b/server/internal.h
@@ -37,7 +37,20 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdarg.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
+#endif
+
+#ifdef HAVE_WS2TCPIP_H
+#include <ws2tcpip.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
 #include <pthread.h>
 
 #define NBDKIT_API_VERSION 2
@@ -58,6 +71,36 @@
 #define SOCK_CLOEXEC 0
 #endif
 
+#ifndef O_NOCTTY
+#define O_NOCTTY 0
+#endif
+
+#ifdef WIN32
+
+/* 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
+
+/* Windows doesn't have flockfile/funlockfile so messages may get
+ * split over threads.
+ */
+#define flockfile(fp)
+#define funlockfile(fp)
+
+#endif /* WIN32 */
+
+#ifndef WIN32
+#define HAVE_UNIX_SOCKETS 1
+#else
+#undef HAVE_UNIX_SOCKETS
+#endif
+
 #if HAVE_VALGRIND
 # include <valgrind.h>
 /* http://valgrind.org/docs/manual/faq.html#faq.unhelpful */
@@ -113,7 +156,11 @@ extern struct backend *backend;
 
 /* 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 handle_quit (int sig);
 extern void set_quit (void);
diff --git a/server/crypto.c b/server/crypto.c
index 711e929..3e73ff1 100644
--- a/server/crypto.c
+++ b/server/crypto.c
@@ -162,7 +162,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) {
diff --git a/server/log-syslog.c b/server/log-syslog.c
index 0c58b75..fbaa802 100644
--- a/server/log-syslog.c
+++ b/server/log-syslog.c
@@ -42,6 +42,11 @@
 #include "internal.h"
 #include "syslog.h"
 
+#ifdef WIN32
+/* The Windows API doesn't have open_memstream, so hack this. */
+#define open_memstream(m,n) NULL
+#endif
+
 /* Tempted to use LOG_FTP instead of LOG_DAEMON! */
 static const int PRIORITY = LOG_DAEMON|LOG_ERR;
 
diff --git a/server/main.c b/server/main.c
index 5ba4d0d..475ba68 100644
--- a/server/main.c
+++ b/server/main.c
@@ -42,14 +42,22 @@
 #include <signal.h>
 #include <getopt.h>
 #include <limits.h>
-#include <pwd.h>
-#include <grp.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/wait.h>
 #include <errno.h>
 #include <assert.h>
-#include <syslog.h>
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
 
 #include <pthread.h>
 
@@ -63,7 +71,6 @@
 
 #define FIRST_SOCKET_ACTIVATION_FD 3 /* defined by systemd ABI */
 
-static char *make_random_fifo (void);
 static struct backend *open_plugin_so (size_t i, const char *filename, int short_name);
 static struct backend *open_filter_so (struct backend *next, size_t i, const char *filename, int short_name);
 static void start_serving (void);
@@ -72,8 +79,6 @@ static void run_command (void);
 static void change_user (void);
 static void write_pidfile (void);
 static void fork_into_background (void);
-static uid_t parseuser (const char *);
-static gid_t parsegroup (const char *);
 static unsigned int get_socket_activation (void);
 static int is_config_key (const char *key, size_t len);
 
@@ -106,8 +111,12 @@ bool forked_into_background;
 /* The currently loaded plugin. */
 struct backend *backend;
 
+#ifdef HAVE_UNIX_SOCKETS
+static char *make_random_fifo (void);
 static char *random_fifo_dir = NULL;
 static char *random_fifo = NULL;
+#endif
+static void free_random_fifo (void);
 
 static void
 usage (void)
@@ -167,6 +176,17 @@ main (int argc, char *argv[])
   size_t i;
   const char *magic_config_key;
 
+#ifdef WIN32
+  /* Initialize winsock. 2.2 is the current & latest version. */
+  WSADATA wsaData;
+  int wsaresult;
+  wsaresult = WSAStartup (MAKEWORD (2, 2), &wsaData);
+  if (wsaresult != 0) {
+    fprintf (stderr, "WSAStartup: failed with error %d\n", wsaresult);
+    exit (EXIT_FAILURE);
+  }
+#endif
+
   threadlocal_init ();
 
   /* The default setting for TLS depends on whether we were
@@ -407,6 +427,7 @@ main (int argc, char *argv[])
       }
       break;
 
+#ifdef HAVE_UNIX_SOCKETS
     case 'U':
       if (socket_activation) {
         fprintf (stderr, "%s: cannot use socket activation with -U flag\n",
@@ -421,6 +442,14 @@ main (int argc, char *argv[])
         exit (EXIT_FAILURE);
       break;
 
+#else /* !HAVE_UNIX_SOCKETS */
+    case 'U':
+      fprintf (stderr, "%s: -U option: Unix domain sockets "
+               "are not supported on this platform\n",
+               program_name);
+      exit (EXIT_FAILURE);
+#endif
+
     case 'u':
       user = optarg;
       break;
@@ -670,21 +699,15 @@ main (int argc, char *argv[])
   free (unixsocket);
   free (pidfile);
 
-  if (random_fifo) {
-    unlink (random_fifo);
-    free (random_fifo);
-  }
-
-  if (random_fifo_dir) {
-    rmdir (random_fifo_dir);
-    free (random_fifo_dir);
-  }
+  free_random_fifo ();
 
   crypto_free ();
 
   exit (EXIT_SUCCESS);
 }
 
+#ifdef HAVE_UNIX_SOCKETS
+
 /* Implementation of '-U -' */
 static char *
 make_random_fifo (void)
@@ -717,6 +740,29 @@ make_random_fifo (void)
   return unixsocket;
 }
 
+static void
+free_random_fifo (void)
+{
+  if (random_fifo) {
+    unlink (random_fifo);
+    free (random_fifo);
+  }
+
+  if (random_fifo_dir) {
+    rmdir (random_fifo_dir);
+    free (random_fifo_dir);
+  }
+}
+
+#else /* !HAVE_UNIX_SOCKETS */
+
+static void
+free_random_fifo (void)
+{
+}
+
+#endif /* !HAVE_UNIX_SOCKETS */
+
 static struct backend *
 open_plugin_so (size_t i, const char *name, int short_name)
 {
@@ -886,6 +932,8 @@ start_serving (void)
   free_listening_sockets (socks, nr_socks);
 }
 
+#ifndef WIN32
+
 static void
 set_up_signals (void)
 {
@@ -905,6 +953,23 @@ set_up_signals (void)
   sigaction (SIGPIPE, &sa, NULL);
 }
 
+#else /* WIN32 */
+
+static void
+set_up_signals (void)
+{
+
+  signal (SIGINT, handle_quit);
+  signal (SIGTERM, handle_quit);
+}
+
+#endif /* WIN32 */
+
+#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
+
+static uid_t parseuser (const char *);
+static gid_t parsegroup (const char *);
+
 static void
 change_user (void)
 {
@@ -937,6 +1002,77 @@ change_user (void)
   }
 }
 
+static uid_t
+parseuser (const char *id)
+{
+  struct passwd *pwd;
+  int saved_errno;
+
+  errno = 0;
+  pwd = getpwnam (id);
+
+  if (NULL == pwd) {
+    int val;
+
+    saved_errno = errno;
+
+    if (sscanf (id, "%d", &val) == 1)
+      return val;
+
+    fprintf (stderr, "%s: -u option: %s is not a valid user name or uid",
+             program_name, id);
+    if (saved_errno != 0)
+      fprintf (stderr, " (getpwnam error: %s)", strerror (saved_errno));
+    fprintf (stderr, "\n");
+    exit (EXIT_FAILURE);
+  }
+
+  return pwd->pw_uid;
+}
+
+static gid_t
+parsegroup (const char *id)
+{
+  struct group *grp;
+  int saved_errno;
+
+  errno = 0;
+  grp = getgrnam (id);
+
+  if (NULL == grp) {
+    int val;
+
+    saved_errno = errno;
+
+    if (sscanf (id, "%d", &val) == 1)
+      return val;
+
+    fprintf (stderr, "%s: -g option: %s is not a valid group name or gid",
+             program_name, id);
+    if (saved_errno != 0)
+      fprintf (stderr, " (getgrnam error: %s)", strerror (saved_errno));
+    fprintf (stderr, "\n");
+    exit (EXIT_FAILURE);
+  }
+
+  return grp->gr_gid;
+}
+
+#else /* a platform like Windows which lacks pwd/grp functions */
+
+static void
+change_user (void)
+{
+  if (user || group) {
+    fprintf (stderr, "%s: "
+             "-u and -g options are not available on this plaform\n",
+           program_name);
+    exit (EXIT_FAILURE);
+  }
+}
+
+#endif
+
 static void
 write_pidfile (void)
 {
@@ -967,6 +1103,8 @@ write_pidfile (void)
   debug ("written pidfile %s", pidfile);
 }
 
+#ifndef WIN32
+
 static void
 fork_into_background (void)
 {
@@ -1003,6 +1141,24 @@ fork_into_background (void)
   debug ("forked into background (new pid = %d)", getpid ());
 }
 
+#else /* WIN32 */
+
+static void
+fork_into_background (void)
+{
+  if (foreground)
+    return;
+
+  fprintf (stderr, "%s: cannot fork into background on Windows.\n"
+           "You must use -f or equivalent option.\n",
+           program_name);
+  exit (EXIT_FAILURE);
+}
+
+#endif /* WIN32 */
+
+#ifndef WIN32
+
 static void
 run_command (void)
 {
@@ -1086,61 +1242,22 @@ run_command (void)
   debug ("forked into background (new pid = %d)", getpid ());
 }
 
-static uid_t
-parseuser (const char *id)
-{
-  struct passwd *pwd;
-  int saved_errno;
-
-  errno = 0;
-  pwd = getpwnam (id);
-
-  if (NULL == pwd) {
-    int val;
-
-    saved_errno = errno;
+#else /* !WIN32 */
 
-    if (sscanf (id, "%d", &val) == 1)
-      return val;
-
-    fprintf (stderr, "%s: -u option: %s is not a valid user name or uid",
-             program_name, id);
-    if (saved_errno != 0)
-      fprintf (stderr, " (getpwnam error: %s)", strerror (saved_errno));
-    fprintf (stderr, "\n");
-    exit (EXIT_FAILURE);
-  }
-
-  return pwd->pw_uid;
-}
-
-static gid_t
-parsegroup (const char *id)
+static void
+run_command (void)
 {
-  struct group *grp;
-  int saved_errno;
-
-  errno = 0;
-  grp = getgrnam (id);
-
-  if (NULL == grp) {
-    int val;
+  if (!run)
+    return;
 
-    saved_errno = errno;
-
-    if (sscanf (id, "%d", &val) == 1)
-      return val;
+  fprintf ("%s: cannot use --run on Windows.\n",
+           program_name);
+  exit (EXIT_FAILURE);
+}
 
-    fprintf (stderr, "%s: -g option: %s is not a valid group name or gid",
-             program_name, id);
-    if (saved_errno != 0)
-      fprintf (stderr, " (getgrnam error: %s)", strerror (saved_errno));
-    fprintf (stderr, "\n");
-    exit (EXIT_FAILURE);
-  }
+#endif /* WIN32 */
 
-  return grp->gr_gid;
-}
+#ifndef WIN32
 
 /* Returns 0 if no socket activation, or the number of FDs.
  * See also virGetListenFDs in libvirt.org:src/util/virutil.c
@@ -1199,6 +1316,16 @@ get_socket_activation (void)
   return nr_fds;
 }
 
+#else /* WIN32 */
+
+static unsigned int
+get_socket_activation (void)
+{
+  return 0;
+}
+
+#endif /* WIN32 */
+
 /* When parsing plugin and filter config key=value from the command
  * line, check that the key is a simple alphanumeric with period,
  * underscore or dash.
diff --git a/server/quit.c b/server/quit.c
index c6f829e..edfc3ea 100644
--- a/server/quit.c
+++ b/server/quit.c
@@ -47,8 +47,16 @@
  * a race.
  */
 volatile int quit;
+
+#ifndef WIN32
 int quit_fd;
 static int write_quit_fd;
+#else
+HANDLE quit_fd;
+static HANDLE write_quit_fd;
+#endif
+
+#ifndef WIN32
 
 void
 set_up_quit_pipe (void)
@@ -63,12 +71,47 @@ set_up_quit_pipe (void)
   write_quit_fd = fds[1];
 }
 
+#else /* WIN32 */
+
+/* Non-blocking I/O is not possible on anonymous pipes in Windows, so
+ * we have to create a random named pipe.  See:
+ * https://stackoverflow.com/questions/60645/overlapped-i-o-on-anonymous-pipe
+ */
+void
+set_up_quit_pipe (void)
+{
+  char name[64];
+
+  snprintf (name, sizeof name, "\\\\.\\Pipe\\nbdkit.%lu",
+            GetCurrentProcessId ());
+  quit_fd = CreateNamedPipeA (name, PIPE_ACCESS_INBOUND|FILE_FLAG_OVERLAPPED,
+                              PIPE_TYPE_BYTE|PIPE_WAIT,
+                              1, 4096, 4096, 0, NULL);
+  if (quit_fd == INVALID_HANDLE_VALUE) {
+    fprintf (stderr, "%s: CreateNamedPipeA for read: %s: error %lu\n",
+             program_name, name, GetLastError ());
+    exit (EXIT_FAILURE);
+  }
+
+  write_quit_fd = CreateFileA (name, GENERIC_WRITE, 0, NULL,
+                               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+  if (write_quit_fd == INVALID_HANDLE_VALUE) {
+    fprintf (stderr, "%s: CreateNamedPipeA for write: %s: error %lu\n",
+             program_name, name, GetLastError ());
+    exit (EXIT_FAILURE);
+  }
+}
+
+#endif /* WIN32 */
+
 void
 handle_quit (int sig)
 {
   set_quit ();
 }
 
+#ifndef WIN32
+
 void
 set_quit (void)
 {
@@ -80,3 +123,16 @@ set_quit (void)
   write (write_quit_fd, &c, 1);
 #pragma GCC diagnostic pop
 }
+
+#else /* WIN32 */
+
+void
+set_quit (void)
+{
+  char c = 0;
+
+  quit = 1;
+  WriteFile (write_quit_fd, &c, 1, NULL, NULL);
+}
+
+#endif /* WIN32 */
diff --git a/server/sockets.c b/server/sockets.c
index cc15c5e..eb19a15 100644
--- a/server/sockets.c
+++ b/server/sockets.c
@@ -39,12 +39,32 @@
 #include <string.h>
 #include <unistd.h>
 #include <sys/types.h>
+#include <errno.h>
+#include <assert.h>
+
+#ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
+#endif
+
+#ifdef HAVE_SYS_UN_H
 #include <sys/un.h>
+#endif
+
+#ifdef HAVE_NETDB_H
 #include <netdb.h>
+#endif
+
+#ifdef HAVE_POLL_H
 #include <poll.h>
-#include <errno.h>
-#include <assert.h>
+#endif
+
+#ifdef HAVE_WS2TCPIP_H
+#include <ws2tcpip.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
 
 #ifdef HAVE_SELINUX_SELINUX_H
 #include <selinux/selinux.h>
@@ -86,6 +106,8 @@ clear_selinux_label (void)
 #endif
 }
 
+#ifdef HAVE_UNIX_SOCKETS
+
 int *
 bind_unix_socket (size_t *nr_socks)
 {
@@ -140,6 +162,23 @@ bind_unix_socket (size_t *nr_socks)
   return ret;
 }
 
+#else /* !HAVE_UNIX_SOCKETS */
+
+int *
+bind_unix_socket (size_t *nr_socks)
+{
+  fprintf (stderr, "%s: -U option: Unix domain sockets "
+           "are not supported on this platform\n",
+           program_name);
+  exit (EXIT_FAILURE);
+}
+
+#endif /* !HAVE_UNIX_SOCKETS */
+
+#ifndef AI_ADDRCONFIG
+#define AI_ADDRCONFIG 0
+#endif
+
 int *
 bind_tcpip_socket (size_t *nr_socks)
 {
@@ -314,6 +353,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.
  *
@@ -360,6 +401,41 @@ check_sockets_and_quit_fd (int *socks, size_t nr_socks)
   }
 }
 
+#else /* WIN32 */
+
+static void
+check_sockets_and_quit_fd (int *socks, size_t nr_socks)
+{
+  size_t i;
+  HANDLE handles[nr_socks+1];
+  DWORD r;
+
+  for (i = 0; i < nr_socks; ++i)
+    handles[i] = socks[i];
+  handles[nr_socks] = quit_fd;
+
+  r = WaitForMultipleObjectsEx ((DWORD) (nr_socks+1), handles,
+                                FALSE, INFINITE, TRUE);
+  if (r == WAIT_FAILED) {
+    fprintf (stderr, "%s: WaitForMultipleObjectsEx: error %lu\n",
+             program_name, GetLastError ());
+    exit (EXIT_FAILURE);
+  }
+
+  if (r == WAIT_OBJECT_0 + nr_socks) /* quit_fd signalled. */
+    return;
+
+  if (r >= WAIT_OBJECT_0 && r <= WAIT_OBJECT_0 + nr_socks - 1) {
+    i = r - WAIT_OBJECT_0;
+    accept_connection (socks[i]);
+    return;
+  }
+
+  debug ("WaitForMultipleObjectsEx: unexpected return value: %lu\n", r);
+}
+
+#endif /* WIN32 */
+
 void
 accept_incoming_connections (int *socks, size_t nr_socks)
 {
diff --git a/server/utils.c b/server/utils.c
index d67ec43..ad2726c 100644
--- a/server/utils.c
+++ b/server/utils.c
@@ -41,9 +41,12 @@
 #include <string.h>
 #include <unistd.h>
 #include <limits.h>
-#include <termios.h>
 #include <errno.h>
 
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
 #include "internal.h"
 #include "realpath.h"
 #include "getline.h"
@@ -184,12 +187,72 @@ nbdkit_parse_bool (const char *str)
   return -1;
 }
 
+#ifndef WIN32
+
+typedef struct termios echo_mode;
+
+static void
+echo_off (echo_mode *old_mode)
+{
+  struct termios temp;
+  int tty;
+
+  tty = isatty (0);
+  if (tty) {
+    tcgetattr (0, old_mode);
+    temp = *old_mode;
+    temp.c_lflag &= ~ECHO;
+    tcsetattr (0, TCSAFLUSH, &temp);
+  }
+}
+
+static void
+echo_restore (const echo_mode *old_mode)
+{
+  int tty;
+
+  tty = isatty (0);
+  if (tty)
+    tcsetattr (0, 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 */
+
 /* Read a password from configuration value. */
 int
 nbdkit_read_password (const char *value, char **password)
 {
-  int tty, err;;
-  struct termios orig, temp;
+  int err;
+  echo_mode orig;
   ssize_t r;
   size_t n;
   FILE *fp;
@@ -199,20 +262,13 @@ nbdkit_read_password (const char *value, char **password)
     printf ("password: ");
 
     /* Set no echo. */
-    tty = isatty (0);
-    if (tty) {
-      tcgetattr (0, &orig);
-      temp = orig;
-      temp.c_lflag &= ~ECHO;
-      tcsetattr (0, TCSAFLUSH, &temp);
-    }
+    echo_off (&orig);
 
     r = getline (password, &n, stdin);
     err = errno;
 
     /* Restore echo. */
-    if (tty)
-      tcsetattr (0, TCSAFLUSH, &orig);
+    echo_restore (&orig);
 
     /* Complete the printf above. */
     printf ("\n");
diff --git a/.gitignore b/.gitignore
index 35561e9..ba29b70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
 *.trs
 
 .deps
+.dirstamp
 .gdb_history
 .libs
 Makefile
@@ -55,10 +56,13 @@ Makefile.in
 /ltmain.sh
 /missing
 /nbdkit
+/nbdkit.exe
 /plugins/example4/nbdkit-example4-plugin
 /plugins/tar/nbdkit-tar-plugin
 /podwrapper.pl
+/server/libnbdkit.a
 /server/nbdkit
+/server/nbdkit.exe
 /server/nbdkit.pc
 /server/protostrings.c
 /server/synopsis.c
diff --git a/TODO b/TODO
index 2ef8e1a..5a4e598 100644
--- a/TODO
+++ b/TODO
@@ -131,3 +131,24 @@ nbdkit processes.
 The nbd plugin (plugins/nbd) already contains an NBD client, so we
 could factor this client out and make it available to other plugins to
 use.
+
+Windows support
+---------------
+
+The following features are not yet implemented on Windows:
+
+Because there is no fork(2) we cannot fork into background (ie. -f or
+equivalent must always be used).  --run does not work for a similar
+reason.
+
+-u/-g flags do not work.
+
+No socket activation.  Unclear if this makes sense on Windows, since
+there may not be a way to pass opened file descriptors to a newly
+created process.
+
+Unix domain sockets (ie. -U option) do not work, the clue being in the
+name.  However is there an equivalent filesystem sockets feature in
+Windows that we should be using instead?
+
+Many plugins and filters have not yet been ported to Windows.
diff --git a/server/Makefile.am b/server/Makefile.am
index 80efef5..c6c6e15 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -32,7 +32,9 @@
 
 include $(top_srcdir)/common-rules.mk
 
-EXTRA_DIST = nbdkit.syms
+EXTRA_DIST = \
+	nbdkit.def \
+	nbdkit.syms
 
 sbin_PROGRAMS = nbdkit
 
@@ -86,6 +88,19 @@ if USE_LINKER_SCRIPT_FOR_SERVER
 nbdkit_LDFLAGS += -Wl,--version-script=$(srcdir)/nbdkit.syms
 endif
 
+if IS_WINDOWS
+
+# On Windows, generate an import library so that plugins can link
+# against the executable.
+# https://stackoverflow.com/questions/15454968/dll-plugin-that-uses-functions-defined-in-the-main-executable
+
+lib_LIBRARIES = libnbdkit.a
+
+libnbdkit.a: .libs/nbdkit.exe
+	$(DLLTOOL) -v .libs/nbdkit.exe -D nbdkit.exe -d nbdkit.def -l $@
+
+endif
+
 # protostrings.c is generated from the protocol.h header file where it
 # is used to map NBD protocol flags to strings.
 
diff --git a/server/nbdkit.def b/server/nbdkit.def
new file mode 100644
index 0000000..df6f9a6
--- /dev/null
+++ b/server/nbdkit.def
@@ -0,0 +1,47 @@
+; nbdkit
+; Copyright (C) 2018 Red Hat Inc.
+; All rights reserved.
+;
+; 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.
+;
+; Controls the visibility of symbols on Windows.
+;
+; NOTE: If you edit this file you must also edit nbdkit.syms.
+;
+EXPORTS
+	nbdkit_absolute_path
+	nbdkit_debug
+	nbdkit_error
+	nbdkit_parse_bool
+	nbdkit_parse_size
+	nbdkit_read_password
+	nbdkit_realpath
+	nbdkit_set_error
+	nbdkit_vdebug
+	nbdkit_verror
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 672abd2..f889311 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -36,6 +36,8 @@
 # functions from nbdkit, so this script lists only the symbols we want
 # to export.
 
+# NOTE: If you edit this file you must also edit nbdkit.def.
+
 {
   # The functions we want plugins and filters to call.
   global:
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e96dac9..f13d091 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -183,7 +183,11 @@ test_ansi_c_plugin_la_CFLAGS = \
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 test_ansi_c_plugin_la_LDFLAGS = \
-	-module -avoid-version -shared -rpath /nowhere
+	-module -avoid-version -shared -rpath /nowhere -no-undefined
+if IS_WINDOWS
+test_ansi_c_plugin_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 
 if HAVE_CXX
 # This builds a plugin and a filter using the C++ compiler.  They
@@ -205,7 +209,11 @@ test_cxx_plugin_la_CXXFLAGS = \
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 test_cxx_plugin_la_LDFLAGS = \
-	-module -avoid-version -shared -rpath /nowhere
+	-module -avoid-version -shared -rpath /nowhere -no-undefined
+if IS_WINDOWS
+test_cxx_plugin_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 
 test_cxx_filter_la_SOURCES = \
 	test-cxx-filter.cpp \
@@ -217,7 +225,11 @@ test_cxx_filter_la_CXXFLAGS = \
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 test_cxx_filter_la_LDFLAGS = \
-	-module -avoid-version -shared -rpath /nowhere
+	-module -avoid-version -shared -rpath /nowhere -no-undefined
+if IS_WINDOWS
+test_cxx_filter_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 endif HAVE_CXX
 
 # Exit with parent test.
@@ -533,7 +545,11 @@ libvixDiskLib_la_CXXFLAGS = \
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 libvixDiskLib_la_LDFLAGS = \
-	-shared -version-number 6:0:0 -rpath /nowhere
+	-shared -version-number 6:0:0 -rpath /nowhere -no-undefined
+if IS_WINDOWS
+libvixDiskLib_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 
 # zero plugin test.
 TESTS += test-zero.sh
@@ -695,7 +711,11 @@ test_layers_plugin_la_CFLAGS = $(WARNINGS_CFLAGS)
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 test_layers_plugin_la_LDFLAGS = \
-	-module -avoid-version -shared -rpath /nowhere
+	-module -avoid-version -shared -rpath /nowhere -no-undefined
+if IS_WINDOWS
+test_layers_plugin_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 
 test_layers_filter1_la_SOURCES = \
 	test-layers-filter.c \
@@ -705,7 +725,11 @@ test_layers_filter1_la_CFLAGS = $(WARNINGS_CFLAGS) -Dlayer='"filter1"'
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 test_layers_filter1_la_LDFLAGS = \
-	-module -avoid-version -shared -rpath /nowhere
+	-module -avoid-version -shared -rpath /nowhere -no-undefined
+if IS_WINDOWS
+test_layers_filter1_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 
 test_layers_filter2_la_SOURCES = \
 	test-layers-filter.c \
@@ -715,7 +739,11 @@ test_layers_filter2_la_CFLAGS = $(WARNINGS_CFLAGS) -Dlayer='"filter2"'
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 test_layers_filter2_la_LDFLAGS = \
-	-module -avoid-version -shared -rpath /nowhere
+	-module -avoid-version -shared -rpath /nowhere -no-undefined
+if IS_WINDOWS
+test_layers_filter2_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 
 test_layers_filter3_la_SOURCES = \
 	test-layers-filter.c \
@@ -725,7 +753,11 @@ test_layers_filter3_la_CFLAGS = $(WARNINGS_CFLAGS) -Dlayer='"filter3"'
 # For use of the -rpath option, see:
 # https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html
 test_layers_filter3_la_LDFLAGS = \
-	-module -avoid-version -shared -rpath /nowhere
+	-module -avoid-version -shared -rpath /nowhere -no-undefined
+if IS_WINDOWS
+test_layers_filter3_la_LIBADD = \
+	-L$(top_builddir)/server -lnbdkit
+endif
 
 # blocksize filter test.
 TESTS += test-blocksize.sh
-- 
2.20.1




More information about the Libguestfs mailing list