X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fguestfs.c;h=fe08cb56ffee46430171e41703e108c5f240d8be;hp=9908e7f8d283ecbaf882fe7b79201d57fd7d4259;hb=92d38b9ae3a0b1b4694da5e3d3a3cb155842cd91;hpb=245ac5673700f54479e9408e8659cb68f80c344a diff --git a/src/guestfs.c b/src/guestfs.c index 9908e7f..fe08cb5 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -85,15 +86,30 @@ #define safe_strdup guestfs_safe_strdup //#define safe_memdup guestfs_safe_memdup +#ifdef __linux__ +#define CAN_CHECK_PEER_EUID 1 +#else +#define CAN_CHECK_PEER_EUID 0 +#endif + static void default_error_cb (guestfs_h *g, void *data, const char *msg); static int send_to_daemon (guestfs_h *g, const void *v_buf, size_t n); static int recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn); static int accept_from_daemon (guestfs_h *g); static int check_peer_euid (guestfs_h *g, int sock, uid_t *rtn); static void close_handles (void); +static int qemu_supports (guestfs_h *g, const char *option); #define UNIX_PATH_MAX 108 +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +#ifdef __APPLE__ +#define xdr_uint32_t xdr_u_int32_t +#endif + /* Also in guestfsd.c */ #define GUESTFWD_ADDR "10.0.2.4" #define GUESTFWD_PORT "6666" @@ -389,7 +405,7 @@ guestfs_perrorf (guestfs_h *g, const char *fs, ...) if (err < 0) return; -#ifndef _GNU_SOURCE +#if !defined(_GNU_SOURCE) || defined(__APPLE__) char buf[256]; strerror_r (errnum, buf, sizeof buf); #else @@ -433,7 +449,11 @@ guestfs_safe_malloc (guestfs_h *g, size_t nbytes) /* Technically we should add an autoconf test for this, testing for the desired functionality, like what's done in gnulib, but for now, this is fine. */ +#if defined(__GLIBC__) #define HAVE_GNU_CALLOC (__GLIBC__ >= 2) +#else +#define HAVE_GNU_CALLOC 0 +#endif /* Allocate zeroed memory for N elements of S bytes, with error checking. S must be nonzero. */ @@ -748,7 +768,8 @@ guestfs__config (guestfs_h *g, } int -guestfs__add_drive (guestfs_h *g, const char *filename) +guestfs__add_drive_with_if (guestfs_h *g, const char *filename, + const char *drive_if) { size_t len = strlen (filename) + 64; char buf[len]; @@ -771,12 +792,12 @@ guestfs__add_drive (guestfs_h *g, const char *filename) int fd = open (filename, O_RDONLY|O_DIRECT); if (fd >= 0) { close (fd); - snprintf (buf, len, "file=%s,cache=off,if=" DRIVE_IF, filename); + snprintf (buf, len, "file=%s,cache=off,if=%s", filename, drive_if); } else { fd = open (filename, O_RDONLY); if (fd >= 0) { close (fd); - snprintf (buf, len, "file=%s,if=" DRIVE_IF, filename); + snprintf (buf, len, "file=%s,if=%s", filename, drive_if); } else { perrorf (g, "%s", filename); return -1; @@ -787,11 +808,9 @@ guestfs__add_drive (guestfs_h *g, const char *filename) } int -guestfs__add_drive_ro (guestfs_h *g, const char *filename) +guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename, + const char *drive_if) { - size_t len = strlen (filename) + 64; - char buf[len]; - if (strchr (filename, ',') != NULL) { error (g, _("filename cannot contain ',' (comma) character")); return -1; @@ -802,12 +821,41 @@ guestfs__add_drive_ro (guestfs_h *g, const char *filename) return -1; } - snprintf (buf, len, "file=%s,snapshot=on,if=%s", filename, DRIVE_IF); + if (qemu_supports (g, NULL) == -1) + return -1; + + /* Only SCSI and virtio drivers support readonly mode. + * This is only supported as a QEMU feature since 2010/01. + */ + int supports_ro = 0; + if ((STREQ (drive_if, "scsi") || STREQ (drive_if, "virtio")) && + qemu_supports (g, "readonly=on")) + supports_ro = 1; + + size_t len = strlen (filename) + 100; + char buf[len]; + + snprintf (buf, len, "file=%s,snapshot=on,%sif=%s", + filename, + supports_ro ? "readonly=on," : "", + drive_if); return guestfs__config (g, "-drive", buf); } int +guestfs__add_drive (guestfs_h *g, const char *filename) +{ + return guestfs__add_drive_with_if (g, filename, DRIVE_IF); +} + +int +guestfs__add_drive_ro (guestfs_h *g, const char *filename) +{ + return guestfs__add_drive_ro_with_if (g, filename, DRIVE_IF); +} + +int guestfs__add_cdrom (guestfs_h *g, const char *filename) { if (strchr (filename, ',') != NULL) { @@ -856,8 +904,6 @@ dir_contains_files (const char *dir, ...) static void print_timestamped_message (guestfs_h *g, const char *fs, ...); static int build_supermin_appliance (guestfs_h *g, const char *path, char **kernel, char **initrd); -static int test_qemu (guestfs_h *g); -static int qemu_supports (guestfs_h *g, const char *option); static int is_openable (guestfs_h *g, const char *path, int flags); static void print_cmdline (guestfs_h *g); @@ -1009,11 +1055,11 @@ guestfs__launch (guestfs_h *g) print_timestamped_message (g, "begin testing qemu features"); /* Get qemu help text and version. */ - if (test_qemu (g) == -1) + if (qemu_supports (g, NULL) == -1) goto cleanup0; /* Choose which vmchannel implementation to use. */ - if (qemu_supports (g, "-net user")) { + if (CAN_CHECK_PEER_EUID && qemu_supports (g, "-net user")) { /* The "null vmchannel" implementation. Requires SLIRP (user mode * networking in qemu) but no other vmchannel support. The daemon * will connect back to a random port number on localhost. @@ -1211,10 +1257,12 @@ guestfs__launch (guestfs_h *g) "%s " /* (selinux) */ "%s " /* (vmchannel) */ "%s " /* (verbose) */ + "TERM=%s " /* (TERM environment variable) */ "%s", /* (append) */ g->selinux ? "selinux=1 enforcing=0" : "selinux=0", vmchannel ? vmchannel : "", g->verbose ? "guestfs_verbose=1" : "", + getenv ("TERM") ? : "linux", g->append ? g->append : ""); add_cmdline (g, "-kernel"); @@ -1241,7 +1289,7 @@ guestfs__launch (guestfs_h *g) if (dup (wfd[0]) == -1) { dup_failed: perror ("dup failed"); - _exit (1); + _exit (EXIT_FAILURE); } if (dup (rfd[1]) == -1) goto dup_failed; @@ -1261,7 +1309,7 @@ guestfs__launch (guestfs_h *g) execv (g->qemu, g->cmdline); /* Run qemu. */ perror (g->qemu); - _exit (1); + _exit (EXIT_FAILURE); } /* Parent (library). */ @@ -1293,11 +1341,11 @@ guestfs__launch (guestfs_h *g) */ for (;;) { if (kill (qemu_pid, 0) == -1) /* qemu's gone away, we aren't needed */ - _exit (0); + _exit (EXIT_SUCCESS); if (kill (parent_pid, 0) == -1) { /* Parent's gone away, qemu still around, so kill qemu. */ kill (qemu_pid, 9); - _exit (0); + _exit (EXIT_SUCCESS); } sleep (2); } @@ -1357,6 +1405,7 @@ guestfs__launch (guestfs_h *g) "libguestfs: warning: unexpected connection from UID %d to port %d\n", uid, null_vmchannel_sock); close (sock); + sock = -1; continue; } } @@ -1451,7 +1500,7 @@ guestfs__launch (guestfs_h *g) close (wfd[1]); close (rfd[0]); } - kill (g->pid, 9); + if (g->pid > 0) kill (g->pid, 9); if (g->recoverypid > 0) kill (g->recoverypid, 9); waitpid (g->pid, NULL, 0); if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0); @@ -1523,8 +1572,9 @@ build_supermin_appliance (guestfs_h *g, const char *path, snprintf (cmd, sizeof cmd, "PATH='%s':$PATH " - "libguestfs-supermin-helper '%s' " host_cpu " " REPO " %s %s", + "libguestfs-supermin-helper%s '%s' " host_cpu " " REPO " %s %s", path, + g->verbose ? " --verbose" : "", path, *kernel, *initrd); if (g->verbose) print_timestamped_message (g, "%s", cmd); @@ -1564,7 +1614,7 @@ print_timestamped_message (guestfs_h *g, const char *fs, ...) va_list args; char *msg; int err; - struct timeval tv, diff; + struct timeval tv; va_start (args, fs); err = vasprintf (&msg, fs, args); @@ -1592,12 +1642,7 @@ test_qemu (guestfs_h *g) char cmd[1024]; FILE *fp; - free (g->qemu_help); - free (g->qemu_version); - g->qemu_help = NULL; - g->qemu_version = NULL; - - snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -help", g->qemu); + snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -nographic -help", g->qemu); fp = popen (cmd, "r"); /* qemu -help should always work (qemu -version OTOH wasn't @@ -1619,7 +1664,8 @@ test_qemu (guestfs_h *g) if (pclose (fp) == -1) goto error; - snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -version 2>/dev/null", g->qemu); + snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -nographic -version 2>/dev/null", + g->qemu); fp = popen (cmd, "r"); if (fp) { @@ -1657,11 +1703,26 @@ read_all (guestfs_h *g, FILE *fp, char **ret) /* 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) { - return g->qemu_help && strstr (g->qemu_help, option) != NULL; + if (!g->qemu_help) { + if (test_qemu (g) == -1) + return -1; + } + + if (option == NULL) + return 1; + + return strstr (g->qemu_help, option) != NULL; } /* Check if a file can be opened. */ @@ -1685,6 +1746,7 @@ is_openable (guestfs_h *g, const char *path, int flags) static int check_peer_euid (guestfs_h *g, int sock, uid_t *rtn) { +#if CAN_CHECK_PEER_EUID struct sockaddr_in peer; socklen_t addrlen = sizeof peer; @@ -1753,6 +1815,12 @@ check_peer_euid (guestfs_h *g, int sock, uid_t *rtn) error (g, "check_peer_euid: no matching TCP connection found in /proc/net/tcp"); fclose (fp); return -1; +#else /* !CAN_CHECK_PEER_EUID */ + /* This function exists but should never be called in this + * configuration. + */ + abort (); +#endif /* !CAN_CHECK_PEER_EUID */ } /* You had to call this function after launch in versions <= 1.0.70, @@ -1780,7 +1848,7 @@ guestfs__kill_subprocess (guestfs_h *g) if (g->verbose) fprintf (stderr, "sending SIGTERM to process %d\n", g->pid); - kill (g->pid, SIGTERM); + if (g->pid > 0) kill (g->pid, SIGTERM); if (g->recoverypid > 0) kill (g->recoverypid, 9); return 0; @@ -1931,7 +1999,7 @@ child_cleanup (guestfs_h *g) if (g->verbose) fprintf (stderr, "child_cleanup: %p: child process died\n", g); - /*kill (g->pid, SIGTERM);*/ + /*if (g->pid > 0) kill (g->pid, SIGTERM);*/ if (g->recoverypid > 0) kill (g->recoverypid, 9); waitpid (g->pid, NULL, 0); if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0); @@ -2298,8 +2366,16 @@ accept_from_daemon (guestfs_h *g) int sock = -1; while (sock == -1) { + /* If the qemu process has died, clean up the zombie (RHBZ#579155). + * By partially polling in the select below we ensure that this + * function will be called eventually. + */ + waitpid (g->pid, NULL, WNOHANG); + rset2 = rset; - int r = select (max_fd+1, &rset2, NULL, NULL, NULL); + + struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; + int r = select (max_fd+1, &rset2, NULL, NULL, &tv); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; @@ -2619,7 +2695,6 @@ guestfs___recv_file (guestfs_h *g, const char *filename) { void *buf; int fd, r; - size_t len; fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666); if (fd == -1) {