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.2050503%... +# https://stackoverflow.com/questions/20007973/how-do-i-generate-an-import-lib... + +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-error... +# 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