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 | 11 ++
configure.ac | 28 +++-
common/utils/Makefile.am | 31 +++++
server/Makefile.am | 22 ++++
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 | 74 ++++++++++-
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 | 3 +
README | 49 +++++++
TODO | 35 ++++-
common-rules.mk | 2 +-
25 files changed, 1040 insertions(+), 24 deletions(-)
diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h
index c377e18d..033362e3 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>
@@ -76,7 +82,12 @@ extern "C" {
#define NBDKIT_EXTENT_HOLE (1<<0) /* Same as NBD_STATE_HOLE */
#define NBDKIT_EXTENT_ZERO (1<<1) /* Same as NBD_STATE_ZERO */
+#ifndef WIN32
#define NBDKIT_EXTERN_DECL(ret, fn, args) extern ret fn args
+#else
+#define NBDKIT_EXTERN_DECL(ret, fn, args) \
+ extern __declspec(dllexport) ret fn args
+#endif
NBDKIT_EXTERN_DECL (void, nbdkit_error,
(const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF (1, 2));
diff --git a/configure.ac b/configure.ac
index dd536258..16762f54 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])
@@ -472,6 +483,7 @@ AC_MSG_CHECKING([if the target is Windows])
AS_CASE([$host_os],
[mingw*|msys*], [
is_windows=yes
+ LIBS="$LIBS -lmsvcrt -lkernel32 -luser32"
NO_UNDEFINED_ON_WINDOWS="-no-undefined"
],
[is_windows=no]
@@ -480,12 +492,20 @@ AC_MSG_RESULT([$is_windows])
AC_SUBST([NO_UNDEFINED_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 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])
])
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/Makefile.am b/server/Makefile.am
index d7150f52..11b3042e 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -115,6 +115,28 @@ nbdkit_LDFLAGS += -Wl,--version-script=$(srcdir)/nbdkit.syms
endif
endif
+if IS_WINDOWS
+# On Windows, generate an import library so that plugins can link
+# against the executable.
+#
https://stackoverflow.com/a/18147774
+#
https://sourceforge.net/p/mingw/mailman/mingw-users/thread/43694A03.20505...
+#
https://stackoverflow.com/questions/20007973/how-do-i-generate-an-import-...
+
+lib_LIBRARIES = libnbdkit.a
+
+libnbdkit.a: nbdkit$(EXEEXT) nbdkit.def
+ $(LIBTOOL) --mode=execute \
+ $(DLLTOOL) -v $< -D nbdkit.exe -d nbdkit.def -l $@
+
+nbdkit.def: nbdkit.syms
+ rm -f $@ $@-t
+ echo '; Generated from $<' > $@-t
+ echo 'EXPORTS' >> $@-t
+ $(SED) -n -e 's/.*\(nbdkit_[a-z0-9_]*\);.*/\t\1/p' < $< >> $@-t
+ mv $@-t $@
+ chmod 0444 $@
+endif
+
# synopsis.c is generated from docs/synopsis.txt where it is also
# used to generate the man page. It is included in main.c.
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 78da5ee8..fa5073d6 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
@@ -61,6 +64,7 @@
#include "ascii-string.h"
#include "exit-with-parent.h"
#include "nbd-protocol.h"
+#include "realpath.h"
#include "strndup.h"
#include "syslog.h"
@@ -79,6 +83,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 */
@@ -136,7 +141,25 @@ display_version (void)
static void
dump_config (void)
{
- CLEANUP_FREE char *binary = realpath ("/proc/self/exe", NULL);
+ CLEANUP_FREE char *binary = NULL;
+
+#ifdef __linux__
+ binary = realpath ("/proc/self/exe", NULL);
+#else
+#ifdef WIN32
+ /* GetModuleFileNameA has a crappy interface that prevents us from
+ * getting the length of the path so we just have to guess at an
+ * upper limit here. It will at least truncate it properly with \0.
+ * _get_pgmptr would be a better alternative except that it isn't
+ * implemented in MinGW. XXX
+ */
+ binary = malloc (256);
+ if (!GetModuleFileNameA (NULL, binary, 256)) {
+ free (binary);
+ binary = NULL;
+ }
+#endif
+#endif
if (binary != NULL)
printf ("%s=%s\n", "binary", binary);
@@ -188,6 +211,7 @@ main (int argc, char *argv[])
const char *magic_config_key;
error_if_stdio_closed ();
+ winsock_init ();
#if !ENABLE_LIBFUZZER
threadlocal_init ();
@@ -405,6 +429,16 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
listen_stdin = true;
+#ifdef WIN32
+ /* This could be implemented with a bit of work. The problem
+ * currently is that we try to use recv() on the stdio file
+ * descriptor which winsock does not support (nor Linux in
+ * fact). We would need to implement a test to see if the file
+ * descriptor is a socket or not and use either read or recv as
+ * appropriate.
+ */
+ NOT_IMPLEMENTED_ON_WINDOWS ("-s");
+#endif
break;
case 't':
@@ -728,6 +762,8 @@ main (int argc, char *argv[])
return EXIT_SUCCESS;
}
+#ifndef WIN32
+
/* Implementation of '-U -' */
static char *
make_random_fifo (void)
@@ -760,6 +796,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)
{
@@ -1001,6 +1047,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);
@@ -1010,6 +1057,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
@@ -1024,6 +1072,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) {
@@ -1041,6 +1090,8 @@ switch_stdio (void)
exit (EXIT_FAILURE);
}
}
+#endif
+#ifndef WIN32
close (STDIN_FILENO);
close (STDOUT_FILENO);
if (open ("/dev/null", O_RDONLY) != STDIN_FILENO ||
@@ -1048,4 +1099,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-er...
+# 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..6919a4d7 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
@@ -86,8 +87,10 @@ plugins/*/*.3
/plugins/rust/target
/plugins/tmpdisk/default-command.c
/podwrapper.pl
+/server/libnbdkit.a
/server/local/nbdkit.pc
/server/nbdkit
+/server/nbdkit.def
/server/nbdkit.pc
/server/synopsis.c
/server/test-public
diff --git a/README b/README
index 0e295146..2bf8ce5f 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 --disable-ocaml --disable-perl --disable-vddk
+ 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
------------------
diff --git a/common-rules.mk b/common-rules.mk
index 263dea98..d808db24 100644
--- a/common-rules.mk
+++ b/common-rules.mk
@@ -35,4 +35,4 @@ NULL =
plugindir = $(libdir)/nbdkit/plugins
filterdir = $(libdir)/nbdkit/filters
-CLEANFILES = *~ *.cmi *.cmx *.cmxa *.so
+CLEANFILES = *~ *.cmi *.cmx *.cmxa *.so *.dll
--
2.27.0