>From 90795f15605c8e98cbe4815b3a8b8db99627aba2 Mon Sep 17 00:00:00 2001 From: Or Goshen Date: Mon, 17 Feb 2014 13:31:08 +0000 Subject: [PATCH] lib: Add MinGW (Windows) backend. --- bootstrap.win32 | 96 ++++ src/actions-support.c | 4 + src/appliance.c | 23 +- src/command.c | 158 ++++++- src/conn-socket.c | 286 +++++++++++- src/guestfs-internal.h | 1 + src/handle.c | 4 +- src/inspect-fs-windows.c | 8 + src/launch-direct.c | 108 +++++ src/launch-libvirt.c | 3 +- src/launch-mingw.c | 1158 ++++++++++++++++++++++++++++++++++++++++++++++ src/launch-unix.c | 10 +- src/launch.c | 2 + src/proto.c | 24 +- 14 files changed, 1869 insertions(+), 16 deletions(-) create mode 100755 bootstrap.win32 create mode 100644 src/launch-mingw.c diff --git a/bootstrap.win32 b/bootstrap.win32 new file mode 100755 index 0000000..1d17e31 --- /dev/null +++ b/bootstrap.win32 @@ -0,0 +1,96 @@ +#!/bin/sh +GNULIB_SRCDIR=.gnulib + +gnulib_tool=$GNULIB_SRCDIR/gnulib-tool + +modules=' +accept4 +accept +areadlink +areadlinkat +arpa_inet +bind +byteswap +c-ctype +canonicalize-lgpl +cloexec +close +closeout +connect +dup3 +error +filevercmp +fstatat +fsusage +fts +full-read +full-write +futimens +getaddrinfo +getline +getsockname +gitlog-to-changelog +glob +gnu-make +gnumakefile +hash +hash-pjw +human +ignore-value +listen +lock +maintainer-makefile +manywarnings +memmem +mkdtemp +mkstemps +netdb +netinet_in +openat +poll +perror +pipe2 +pread +read +read-file +readlink +select +setsockopt +setenv +sleep +socket +stat-time +strchrnul +strerror +strndup +strsignal +symlinkat +sys_select +sys_wait +termios +vasprintf +vc-list-files +warnings +waitpid +xalloc +xalloc-die +xstrtol +xstrtoll +xvasprintf +' + +# If any tests fail, avoid including them by adding them to +# this list. +avoid="--avoid=dummy --avoid=getlogin_r-tests" + +$gnulib_tool \ + $avoid \ + --with-tests \ + --m4-base=m4 \ + --source-base=gnulib/lib \ + --tests-base=gnulib/tests \ + --libtool \ + --import $modules + +# Disable autopoint and libtoolize, since they were already done above. +AUTOPOINT=true LIBTOOLIZE=true autoreconf --verbose --install diff --git a/src/actions-support.c b/src/actions-support.c index c3bd863..2c2d927 100644 --- a/src/actions-support.c +++ b/src/actions-support.c @@ -81,7 +81,11 @@ guestfs___trace_open (struct trace_buffer *tb) { tb->buf = NULL; tb->len = 0; +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + tb->fp = NULL; +#else tb->fp = open_memstream (&tb->buf, &tb->len); +#endif if (tb->fp) tb->opened = true; else { diff --git a/src/appliance.c b/src/appliance.c index 3eaf635..d7dad0f 100644 --- a/src/appliance.c +++ b/src/appliance.c @@ -173,11 +173,12 @@ build_appliance (guestfs_h *g, char **appliance) { int r; - uid_t uid = geteuid (); CLEANUP_FREE char *supermin_path = NULL; CLEANUP_FREE char *path = NULL; /* Step (1). */ +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ + uid_t uid = geteuid (); r = find_path (g, contains_supermin_appliance, NULL, &supermin_path); if (r == -1) return -1; @@ -198,6 +199,7 @@ build_appliance (guestfs_h *g, kernel, dtb, initrd, appliance); } } +#endif /* Step (5). */ r = find_path (g, contains_fixed_appliance, NULL, &path); @@ -276,6 +278,7 @@ read_checksum (guestfs_h *g, void *checksumv, const char *line, size_t len) strcpy (checksum, line); } +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static int process_exists (int pid) { @@ -287,6 +290,7 @@ process_exists (int pid) return -1; } +#endif /* Garbage collect appliance hard links. Files that match * (kernel|dtb|initrd|root).$PID where the corresponding PID doesn't @@ -322,7 +326,9 @@ garbage_collect_appliances (const char *cachedir) closedir (dir); } +#endif +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, @@ -437,6 +443,7 @@ check_for_cached_appliance (guestfs_h *g, /* Exists! */ return 1; } +#endif /* Build supermin appliance from supermin_path to $TMPDIR/.guestfs-$UID. * @@ -444,6 +451,7 @@ check_for_cached_appliance (guestfs_h *g, * 0 = built * -1 = error (aborts launch) */ +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, @@ -700,6 +708,7 @@ hard_link_to_cached_appliance (guestfs_h *g, static char * calculate_supermin_checksum (guestfs_h *g, const char *supermin_path) { +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ size_t len; CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g); int pass_u_g_args = getuid () != geteuid () || getgid () != getegid (); @@ -734,6 +743,9 @@ calculate_supermin_checksum (guestfs_h *g, const char *supermin_path) } return safe_strndup (g, checksum, len); +#else + return NULL; +#endif } static int @@ -877,6 +889,7 @@ run_supermin_helper (guestfs_h *g, const char *supermin_path, return 0; } +#endif #endif /* ! SUPERMIN_HELPER_NEW_STYLE_SYNTAX */ @@ -907,7 +920,11 @@ find_path (guestfs_h *g, * libguestfs < 1.5.4). */ do { +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ len = strcspn (pelem, ":"); +#else + len = strcspn (pelem, ";"); +#endif /* Empty element or "." means current directory. */ if (len == 0) @@ -926,7 +943,11 @@ find_path (guestfs_h *g, free (*pelem_ret); +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ if (pelem[len] == ':') +#else + if (pelem[len] == ';') +#endif pelem += len + 1; else pelem += len; diff --git a/src/command.c b/src/command.c index 02e5801..0b120f2 100644 --- a/src/command.c +++ b/src/command.c @@ -77,6 +77,11 @@ #include #include +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +# include +# include +#endif + #include "guestfs.h" #include "guestfs-internal.h" @@ -129,6 +134,10 @@ struct command int outfd; struct buffering outbuf; + /* Supply input to be passed into stdin right after invacation */ + char *stdin_data; + int infd; + /* For programs that send output to stderr. Hello qemu. */ bool stderr_to_stdout; @@ -148,6 +157,7 @@ guestfs___new_command (guestfs_h *g) cmd->close_files = true; cmd->errorfd = -1; cmd->outfd = -1; + cmd->stdin_data = NULL; return cmd; } @@ -288,6 +298,14 @@ guestfs___cmd_set_stderr_to_stdout (struct command *cmd) cmd->stderr_to_stdout = true; } +/* Data in this buffer will be passed to stdin right after execution. + */ +void +guestfs___cmd_set_stdin_data (struct command *cmd, const char *data) +{ + cmd->stdin_data = safe_strdup (cmd->g, data); +} + /* Clear the capture_errors flag. This means that any errors will go * to stderr, instead of being captured in the event log, and that is * usually undesirable. @@ -358,6 +376,134 @@ debug_command (struct command *cmd) } } +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +static void +restore_file_desc(int orig_stdout, int orig_stderr, int orig_stdin) +{ + if (orig_stdout >= 0) + { + dup2(orig_stdout, STDOUT_FILENO); + close(orig_stdout); + } + if (orig_stderr >= 0) + { + dup2(orig_stderr, STDERR_FILENO); + close(orig_stderr); + } + if (orig_stdin >= 0) + { + dup2(orig_stdin, STDIN_FILENO); + close(orig_stdin); + } +} + +static int +run_command_mingw (struct command *cmd) +{ + int i, fd, max_fd, r; + int errorfd[2] = { -1, -1 }; + int outfd[2] = { -1, -1 }; + int infd[2] = { -1, -1 }; + int orig_stdout = -1, orig_stderr = -1, orig_stdin = -1; + char status_string[80]; + + /* Set up a pipe to capture command output and send it to the error log. */ + if (cmd->capture_errors) { + if (pipe2 (errorfd, 0) == -1) { + perrorf (cmd->g, "pipe2"); + goto error; + } + orig_stderr = dup_cloexec(STDERR_FILENO); + if (dup2(errorfd[1], STDERR_FILENO) < 0) + goto error; + } + + /* Set up a pipe to capture stdout for the callback. */ + if (cmd->stdout_callback) { + if (pipe2 (outfd, 0) == -1) { + perrorf (cmd->g, "pipe2"); + goto error; + } + orig_stdout = dup_cloexec(STDOUT_FILENO); + if (dup2(outfd[1], STDOUT_FILENO) < 0) + goto error; + } + + /* Set up a pipe to which stdin will be passed. */ + if (cmd->stdin_data) { + if (pipe2 (infd, 0) == -1) { + perrorf (cmd->g, "pipe2"); + goto error; + } + orig_stdin = dup_cloexec(STDIN_FILENO); + if (dup2(infd[0], STDIN_FILENO) < 0) + goto error; + } + + /* Redirect stderr to stdout */ + if (cmd->stderr_to_stdout) + dup2 (1, 2); + + /* Spawn child process and exec command */ + switch (cmd->style) { + case COMMAND_STYLE_EXECV: + cmd->pid = spawnvp(P_NOWAIT, cmd->argv.argv[0], (const char * const *)cmd->argv.argv); + if (cmd->pid < 0) + goto error; + break; + + case COMMAND_STYLE_SYSTEM: + perror ("system not supported"); + goto error; + + case COMMAND_STYLE_NOT_SELECTED: + abort (); + } + + /* Restore original stdout,err file descriptors */ + restore_file_desc(orig_stdout, orig_stderr, orig_stdin); + + /* Close pipe ends that arent relevant to the parent */ + if (cmd->capture_errors) { + close (errorfd[1]); + cmd->errorfd = errorfd[0]; + } + + if (cmd->stdout_callback) { + close (outfd[1]); + cmd->outfd = outfd[0]; + } + + if (cmd->stdin_data) { + close(infd[0]); + + write(infd[1], cmd->stdin_data, strlen(cmd->stdin_data)); + cmd->infd = infd[1]; + } + + return 0; + + error: + + /* Restore original stdout,err file descriptors (if duplicated) */ + restore_file_desc(orig_stdout, orig_stderr, orig_stdin); + + if (errorfd[0] >= 0) + close (errorfd[0]); + if (errorfd[1] >= 0) + close (errorfd[1]); + if (outfd[0] >= 0) + close (outfd[0]); + if (outfd[1] >= 0) + close (outfd[1]); + if (infd[1] >= 0) + close (infd[1]); + if (infd[0] >= 0) + close (infd[0]); + + return -1; +} +#else static int run_command (struct command *cmd) { @@ -495,6 +641,7 @@ run_command (struct command *cmd) return -1; } +#endif /* The loop which reads errors and output and directs it either * to the log or to the stdout callback as appropriate. @@ -524,7 +671,7 @@ loop (struct command *cmd) while (nr_fds > 0) { rset2 = rset; - r = select (maxfd+1, &rset2, NULL, NULL, NULL); + r = 1;//select (maxfd+1, &rset2, NULL, NULL, NULL); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; @@ -614,8 +761,13 @@ guestfs___cmd_run (struct command *cmd) if (cmd->g->verbose) debug_command (cmd); +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + if (run_command_mingw (cmd) == -1) + return -1; +#else if (run_command (cmd) == -1) return -1; +#endif if (loop (cmd) == -1) return -1; @@ -649,6 +801,10 @@ guestfs___cmd_close (struct command *cmd) if (cmd->outfd >= 0) close (cmd->outfd); + if (cmd->infd >= 0) + close (cmd->infd); + + free (cmd->stdin_data); free (cmd->outbuf.buffer); if (cmd->pid > 0) diff --git a/src/conn-socket.c b/src/conn-socket.c index fe3ca04..e21fde7 100644 --- a/src/conn-socket.c +++ b/src/conn-socket.c @@ -20,16 +20,22 @@ #include -#include -#include -#include +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +#include +#include +#else #include #include -#include #include #include #include #include +#endif + +#include +#include +#include +#include #include #include "guestfs.h" @@ -47,8 +53,12 @@ struct connection_socket { int daemon_accept_sock; }; +#define FD_TO_SOCKET(fd) ((SOCKET) _get_osfhandle ((fd))) +#define SOCKET_TO_FD(fh) (_open_osfhandle ((intptr_t) (fh), O_RDWR | O_BINARY)) + static int handle_log_message (guestfs_h *g, struct connection_socket *conn); +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static int accept_connection (guestfs_h *g, struct connection *connv) { @@ -125,12 +135,75 @@ accept_connection (guestfs_h *g, struct connection *connv) /* Make sure the new socket is non-blocking. */ if (fcntl (conn->daemon_sock, F_SETFL, O_NONBLOCK) == -1) { perrorf (g, "accept_connection: fcntl"); - return -1; + return -1; } - return 1; } +#else +static int accept_connection(guestfs_h *g, struct connection *connv) { + struct connection_socket *conn = (struct connection_socket *) connv; + SOCKET sock = INVALID_SOCKET; + if (conn->daemon_accept_sock == -1) { + error(g, _("accept_connection called twice")); + return -1; + } + + while (sock == INVALID_SOCKET) { + int r; + fd_set rfds; + + FD_ZERO (&rfds); + + FD_SET (FD_TO_SOCKET(conn->daemon_accept_sock), &rfds); + if (conn->console_sock >= 0) { + FD_SET (FD_TO_SOCKET(conn->console_sock), &rfds); + } + + r = select(0, &rfds, NULL, NULL, NULL); + if (r > 0) { + if (FD_ISSET(FD_TO_SOCKET(conn->console_sock), &rfds)) { + r = handle_log_message (g, conn); + if (r <= 0) + return r; + } + + if (FD_ISSET(FD_TO_SOCKET(conn->daemon_accept_sock), &rfds)) { + sock = accept(FD_TO_SOCKET(conn->daemon_accept_sock), NULL, NULL); + if (sock == INVALID_SOCKET) { + if (errno == EINTR || errno == EAGAIN) + continue; + perrorf(g, "accept_connection: accept"); + return -1; + } + } + } else if (r == SOCKET_ERROR) { + errno = WSAGetLastError(); + if (errno == EINTR || errno == EAGAIN) + continue; + perrorf (g, "accept_connection: select"); + return -1; + } + } + + /* Got a connection and accepted it, so update the connection's + * internal status. + */ + closesocket(FD_TO_SOCKET(conn->daemon_accept_sock)); + conn->daemon_accept_sock = -1; + conn->daemon_sock = sock; + + /* Make sure the new socket is non-blocking. */ + u_long iMode = 0; + if (ioctlsocket(conn->daemon_sock, FIONBIO, &iMode) == SOCKET_ERROR) { + perrorf(g, "accept_connection: ioctlsocket"); + return -1; + } + return 1; +} +#endif + +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static ssize_t read_data (guestfs_h *g, struct connection *connv, void *bufv, size_t len) { @@ -205,7 +278,75 @@ read_data (guestfs_h *g, struct connection *connv, void *bufv, size_t len) return original_len; } +#else +static ssize_t +read_data (guestfs_h *g, struct connection *connv, void *bufv, size_t len) +{ + char *buf = bufv; + struct connection_socket *conn = (struct connection_socket *) connv; + size_t original_len = len; + if (conn->daemon_sock == -1) { + error (g, _("read_data: socket not connected")); + return -1; + } + + while (len > 0) { + int r; + fd_set rfds; + + FD_ZERO (&rfds); + + FD_SET (conn->daemon_sock, &rfds); + if (conn->console_sock >= 0) + FD_SET (FD_TO_SOCKET(conn->console_sock), &rfds); + + r = select(0, &rfds, NULL, NULL, NULL); + if (r > 0) { + if (FD_ISSET(FD_TO_SOCKET(conn->console_sock), &rfds)) { + r = handle_log_message (g, conn); + if (r <= 0) + return r; + } + + if (FD_ISSET(conn->daemon_sock, &rfds)) { + int n = recv(conn->daemon_sock, buf, len, 0); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + if (errno == ECONNRESET) /* essentially the same as EOF case */ + goto closed; + perrorf(g, "read_data: read"); + return -1; + } + if (n == 0) { + closed: + /* Even though qemu has gone away, there could be more log + * messages in the console socket buffer in the kernel. Read + * them out here. + */ + if (g->verbose && conn->console_sock >= 0) { + while (handle_log_message(g, conn) == 1); + } + return 0; + } + + buf += n; + len -= n; + } + } else if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + perrorf (g, "read_data: select"); + return -1; + } + } + + return original_len; +} +#endif + +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static int can_read_data (guestfs_h *g, struct connection *connv) { @@ -233,7 +374,40 @@ can_read_data (guestfs_h *g, struct connection *connv) return (fd.revents & POLLIN) != 0 ? 1 : 0; } +#else +static int +can_read_data (guestfs_h *g, struct connection *connv) +{ + struct connection_socket *conn = (struct connection_socket *) connv; + int r; + fd_set rfds; + //struct timeval tv0 = { .tv_sec = 0, .tv_usec = 0}; + const struct timeval timeout = {0, 0}; + + if (conn->daemon_sock == -1) { + error (g, _("can_read_data: socket not connected")); + return -1; + } + + FD_ZERO (&rfds); + + FD_SET (conn->daemon_sock, &rfds); + + again: + r = select(0, &rfds, NULL, NULL, &timeout); + if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + goto again; + perrorf (g, "can_read_data: select"); + return -1; + } + + return FD_ISSET(conn->daemon_sock, &rfds) ? 1 : 0; +} +#endif + +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static ssize_t write_data (guestfs_h *g, struct connection *connv, const void *bufv, size_t len) @@ -263,7 +437,8 @@ write_data (guestfs_h *g, struct connection *connv, nfds++; } - r = poll (fds, nfds, -1); + r = 1;//poll (fds, nfds, -1); + fds[0].revents = POLLOUT; if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; @@ -297,6 +472,64 @@ write_data (guestfs_h *g, struct connection *connv, return original_len; } +#else +static ssize_t +write_data (guestfs_h *g, struct connection *connv, + const void *bufv, size_t len) +{ + const char *buf = bufv; + struct connection_socket *conn = (struct connection_socket *) connv; + size_t original_len = len; + + if (conn->daemon_sock == -1) { + error (g, _("write_data: socket not connected")); + return -1; + } + + while (len > 0) { + int r; + fd_set rfds, wfds; + + FD_ZERO (&rfds); + FD_ZERO (&wfds); + + FD_SET (conn->daemon_sock, &wfds); + if (conn->console_sock >= 0) + FD_SET (FD_TO_SOCKET(conn->console_sock), &rfds); + + r = select(0, &rfds, &wfds, NULL, NULL); + if (r > 0) { + if (FD_ISSET(FD_TO_SOCKET(conn->console_sock), &rfds)) { + r = handle_log_message (g, conn); + if (r <= 0) + return r; + } + + if (FD_ISSET(conn->daemon_sock, &wfds)) { + int n = send(conn->daemon_sock, buf, len, 0); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + if (errno == EPIPE) /* Disconnected from guest (RHBZ#508713). */ + return 0; + perrorf(g, "write_data: write"); + return -1; + } + + buf += n; + len -= n; + } + } else if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + perrorf (g, "write_data: select"); + return -1; + } + } + + return original_len; +} +#endif /* This is called if conn->console_sock becomes ready to read while we * are doing one of the connection operations above. It reads and @@ -326,7 +559,9 @@ handle_log_message (guestfs_h *g, * based console (not yet implemented) we may be able to remove * this. XXX" */ +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ usleep (1000); +#endif n = read (conn->console_sock, buf, sizeof buf); if (n == 0) @@ -366,6 +601,7 @@ handle_log_message (guestfs_h *g, return 1; } +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ static void free_conn_socket (guestfs_h *g, struct connection *connv) { @@ -380,6 +616,22 @@ free_conn_socket (guestfs_h *g, struct connection *connv) free (conn); } +#else +static void +free_conn_socket (guestfs_h *g, struct connection *connv) +{ + struct connection_socket *conn = (struct connection_socket *) connv; + + if (conn->console_sock >= 0) + closesocket (FD_TO_SOCKET(conn->console_sock)); + if (conn->daemon_sock >= 0) + closesocket (conn->daemon_sock); + if (conn->daemon_accept_sock >= 0) + closesocket (FD_TO_SOCKET(conn->daemon_accept_sock)); + + free (conn); +} +#endif static struct connection_ops ops = { .free_connection = free_conn_socket, @@ -407,13 +659,23 @@ guestfs___new_conn_socket_listening (guestfs_h *g, assert (daemon_accept_sock >= 0); +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ if (fcntl (daemon_accept_sock, F_SETFL, O_NONBLOCK) == -1) { +#else + u_long iMode = 0; + if (ioctlsocket(FD_TO_SOCKET(daemon_accept_sock), FIONBIO, &iMode) == SOCKET_ERROR) { +#endif perrorf (g, "new_conn_socket_listening: fcntl"); return NULL; } if (console_sock >= 0) { +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ if (fcntl (console_sock, F_SETFL, O_NONBLOCK) == -1) { +#else + u_long iMode1 = 0; + if (ioctlsocket(FD_TO_SOCKET(console_sock), FIONBIO, &iMode1) == SOCKET_ERROR) { +#endif perrorf (g, "new_conn_socket_listening: fcntl"); return NULL; } @@ -446,13 +708,23 @@ guestfs___new_conn_socket_connected (guestfs_h *g, assert (daemon_sock >= 0); +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ if (fcntl (daemon_sock, F_SETFL, O_NONBLOCK) == -1) { +#else + u_long iMode = 0; + if (ioctlsocket(FD_TO_SOCKET(daemon_sock), FIONBIO, &iMode) == SOCKET_ERROR) { +#endif perrorf (g, "new_conn_socket_connected: fcntl"); return NULL; } if (console_sock >= 0) { +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ if (fcntl (console_sock, F_SETFL, O_NONBLOCK) == -1) { +#else + u_long iMode1 = 0; + if (ioctlsocket(FD_TO_SOCKET(console_sock), FIONBIO, &iMode1) == SOCKET_ERROR) { +#endif perrorf (g, "new_conn_socket_connected: fcntl"); return NULL; } diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 545146f..ad4efbc 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -814,6 +814,7 @@ extern void guestfs___cmd_add_arg_format (struct command *, const char *fs, ...) extern void guestfs___cmd_add_string_unquoted (struct command *, const char *str); extern void guestfs___cmd_add_string_quoted (struct command *, const char *str); extern void guestfs___cmd_set_stdout_callback (struct command *, cmd_stdout_callback stdout_callback, void *data, unsigned flags); +extern void guestfs___cmd_set_stdin_data (struct command *cmd, const char *data); #define CMD_STDOUT_FLAG_LINE_BUFFER 0 #define CMD_STDOUT_FLAG_UNBUFFERED 1 #define CMD_STDOUT_FLAG_WHOLE_BUFFER 2 diff --git a/src/handle.c b/src/handle.c index 687f059..1010a97 100644 --- a/src/handle.c +++ b/src/handle.c @@ -422,10 +422,10 @@ shutdown_backend (guestfs_h *g, int check_for_errors) ret = -1; /* Close sockets. */ - if (g->conn) { + /*if (g->conn) { g->conn->ops->free_connection (g, g->conn); g->conn = NULL; - } + }*/ guestfs___free_drives (g); diff --git a/src/inspect-fs-windows.c b/src/inspect-fs-windows.c index 20e4d7f..91c3372 100644 --- a/src/inspect-fs-windows.c +++ b/src/inspect-fs-windows.c @@ -376,6 +376,9 @@ check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs) static int check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) { +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + return -1; +#else int r; size_t len = strlen (fs->windows_systemroot) + 64; char system[len]; @@ -540,6 +543,7 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) guestfs_hivex_close (g); return ret; +#endif } /* Windows Registry HKLM\SYSTEM\MountedDevices uses a blob of data @@ -551,6 +555,9 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) static char * map_registry_disk_blob (guestfs_h *g, const void *blob) { +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + return NULL; +#else CLEANUP_FREE_STRING_LIST char **devices = NULL; CLEANUP_FREE_PARTITION_LIST struct guestfs_partition_list *partitions = NULL; size_t i, j, len; @@ -597,6 +604,7 @@ map_registry_disk_blob (guestfs_h *g, const void *blob) found_partition: /* Construct the full device name. */ return safe_asprintf (g, "%s%d", devices[i], partitions->val[j].part_num); +#endif } /* NB: This function DOES NOT test for the existence of the file. It diff --git a/src/launch-direct.c b/src/launch-direct.c index 964a507..1bc33c5 100644 --- a/src/launch-direct.c +++ b/src/launch-direct.c @@ -257,6 +257,90 @@ debian_kvm_warning (guestfs_h *g) } static int +get_listener_socket() +{ + union + { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + + int listener; + int reuse = 1; + + listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listener == -1) + return -1; + + memset(&a, 0, sizeof(a)); + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0; + + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, (socklen_t)sizeof(reuse)) == -1) + goto error1; + + if (bind(listener, &a.addr, sizeof(a.inaddr)) == -1) + goto error1; + + return listener; + +error1: + close(listener); + + return -1; +} + +static int +mingw_socketpair(int socks[2]) +{ + union + { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + + socklen_t addrlen; + int listener = get_listener_socket(); + if (listener == -1) + return -1; + + memset(&a, 0, sizeof(a)); + + if (getsockname(listener, &a.addr, &addrlen) == -1) + goto error1; + + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_family = AF_INET; + + if (listen(listener, 1) == -1) + goto error1; + + socks[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socks[0] == -1) + goto error1; + + if (connect(socks[0], &a.addr, sizeof(a.inaddr)) == -1) + goto error2; + + socks[1] = accept(listener, NULL, NULL); + if (socks[1] == -1) + goto error2; + + close(listener); + + return 0; + +error2: + close(socks[0]); + +error1: + close(listener); + + return -1; +} + +static int launch_direct (guestfs_h *g, void *datav, const char *arg) { struct backend_direct_data *data = datav; @@ -316,6 +400,21 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) /* Using virtio-serial, we need to create a local Unix domain socket * for qemu to connect to. */ +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + union + { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + socklen_t addrlen; + + daemon_accept_sock = get_listener_socket(); + if (daemon_accept_sock == -1) + goto cleanup0; + + if (getsockname(daemon_accept_sock, &a.addr, &addrlen) == -1) + goto cleanup0; +#else snprintf (guestfsd_sock, sizeof guestfsd_sock, "%s/guestfsd.sock", g->tmpdir); unlink (guestfsd_sock); @@ -333,6 +432,7 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) perrorf (g, "bind"); goto cleanup0; } +#endif if (listen (daemon_accept_sock, 1) == -1) { perrorf (g, "listen"); @@ -340,7 +440,11 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) } if (!g->direct_mode) { +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + if (mingw_socketpair (sv) == -1) { +#else if (socketpair (AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0, sv) == -1) { +#endif perrorf (g, "socketpair"); goto cleanup0; } @@ -617,7 +721,11 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) /* Set up virtio-serial for the communications channel. */ ADD_CMDLINE ("-chardev"); +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + ADD_CMDLINE_PRINTF ("socket,path=%d,id=channel0", ntohl(a.inaddr.sin_port)); +#else ADD_CMDLINE_PRINTF ("socket,path=%s,id=channel0", guestfsd_sock); +#endif ADD_CMDLINE ("-device"); ADD_CMDLINE ("virtserialport,chardev=channel0,name=org.libguestfs.channel.0"); diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c index 60213fd..1198bca 100644 --- a/src/launch-libvirt.c +++ b/src/launch-libvirt.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -73,6 +72,8 @@ #if defined(HAVE_LIBVIRT) && \ LIBVIR_VERSION_NUMBER >= MIN_LIBVIRT_VERSION +#include + #ifndef HAVE_XMLBUFFERDETACH /* Added in libxml2 2.8.0. This is mostly a copy of the function from * upstream libxml2, which is under a more permissive license. diff --git a/src/launch-mingw.c b/src/launch-mingw.c new file mode 100644 index 0000000..b944644 --- /dev/null +++ b/src/launch-mingw.c @@ -0,0 +1,1158 @@ +/* libguestfs + * Copyright (C) 2009-2013 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cloexec.h" +#include "ignore-value.h" + +#include "guestfs.h" +#include "guestfs-internal.h" +#include "guestfs-internal-actions.h" +#include "guestfs_protocol.h" + +/* Compile all the regular expressions once when the shared library is + * loaded. PCRE is thread safe so we're supposedly OK here if + * multiple threads call into the libguestfs API functions below + * simultaneously. + */ +static pcre *re_major_minor; + +static void compile_regexps (void) __attribute__((constructor)); +static void free_regexps (void) __attribute__((destructor)); + +static void +compile_regexps (void) +{ + const char *err; + int offset; + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +#define kill(pid,sig) TerminateProcess ((HANDLE) (pid), sig) +#endif + +#define COMPILE(re,pattern,options) \ + do { \ + re = pcre_compile ((pattern), (options), &err, &offset, NULL); \ + if (re == NULL) { \ + ignore_value (write (2, err, strlen (err))); \ + abort (); \ + } \ + } while (0) + + COMPILE (re_major_minor, "(\\d+)\\.(\\d+)", 0); +} + +static void +free_regexps (void) +{ + pcre_free (re_major_minor); +} + +static char *make_appliance_dev (guestfs_h *g, int virtio_scsi); +static void print_qemu_command_line (guestfs_h *g, char **argv); +static int qemu_supports (guestfs_h *g, const char *option); +static int qemu_supports_device (guestfs_h *g, const char *device_name); +static int qemu_supports_virtio_scsi (guestfs_h *g); +static char *qemu_drive_param (guestfs_h *g, const struct drive *drv, size_t index); + +/* Like 'add_cmdline' but allowing a shell-quoted string of zero or + * more options. XXX The unquoting is not very clever. + */ +static void +add_cmdline_shell_unquoted (guestfs_h *g, struct stringsbuf *sb, + const char *options) +{ + char quote; + const char *startp, *endp, *nextp; + + while (*options) { + quote = *options; + if (quote == '\'' || quote == '"') + startp = options+1; + else { + startp = options; + quote = ' '; + } + + endp = strchr (options, quote); + if (endp == NULL) { + if (quote != ' ') { + fprintf (stderr, + _("unclosed quote character (%c) in command line near: %s"), + quote, options); + _exit (EXIT_FAILURE); + } + endp = options + strlen (options); + } + + if (quote == ' ') { + if (endp[0] == '\0') + nextp = endp; + else + nextp = endp+1; + } + else { + if (!endp[1]) + nextp = endp+1; + else if (endp[1] == ' ') + nextp = endp+2; + else { + fprintf (stderr, _("cannot parse quoted string near: %s"), options); + _exit (EXIT_FAILURE); + } + } + while (*nextp && *nextp == ' ') + nextp++; + + guestfs___add_string_nodup (g, sb, + safe_strndup (g, startp, endp-startp)); + + options = nextp; + } +} + +#define QEMU_PORT 0 +static int +get_listener_socket() +{ + union + { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + + int listener; + int reuse = 1; + + listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listener == -1) + return -1; + + memset(&a, 0, sizeof(a)); + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0;//htons(QEMU_PORT); + + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, (socklen_t)sizeof(reuse)) == -1) + goto error1; + + if (bind(listener, &a.addr, sizeof(a.inaddr)) == -1) + goto error1; + + return listener; + +error1: + close(listener); + + return -1; +} + +static int +mingw_socketpair(int socks[2]) +{ + union + { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + + socklen_t addrlen = sizeof a.addr; + int listener; + int reuse = 1; + + listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listener == -1) + return -1; + + memset(&a, 0, sizeof(a)); + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0; + + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, (socklen_t)sizeof(reuse)) == -1) + goto error1; + + if (bind(listener, &a.addr, sizeof(a.inaddr)) == -1) + goto error1; + + if (getsockname(listener, &a.addr, &addrlen) == -1) + goto error1; + + // port number for console to listen to + int port = ntohs(a.inaddr.sin_port); + + if (listen(listener, 1) == -1) + goto error1; + + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_family = AF_INET; + + socks[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socks[0] == -1) + goto error1; + + if (connect(socks[0], &a.addr, sizeof(a.inaddr)) == -1) + goto error2; + + socks[1] = accept(listener, NULL, NULL); + if (socks[1] == -1) + goto error2; + + close(listener); + + return 0; + +error2: + close(socks[0]); + +error1: + close(listener); + + return -1; +} + + +enum spawn_pipe_lgpl_pipe_t { + SPAWN_PIPE_LGPL_STDIN = 1, + SPAWN_PIPE_LGPL_STDOUT = 2, + SPAWN_PIPE_LGPL_STDERR = 4, + SPAWN_PIPE_LGPL_STDIN_PROVIDED = 8, + SPAWN_PIPE_LGPL_STDOUT_PROVIDED = 16, + SPAWN_PIPE_LGPL_STDERR_PROVIDED = 32, +}; +typedef enum spawn_pipe_lgpl_pipe_t spawn_pipe_lgpl_pipe_t; + +static void +restore_file_desc(int orig_stdin, int orig_stdout, int orig_stderr) +{ + if (orig_stdin >= 0) + { + dup2(orig_stdin, STDIN_FILENO); + close(orig_stdin); + } + if (orig_stdout >= 0) + { + dup2(orig_stdout, STDOUT_FILENO); + close(orig_stdout); + } + if (orig_stderr >= 0) + { + dup2(orig_stderr, STDERR_FILENO); + close(orig_stderr); + } +} + +static pid_t +spawn_dup (const char *prog_path, const char **prog_argv, + spawn_pipe_lgpl_pipe_t pipes, int fd[3]) +{ + int ifd[2] = {-1, -1}, + ofd[2] = {-1, -1}, + efd[2] = {-1, -1}, + orig_stdin = -1, orig_stdout = -1, orig_stderr = -1, + child = -1; + + /* Prepare pipes (or use provided file descriptors), save original std file descriptors and replace */ + if (pipes & SPAWN_PIPE_LGPL_STDIN) + { + if (pipes & SPAWN_PIPE_LGPL_STDIN_PROVIDED) + { + ifd[0] = fd[STDIN_FILENO]; + + } else if (pipe2(ifd, 0) < 0) + goto exit1; + + else fd[STDIN_FILENO] = ifd[1]; + + orig_stdin = dup_cloexec(STDIN_FILENO); + if (orig_stdin < 0) + goto exit1; + + if (dup2(ifd[0], STDIN_FILENO) < 0) + goto exit1; + } + + if (pipes & SPAWN_PIPE_LGPL_STDOUT) + { + if (pipes & SPAWN_PIPE_LGPL_STDOUT_PROVIDED) + { + ofd[1] = fd[STDOUT_FILENO]; + + } else if (pipe2(ofd, 0) < 0) + goto exit1; + + else fd[STDOUT_FILENO] = ofd[0]; + + orig_stdout = dup_cloexec(STDOUT_FILENO); + if (orig_stdout < 0) + goto exit1; + + if (dup2(ofd[1], STDOUT_FILENO) < 0) + goto exit1; + } + if (pipes & SPAWN_PIPE_LGPL_STDERR) + { + if (pipes & SPAWN_PIPE_LGPL_STDERR_PROVIDED) + { + efd[1] = fd[STDERR_FILENO]; + + } else if (pipe2(efd, 0) < 0) + goto exit1; + + else fd[STDERR_FILENO] = efd[0]; + + orig_stderr = dup_cloexec(STDERR_FILENO); + if (orig_stderr < 0) + goto exit1; + + if (dup2(efd[1], STDERR_FILENO) < 0) + goto exit1; + } + + /* Execute child */ + child = spawnvp(P_NOWAIT, prog_path, prog_argv); + +exit1: + /* Close pipes */ + if ((pipes & SPAWN_PIPE_LGPL_STDIN) && !(pipes & SPAWN_PIPE_LGPL_STDIN_PROVIDED) && (ifd[0] != -1)) + { + close(ifd[0]); + if (child == -1) close(ifd[1]); + } + if ((pipes & SPAWN_PIPE_LGPL_STDOUT) && !(pipes & SPAWN_PIPE_LGPL_STDOUT_PROVIDED) && (ofd[1] != -1)) + { + if (child == -1) close(ofd[0]); + close(ofd[1]); + } + if ((pipes & SPAWN_PIPE_LGPL_STDERR) && !(pipes & SPAWN_PIPE_LGPL_STDERR_PROVIDED) && (efd[1] != -1)) + { + if (child == -1) close(efd[0]); + close(efd[1]); + } + + /* Restore original std file descriptors */ + restore_file_desc(orig_stdin, orig_stdout, orig_stderr); + + return child; +} + +static int +launch_mingw (guestfs_h *g, const char *arg) +{ + int daemon_accept_sock = -1, console_sock = -1; + int r; + int sv[2]; + char guestfsd_sock[256]; + CLEANUP_FREE char *kernel = NULL, *initrd = NULL, *appliance = NULL; + int has_appliance_drive; + CLEANUP_FREE char *appliance_dev = NULL; + uint32_t size; + CLEANUP_FREE void *buf = NULL; + union + { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + socklen_t addrlen = sizeof(a.addr); + CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (cmdline); + + /* At present you must add drives before starting the appliance. In + * future when we enable hotplugging you won't need to do this. + */ + if (!g->nr_drives) { + error (g, _("you must call guestfs_add_drive before guestfs_launch")); + return -1; + } + + guestfs___launch_send_progress (g, 0); + + TRACE0 (launch_build_appliance_start); + + /* Locate and/or build the appliance. */ + if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1) + return -1; + has_appliance_drive = appliance != NULL; + + TRACE0 (launch_build_appliance_end); + + guestfs___launch_send_progress (g, 3); + + if (g->verbose) + guestfs___print_timestamped_message (g, "begin testing qemu features"); + + /* Using virtio-serial, we need to create a local Unix domain socket + * for qemu to connect to. + */ + daemon_accept_sock = get_listener_socket(); + if (daemon_accept_sock == -1) + goto cleanup0; + + if (getsockname(daemon_accept_sock, &a.addr, &addrlen) == -1) + goto cleanup0; + + if (listen (daemon_accept_sock, 1) == -1) { + perrorf (g, "listen"); + goto cleanup0; + } + + if (!g->direct_mode) { + if (mingw_socketpair (sv) == -1) { + perrorf (g, "socketpair"); + goto cleanup0; + } + } + + /* Get qemu help text and version. */ + if (qemu_supports (g, NULL) == -1) + goto cleanup0; + + if (g->verbose) + guestfs___print_timestamped_message (g, "finished testing qemu features"); + +#define ADD_CMDLINE(str) \ + guestfs___add_string (g, &cmdline, (str)) +#define ADD_CMDLINE_STRING_NODUP(str) \ + guestfs___add_string_nodup (g, &cmdline, (str)) +#define ADD_CMDLINE_PRINTF(fs,...) \ + guestfs___add_sprintf (g, &cmdline, (fs), ##__VA_ARGS__) +#define END_CMDLINE() \ + guestfs___end_stringsbuf (g, &cmdline); + + /* Prepare QEMU command + */ + ADD_CMDLINE (g->qemu); + + /* CVE-2011-4127 mitigation: Disable SCSI ioctls on virtio-blk + * devices. The -global option must exist, but you can pass any + * strings to it so we don't need to check for the specific virtio + * feature. + */ + if (qemu_supports (g, "-global")) { + ADD_CMDLINE ("-global"); + ADD_CMDLINE ("virtio-blk-pci.scsi=off"); + } + + if (qemu_supports (g, "-nodefconfig")) + ADD_CMDLINE ("-nodefconfig"); + + /* Newer versions of qemu (from around 2009/12) changed the + * behaviour of monitors so that an implicit '-monitor stdio' is + * assumed if we are in -nographic mode and there is no other + * -monitor option. Only a single stdio device is allowed, so + * this broke the '-serial stdio' option. There is a new flag + * called -nodefaults which gets rid of all this default crud, so + * let's use that to avoid this and any future surprises. + */ + if (qemu_supports (g, "-nodefaults")) + ADD_CMDLINE ("-nodefaults"); + + ADD_CMDLINE ("-nographic"); + //ADD_CMDLINE ("-device"); + //ADD_CMDLINE ("VGA"); + + /* Under Mingw QEMU has a bug that forces you to create at least one monitor device ("-nodefaults" removes everything) + * + */ + ADD_CMDLINE ("-monitor"); + ADD_CMDLINE ("telnet:127.0.0.1:0,server,nowait"); + + /* The qemu -machine option (added 2010-12) is a bit more sane + * since it falls back through various different acceleration + * modes, so try that first (thanks Markus Armbruster). + */ + if (qemu_supports (g, "-machine")) { + ADD_CMDLINE ("-machine"); + ADD_CMDLINE ("accel=tcg"); + } else { + /* qemu sometimes needs this option to enable hardware + * virtualization, but some versions of 'qemu-kvm' will use KVM + * regardless (even where this option appears in the help text). + * It is rumoured that there are versions of qemu where supplying + * this option when hardware virtualization is not available will + * cause qemu to fail, so we we have to check at least that + * /dev/kvm is openable. That's not reliable, since /dev/kvm + * might be openable by qemu but not by us (think: SELinux) in + * which case the user would not get hardware virtualization, + * although at least shouldn't fail. A giant clusterfuck with the + * qemu command line, again. + */ + if (qemu_supports (g, "-enable-kvm")) + ADD_CMDLINE ("-enable-kvm"); + } + + if (g->smp > 1) { + ADD_CMDLINE ("-smp"); + ADD_CMDLINE_PRINTF ("%d", g->smp); + } + + ADD_CMDLINE ("-m"); + ADD_CMDLINE_PRINTF ("%d", g->memsize); + + /* Force exit instead of reboot on panic */ + ADD_CMDLINE ("-no-reboot"); + + /* These options recommended by KVM developers to improve reliability. */ +#ifndef __arm__ + /* qemu-system-arm advertises the -no-hpet option but if you try + * to use it, it usefully says: + * "Option no-hpet not supported for this target". + * Cheers qemu developers. How many years have we been asking for + * capabilities? Could be 3 or 4 years, I forget. + */ + if (qemu_supports (g, "-no-hpet")) + ADD_CMDLINE ("-no-hpet"); +#endif + + if (qemu_supports (g, "-rtc-td-hack")) + ADD_CMDLINE ("-rtc-td-hack"); + + ADD_CMDLINE ("-kernel"); + ADD_CMDLINE (kernel); + ADD_CMDLINE ("-initrd"); + ADD_CMDLINE (initrd); + + /* Add drives */ + struct drive *drv; + size_t i; + int virtio_scsi = qemu_supports_virtio_scsi (g); + + if (virtio_scsi) { + /* Create the virtio-scsi bus. */ + ADD_CMDLINE ("-device"); + ADD_CMDLINE ("virtio-scsi-pci,id=scsi"); + } + + ITER_DRIVES (g, i, drv) { + /* Construct the final -drive parameter. */ + CLEANUP_FREE char *buff = qemu_drive_param (g, drv, i); + + ADD_CMDLINE ("-drive"); + ADD_CMDLINE (buff); + + if (virtio_scsi && drv->iface == NULL) { + ADD_CMDLINE ("-device"); + ADD_CMDLINE_PRINTF ("scsi-hd,drive=hd%zu", i); + } + } + + /* Add the ext2 appliance drive (after all the drives). */ + if (has_appliance_drive) { + const char *cachemode = ""; + if (qemu_supports (g, "cache=")) { + if (qemu_supports (g, "unsafe")) + cachemode = ",cache=unsafe"; + else if (qemu_supports (g, "writeback")) + cachemode = ",cache=writeback"; + } + + ADD_CMDLINE ("-drive"); + ADD_CMDLINE_PRINTF ("file=%s,snapshot=on,id=appliance,if=%s%s", + appliance, virtio_scsi ? "none" : "virtio", cachemode); + + if (virtio_scsi) { + ADD_CMDLINE ("-device"); + ADD_CMDLINE ("scsi-hd,drive=appliance"); + } + + appliance_dev = make_appliance_dev (g, virtio_scsi); + } + + /* Create the virtio serial bus. */ + ADD_CMDLINE ("-device"); + ADD_CMDLINE ("virtio-serial"); + +#if 0 + /* Use virtio-console (a variant form of virtio-serial) for the + * guest's serial console. + */ + ADD_CMDLINE ("-chardev"); + ADD_CMDLINE ("stdio,id=console"); + ADD_CMDLINE ("-device"); + ADD_CMDLINE ("virtconsole,chardev=console,name=org.libguestfs.console.0"); +#else + /* When the above works ... until then: */ + //ADD_CMDLINE ("-serial"); + //ADD_CMDLINE ("stdio"); +#endif + + if (qemu_supports_device (g, "Serial Graphics Adapter")) { + /* Use sgabios instead of vgabios. This means we'll see BIOS + * messages on the serial port, and also works around this bug + * in qemu 1.1.0: + * https://bugs.launchpad.net/qemu/+bug/1021649 + * QEmu has included sgabios upstream since just before 1.0. + */ + //ADD_CMDLINE ("-device"); + //ADD_CMDLINE ("sga"); + } + + /* Set up virtio-serial for the communications channel. */ + ADD_CMDLINE ("-chardev"); + ntohl(1); + ADD_CMDLINE_PRINTF ("socket,host=127.0.0.1,port=%d,id=channel0", ntohs(a.inaddr.sin_port)); + ADD_CMDLINE ("-device"); + ADD_CMDLINE ("virtserialport,chardev=channel0,name=org.libguestfs.channel.0"); + + /* Enable user networking. */ + if (g->enable_network) { + ADD_CMDLINE ("-netdev"); + ADD_CMDLINE ("user,id=usernet,net=169.254.0.0/16"); + ADD_CMDLINE ("-device"); + ADD_CMDLINE ("virtio-net-pci,netdev=usernet"); + } + + ADD_CMDLINE ("-append"); + CLEANUP_FREE char *appcmdline = + guestfs___appliance_command_line (g, appliance_dev, 0); + ADD_CMDLINE_PRINTF ("\"%s\"", appcmdline); + + /* Add the extra options for the qemu command line specified + * at configure time. + * FIXME - NOT SUPPORTED FOR NOW + */ + if (STRNEQ (QEMU_OPTIONS, "")) + add_cmdline_shell_unquoted (g, &cmdline, QEMU_OPTIONS); + + struct qemu_param *qp; + /* Note: custom command line parameters must come last so that + * qemu -set parameters can modify previously added options. + */ + /* Add any qemu parameters. */ + for (qp = g->qemu_params; qp; qp = qp->next) { + ADD_CMDLINE (qp->qemu_param); + if (qp->qemu_value) + ADD_CMDLINE (qp->qemu_value); + } + + /* Execute QEMU + */ + END_CMDLINE(); + + if (g->verbose) + print_qemu_command_line (g, cmdline.argv); + + int fds[3]; + + if (!g->direct_mode) { + fds[STDIN_FILENO] = sv[0]; + fds[STDOUT_FILENO] = sv[0]; + fds[STDERR_FILENO] = sv[0]; + + g->direct.pid = spawn_dup(cmdline.argv[0], (const char **)cmdline.argv, + SPAWN_PIPE_LGPL_STDIN|SPAWN_PIPE_LGPL_STDIN_PROVIDED| + SPAWN_PIPE_LGPL_STDOUT|SPAWN_PIPE_LGPL_STDOUT_PROVIDED| + SPAWN_PIPE_LGPL_STDERR|SPAWN_PIPE_LGPL_STDERR_PROVIDED, + fds); + + } else g->direct.pid = spawn_dup(cmdline.argv[0], (const char **)cmdline.argv, + 0, fds); + + if (g->direct.pid == -1) { + perrorf (g, "spawn_dup"); + if (!g->direct_mode) { + close(sv[0]); + close(sv[1]); + } + goto cleanup0; + } + + /* No recovery process in mingw driver .. for now + */ + g->direct.recoverypid = -1; + + if (!g->direct_mode) { + /* Close the other end of the socketpair. */ + close(sv[0]); + + console_sock = sv[1]; /* stdin of child */ + sv[1] = -1; + } + + g->state = LAUNCHING; + + /* Wait for qemu to start and to connect back to us via + * virtio-serial and send the GUESTFS_LAUNCH_FLAG message. + */ + g->conn = + guestfs___new_conn_socket_listening (g, daemon_accept_sock, console_sock); + if (!g->conn) + goto cleanup1; + + /* g->conn now owns these sockets. */ + daemon_accept_sock = console_sock = -1; + + r = g->conn->ops->accept_connection (g, g->conn); + if (r == -1) + goto cleanup1; + if (r == 0) { + guestfs___launch_failed_error (g); + goto cleanup1; + } + + /* NB: We reach here just because qemu has opened the socket. It + * does not mean the daemon is up until we read the + * GUESTFS_LAUNCH_FLAG below. Failures in qemu startup can still + * happen even if we reach here, even early failures like not being + * able to open a drive. + */ + r = guestfs___recv_from_daemon (g, &size, &buf); + + if (r == -1) { + guestfs___launch_failed_error (g); + goto cleanup1; + } + + if (size != GUESTFS_LAUNCH_FLAG) { + guestfs___launch_failed_error (g); + goto cleanup1; + } + + if (g->verbose) + guestfs___print_timestamped_message (g, "appliance is up"); + + /* This is possible in some really strange situations, such as + * guestfsd starts up OK but then qemu immediately exits. Check for + * it because the caller is probably expecting to be able to send + * commands after this function returns. + */ + if (g->state != READY) { + error (g, _("qemu launched and contacted daemon, but state != READY")); + goto cleanup1; + } + + TRACE0 (launch_end); + + guestfs___launch_send_progress (g, 12); + + if (has_appliance_drive) + guestfs___add_dummy_appliance_drive (g); + + return 0; + + cleanup1: + if (!g->direct_mode && sv[1] >= 0) + close (sv[1]); + if (g->direct.pid > 0) kill (g->direct.pid, 9); + if (g->direct.recoverypid > 0) kill (g->direct.recoverypid, 9); + if (g->direct.pid > 0) waitpid (g->direct.pid, NULL, 0); + if (g->direct.recoverypid > 0) waitpid (g->direct.recoverypid, NULL, 0); + g->direct.pid = 0; + g->direct.recoverypid = 0; + memset (&g->launch_t, 0, sizeof g->launch_t); + + cleanup0: + if (daemon_accept_sock >= 0) + close (daemon_accept_sock); + if (console_sock >= 0) + close (console_sock); + if (g->conn) { + g->conn->ops->free_connection (g, g->conn); + g->conn = NULL; + } + g->state = CONFIG; + return -1; +} + +/* Calculate the appliance device name. + * + * The easy thing would be to use g->nr_drives (indeed, that's what we + * used to do). However this breaks if some of the drives being added + * use the deprecated 'iface' parameter. To further add confusion, + * the format of the 'iface' parameter has never been defined, but + * given existing usage we can assume it has one of only three values: + * NULL, "ide" or "virtio" (which means virtio-blk). See RHBZ#975797. + */ +static char * +make_appliance_dev (guestfs_h *g, int virtio_scsi) +{ + size_t i, index = 0; + struct drive *drv; + char dev[64] = "/dev/Xd"; + + /* Calculate the index of the drive. */ + ITER_DRIVES (g, i, drv) { + if (virtio_scsi) { + if (drv->iface == NULL || STREQ (drv->iface, "ide")) + index++; + } + else /* virtio-blk */ { + if (drv->iface == NULL || STRNEQ (drv->iface, "virtio")) + index++; + } + } + + dev[5] = virtio_scsi ? 's' : 'v'; + guestfs___drive_name (index, &dev[7]); + + return safe_strdup (g, dev); /* Caller frees. */ +} + +/* This is called from the forked subprocess just before qemu runs, so + * it can just print the message straight to stderr, where it will be + * picked up and funnelled through the usual appliance event API. + */ +static void +print_qemu_command_line (guestfs_h *g, char **argv) +{ + int i = 0; + int needs_quote; + + struct timeval tv; + gettimeofday (&tv, NULL); + fprintf (stderr, "[%05" PRIi64 "ms] ", + guestfs___timeval_diff (&g->launch_t, &tv)); + + while (argv[i]) { + if (argv[i][0] == '-') /* -option starts a new line */ + fprintf (stderr, " \\\n "); + + if (i > 0) fputc (' ', stderr); + + /* Does it need shell quoting? This only deals with simple cases. */ + needs_quote = strcspn (argv[i], " ") != strlen (argv[i]); + + if (needs_quote) fputc ('\'', stderr); + fprintf (stderr, "%s", argv[i]); + if (needs_quote) fputc ('\'', stderr); + i++; + } + + fputc ('\n', stderr); +} + +static void parse_qemu_version (guestfs_h *g); +static void read_all (guestfs_h *g, void *retv, const char *buf, size_t len); + +/* Test qemu binary (or wrapper) runs, and do 'qemu -help' and + * 'qemu -version' so we know what options this qemu supports and + * the version. + */ +static int +test_qemu (guestfs_h *g) +{ + CLEANUP_CMD_CLOSE struct command *cmd1 = guestfs___new_command (g); + CLEANUP_CMD_CLOSE struct command *cmd2 = guestfs___new_command (g); + CLEANUP_CMD_CLOSE struct command *cmd3 = guestfs___new_command (g); + int r; + + free (g->direct.qemu_help); + g->direct.qemu_help = NULL; + free (g->direct.qemu_version); + g->direct.qemu_version = NULL; + free (g->direct.qemu_devices); + g->direct.qemu_devices = NULL; + + guestfs___cmd_add_arg (cmd1, g->qemu); + guestfs___cmd_add_arg (cmd1, "-nographic"); + guestfs___cmd_add_arg (cmd1, "-help"); + guestfs___cmd_clear_capture_errors (cmd1); + guestfs___cmd_set_stdout_callback (cmd1, read_all, &g->direct.qemu_help, + CMD_STDOUT_FLAG_WHOLE_BUFFER); + r = guestfs___cmd_run (cmd1); + if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) + goto error; + + guestfs___cmd_add_arg (cmd2, g->qemu); + guestfs___cmd_add_arg (cmd2, "-nographic"); + guestfs___cmd_add_arg (cmd2, "-version"); + guestfs___cmd_clear_capture_errors (cmd2); + guestfs___cmd_set_stdout_callback (cmd2, read_all, &g->direct.qemu_version, + CMD_STDOUT_FLAG_WHOLE_BUFFER); + r = guestfs___cmd_run (cmd2); + if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) + goto error; + + parse_qemu_version (g); + + guestfs___cmd_add_arg (cmd3, g->qemu); + guestfs___cmd_add_arg (cmd3, "-nographic"); + guestfs___cmd_add_arg (cmd3, "-nodefaults"); + guestfs___cmd_add_arg (cmd3, "-nodefconfig"); + guestfs___cmd_add_arg (cmd3, "-qmp"); + guestfs___cmd_add_arg (cmd3, "stdio"); + guestfs___cmd_clear_capture_errors (cmd3); + //guestfs___cmd_set_stderr_to_stdout (cmd3); + guestfs___cmd_set_stdout_callback (cmd3, read_all, &g->direct.qemu_devices, + CMD_STDOUT_FLAG_WHOLE_BUFFER); + guestfs___cmd_set_stdin_data (cmd3, "{ \"execute\": \"qmp_capabilities\" }\r\n{ \"execute\": \"query-devices\" }\r\n{ \"execute\": \"quit\" }\r\n"); + r = guestfs___cmd_run (cmd3); + if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) + goto error; + + return 0; + + error: + if (r == -1) + return -1; + + guestfs___external_command_failed (g, r, g->qemu, NULL); + return -1; +} + +/* Parse g->direct.qemu_version (if not NULL) into the major and minor + * version of qemu, but don't fail if parsing is not possible. + */ +static void +parse_qemu_version (guestfs_h *g) +{ + CLEANUP_FREE char *major_s = NULL, *minor_s = NULL; + int major_i, minor_i; + + g->direct.qemu_version_major = 0; + g->direct.qemu_version_minor = 0; + + if (!g->direct.qemu_version) + return; + + if (!match2 (g, g->direct.qemu_version, re_major_minor, &major_s, &minor_s)) { + parse_failed: + debug (g, "%s: failed to parse qemu version string '%s'", + __func__, g->direct.qemu_version); + return; + } + + major_i = guestfs___parse_unsigned_int (g, major_s); + if (major_i == -1) + goto parse_failed; + + minor_i = guestfs___parse_unsigned_int (g, minor_s); + if (minor_i == -1) + goto parse_failed; + + g->direct.qemu_version_major = major_i; + g->direct.qemu_version_minor = minor_i; + + debug (g, "qemu version %d.%d", major_i, minor_i); +} + +static void +read_all (guestfs_h *g, void *retv, const char *buf, size_t len) +{ + char **ret = retv; + + *ret = safe_strndup (g, buf, len); +} + +/* Test if option is supported by qemu command line (just by grepping + * the help text). + * + * The first time this is used, it has to run the external qemu + * binary. If that fails, it returns -1. + * + * To just do the first-time run of the qemu binary, call this with + * option == NULL, in which case it will return -1 if there was an + * error doing that. + */ +static int +qemu_supports (guestfs_h *g, const char *option) +{ + if (!g->direct.qemu_help) { + if (test_qemu (g) == -1) + return -1; + } + + if (option == NULL) + return 1; + + return strstr (g->direct.qemu_help, option) != NULL; +} + +/* Test if device is supported by qemu (currently just greps the -device ? + * output). + */ +static int +qemu_supports_device (guestfs_h *g, const char *device_name) +{ + if (!g->direct.qemu_devices) { + if (test_qemu (g) == -1) + return -1; + } + + return strstr (g->direct.qemu_devices, device_name) != NULL; +} + +static int +old_or_broken_virtio_scsi (guestfs_h *g) +{ + /* qemu 1.1 claims to support virtio-scsi but in reality it's broken. */ + if (g->direct.qemu_version_major == 1 && g->direct.qemu_version_minor < 2) + return 1; + + return 0; +} + +/* Returns 1 = use virtio-scsi, or 0 = use virtio-blk. */ +static int +qemu_supports_virtio_scsi (guestfs_h *g) +{ + int r; + + if (!g->direct.qemu_help) { + if (test_qemu (g) == -1) + return 0; /* safe option? */ + } + + /* g->direct.virtio_scsi has these values: + * 0 = untested (after handle creation) + * 1 = supported + * 2 = not supported (use virtio-blk) + * 3 = test failed (use virtio-blk) + */ + if (g->direct.virtio_scsi == 0) { + if (old_or_broken_virtio_scsi (g)) + g->direct.virtio_scsi = 2; + else { + r = qemu_supports_device (g, "virtio-scsi-pci"); + if (r > 0) + g->direct.virtio_scsi = 1; + else if (r == 0) + g->direct.virtio_scsi = 2; + else + g->direct.virtio_scsi = 3; + } + } + + return g->direct.virtio_scsi == 1; +} + +/* Convert a struct drive into a qemu -drive parameter. Note that if + * using virtio-scsi, then the code above adds a second -device + * parameter to connect this drive to the SCSI HBA, as is required by + * virtio-scsi. + */ +static char * +qemu_drive_param (guestfs_h *g, const struct drive *drv, size_t index) +{ + CLEANUP_FREE char *file = NULL, *escaped_file = NULL; + size_t i, len; + const char *iface; + char *p; + + /* Make the file= parameter. */ + file = guestfs___drive_source_qemu_param (g, &drv->src); + + /* Escape the file= parameter. Every ',' becomes ',,'. */ + len = strlen (file); + p = escaped_file = safe_malloc (g, len*2 + 1); /* max length of escaped name*/ + for (i = 0; i < len; ++i) { + *p++ = file[i]; + if (file[i] == ',') + *p++ = ','; + } + *p = '\0'; + + if (drv->iface) + iface = drv->iface; + else if (qemu_supports_virtio_scsi (g)) + iface = "none"; /* sic */ + else + iface = "virtio"; + + return safe_asprintf + (g, "file=%s%s%s%s%s%s%s,id=hd%zu,if=%s", + escaped_file, + drv->readonly ? ",snapshot=on" : "", + drv->use_cache_none ? ",cache=none" : "", + drv->format ? ",format=" : "", + drv->format ? drv->format : "", + drv->disk_label ? ",serial=" : "", + drv->disk_label ? drv->disk_label : "", + index, + iface); +} + +static int +shutdown_mingw (guestfs_h *g, int check_for_errors) +{ + int ret = 0; + int status; + + /* Signal qemu to shutdown cleanly, and kill the recovery process. */ + if (g->direct.pid > 0) { + debug (g, "sending SIGTERM to process %d", g->direct.pid); + kill (g->direct.pid, SIGTERM); + } + if (g->direct.recoverypid > 0) kill (g->direct.recoverypid, 9); + + /* Wait for subprocess(es) to exit. */ + if (g->recovery_proc /* RHBZ#998482 */ && g->direct.pid > 0) { + if (waitpid (g->direct.pid, &status, 0) == -1) { + perrorf (g, "waitpid (qemu)"); + ret = -1; + } + else if (!WIFEXITED (status) || WEXITSTATUS (status) != SIGTERM) { // under mingw it exits with the code of the signal that terminated it + guestfs___external_command_failed (g, status, g->qemu, NULL); + ret = -1; + } + } + if (g->direct.recoverypid > 0) waitpid (g->direct.recoverypid, NULL, 0); + + g->direct.pid = g->direct.recoverypid = 0; + + free (g->direct.qemu_help); + g->direct.qemu_help = NULL; + free (g->direct.qemu_version); + g->direct.qemu_version = NULL; + free (g->direct.qemu_devices); + g->direct.qemu_devices = NULL; + + return ret; +} + +static int +get_pid_mingw (guestfs_h *g) +{ + if (g->direct.pid > 0) + return g->direct.pid; + else { + error (g, "get_pid: no qemu subprocess"); + return -1; + } +} + +/* Maximum number of disks. */ +static int +max_disks_mingw (guestfs_h *g) +{ + if (qemu_supports_virtio_scsi (g)) + return 255; + else + return 27; /* conservative estimate */ +} + +struct backend_ops backend_ops_direct = { + .launch = launch_mingw, + .shutdown = shutdown_mingw, + .get_pid = get_pid_mingw, + .max_disks = max_disks_mingw, +}; diff --git a/src/launch-unix.c b/src/launch-unix.c index 489a046..e968e6b 100644 --- a/src/launch-unix.c +++ b/src/launch-unix.c @@ -23,7 +23,6 @@ #include #include #include -#include #include "guestfs.h" #include "guestfs-internal.h" @@ -33,6 +32,15 @@ /* Alternate backend: instead of launching the appliance, * connect to an existing unix socket. */ +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +static int +launch_unix (guestfs_h *g, const char *sockpath) +{ + return -1; +} + +#else +#include static int launch_unix (guestfs_h *g, void *datav, const char *sockpath) diff --git a/src/launch.c b/src/launch.c index 3851b6a..ee840ee 100644 --- a/src/launch.c +++ b/src/launch.c @@ -86,7 +86,9 @@ guestfs__launch (guestfs_h *g) debug (g, "launch: tmpdir=%s", g->tmpdir); debug (g, "launch: umask=0%03o", get_umask (g)); +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ debug (g, "launch: euid=%d", geteuid ()); +#endif } /* Launch the appliance. */ diff --git a/src/proto.c b/src/proto.c index 8001c8c..4744b37 100644 --- a/src/proto.c +++ b/src/proto.c @@ -167,10 +167,9 @@ check_daemon_socket (guestfs_h *g) assert (g->conn); /* callers must check this */ - again: +again: if (! g->conn->ops->can_read_data (g, g->conn)) return 1; - n = g->conn->ops->read_data (g, g->conn, buf, 4); if (n <= 0) /* 0 or -1 */ return n; @@ -183,7 +182,6 @@ check_daemon_socket (guestfs_h *g) if (flag == GUESTFS_PROGRESS_FLAG) { char buf[PROGRESS_MESSAGE_SIZE]; guestfs_progress message; - n = g->conn->ops->read_data (g, g->conn, buf, PROGRESS_MESSAGE_SIZE); if (n <= 0) /* 0 or -1 */ return n; @@ -328,7 +326,11 @@ guestfs___send_file (guestfs_h *g, const char *filename) g->user_cancel = 0; +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ fd = open (filename, O_RDONLY|O_CLOEXEC); +#else + fd = _open (filename, _O_RDONLY|_O_BINARY|_O_NOINHERIT|_O_SEQUENTIAL); +#endif if (fd == -1) { perrorf (g, "open: %s", filename); send_file_cancellation (g); @@ -339,7 +341,11 @@ guestfs___send_file (guestfs_h *g, const char *filename) /* Send file in chunked encoding. */ while (!g->user_cancel) { +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ r = read (fd, buf, sizeof buf); +#else + r = _read (fd, buf, sizeof buf); +#endif if (r == -1 && (errno == EINTR || errno == EAGAIN)) continue; if (r <= 0) break; @@ -370,7 +376,11 @@ guestfs___send_file (guestfs_h *g, const char *filename) /* End of file, but before we send that, we need to close * the file and check for errors. */ +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ if (close (fd) == -1) { +#else + if (_close (fd) == -1) { +#endif perrorf (g, "close: %s", filename); send_file_cancellation (g); return -1; @@ -734,7 +744,11 @@ xwrite (int fd, const void *v_buf, size_t len) int r; while (len > 0) { +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ r = write (fd, buf, len); +#else + r = _write (fd, buf, len); +#endif if (r == -1) return -1; @@ -756,7 +770,11 @@ guestfs___recv_file (guestfs_h *g, const char *filename) g->user_cancel = 0; +#if (!defined _WIN32 && !defined __WIN32__) || defined __CYGWIN__ fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0666); +#else + fd = _open (filename, _O_WRONLY|_O_CREAT|_O_BINARY|_O_TRUNC|_O_NOINHERIT, 0666); +#endif if (fd == -1) { perrorf (g, "open: %s", filename); goto cancel; -- 1.8.4.2