From 99bd425a0a8fb02c27e0c22b32dafa804773a7b1 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 3 Apr 2009 22:25:34 +0100 Subject: [PATCH] Implemented 'mount' and 'touch' commands. --- .gitignore | 1 + daemon/Makefile.am | 2 + daemon/daemon.h | 22 ++++++ daemon/file.c | 63 +++++++++++++++++ daemon/guestfsd.c | 193 +++++++++++++++++++++++++++++++++++++++++++++------ daemon/mount.c | 76 ++++++++++++++++++++ make-initramfs.sh.in | 3 + src/generator.ml | 8 ++- src/guestfs.c | 45 ++++++++++++ 9 files changed, 390 insertions(+), 23 deletions(-) create mode 100644 daemon/file.c create mode 100644 daemon/mount.c diff --git a/.gitignore b/.gitignore index 8771111..3b1d2f0 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,6 @@ m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 stamp-h1 +test*.img update-initramfs.sh vmlinuz.* diff --git a/daemon/Makefile.am b/daemon/Makefile.am index cf55250..ea60792 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -21,7 +21,9 @@ noinst_PROGRAMS = guestfsd guestfsd_SOURCES = \ actions.h \ daemon.h \ + file.c \ guestfsd.c \ + mount.c \ proto.c \ stubs.c \ sync.c \ diff --git a/daemon/daemon.h b/daemon/daemon.h index a328435..fa896e1 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -26,11 +26,15 @@ /* in guestfsd.c */ extern void xwrite (int sock, const void *buf, size_t len); extern void xread (int sock, void *buf, size_t len); +extern int command (char **stdoutput, char **stderror, const char *name, ...); /* in proto.c */ extern int proc_nr; extern int serial; +/* in mount.c */ +extern int root_mounted; + /* in stubs.c (auto-generated) */ extern void dispatch_incoming_message (XDR *); @@ -40,4 +44,22 @@ extern void reply_with_error (const char *fs, ...); extern void reply_with_perror (const char *fs, ...); extern void reply (xdrproc_t xdrp, char *ret); +#define NEED_ROOT \ + do { \ + if (!root_mounted) { \ + reply_with_error ("%s: you must call 'mount' first to mount the root filesystem", __func__); \ + return -1; \ + } \ + } \ + while (0) + +/* NB: + * (1) You must match CHROOT_IN and CHROOT_OUT even along error paths. + * (2) You must not change directory! cwd must always be "/", otherwise + * we can't escape our own chroot. + * (3) All paths specified must be absolute. + */ +#define CHROOT_IN chroot ("/sysroot"); +#define CHROOT_OUT chroot ("."); + #endif /* GUESTFSD_DAEMON_H */ diff --git a/daemon/file.c b/daemon/file.c new file mode 100644 index 0000000..1e633d8 --- /dev/null +++ b/daemon/file.c @@ -0,0 +1,63 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#define _GNU_SOURCE /* for futimens(2) */ + +#include +#include +#include +#include +#include +#include + +#include "daemon.h" +#include "actions.h" + +int +do_touch (const char *path) +{ + int fd; + + NEED_ROOT; + + if (path[0] != '/') { + reply_with_error ("touch: path must start with a / character"); + return -1; + } + + CHROOT_IN; + fd = open (path, O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666); + CHROOT_OUT; + + if (fd == -1) { + reply_with_perror ("open: %s", path); + close (fd); + return -1; + } + + if (futimens (fd, NULL) == -1) { + reply_with_perror ("futimens: %s", path); + close (fd); + return -1; + } + + close (fd); + return 0; +} diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c index 6fc8b19..810d9d0 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -18,6 +18,8 @@ #include +#define _BSD_SOURCE /* for daemon(3) */ + #include #include #include @@ -26,6 +28,10 @@ #include #include #include +#include +#include +#include +#include #include "daemon.h" @@ -170,19 +176,15 @@ main (int argc, char *argv[]) xdr_destroy (&xdr); - /* XXX Fork into the background. */ - - - - - - - - - - - + /* Fork into the background. */ + if (!dont_fork) { + if (daemon (0, 1) == -1) { + perror ("daemon"); + exit (1); + } + } + /* Enter the main loop, reading and performing actions. */ main_loop (sock); exit (0); @@ -230,17 +232,164 @@ usage (void) fprintf (stderr, "guestfsd [-f] [-h host -p port]\n"); } -/* Some unimplemented actions. */ +/* This is a more sane version of 'system(3)' for running external + * commands. It uses fork/execvp, so we don't need to worry about + * quoting of parameters, and it allows us to capture any error + * messages in a buffer. + */ int -do_mount (const char *device, const char *mountpoint) +command (char **stdoutput, char **stderror, const char *name, ...) { - reply_with_error ("mount not implemented"); - return -1; -} + int so_size = 0, se_size = 0; + int so_fd[2], se_fd[2]; + int pid, r, quit; + fd_set rset, rset2; + char buf[256]; + + if (stdoutput) *stdoutput = NULL; + if (stderror) *stderror = NULL; + + if (pipe (so_fd) == -1 || pipe (se_fd) == -1) { + perror ("pipe"); + return -1; + } -int -do_touch (const char *path) -{ - reply_with_error ("touch not implemented"); - return -1; + pid = fork (); + if (pid == -1) { + perror ("fork"); + return -1; + } + + if (pid == 0) { /* Child process. */ + va_list args; + char **argv; + char *s; + int i; + + /* Collect the command line arguments into an array. */ + va_start (args, name); + + i = 2; + argv = malloc (sizeof (char *) * i); + argv[0] = (char *) name; + argv[1] = NULL; + + while ((s = va_arg (args, char *)) != NULL) { + argv = realloc (argv, sizeof (char *) * (++i)); + argv[i-2] = s; + argv[i-1] = NULL; + } + + close (0); + close (so_fd[0]); + close (se_fd[0]); + dup2 (so_fd[1], 1); + dup2 (se_fd[1], 2); + close (so_fd[1]); + close (se_fd[1]); + + execvp (name, argv); + perror (name); + _exit (1); + } + + /* Parent process. */ + close (so_fd[1]); + close (se_fd[1]); + + FD_ZERO (&rset); + FD_SET (so_fd[0], &rset); + FD_SET (se_fd[0], &rset); + + quit = 0; + while (!quit) { + rset2 = rset; + r = select (MAX (so_fd[0], se_fd[0]) + 1, &rset2, NULL, NULL, NULL); + if (r == -1) { + perror ("select"); + waitpid (pid, NULL, 0); + return -1; + } + + if (FD_ISSET (so_fd[0], &rset2)) { /* something on stdout */ + r = read (so_fd[0], buf, sizeof buf); + if (r == -1) { + perror ("read"); + waitpid (pid, NULL, 0); + return -1; + } + if (r == 0) quit = 1; + + if (r > 0 && stdoutput) { + so_size += r; + *stdoutput = realloc (*stdoutput, so_size); + if (*stdoutput == NULL) { + perror ("realloc"); + *stdoutput = NULL; + continue; + } + memcpy (*stdoutput + so_size - r, buf, r); + } + } + + if (FD_ISSET (se_fd[0], &rset2)) { /* something on stderr */ + r = read (se_fd[0], buf, sizeof buf); + if (r == -1) { + perror ("read"); + waitpid (pid, NULL, 0); + return -1; + } + if (r == 0) quit = 1; + + if (r > 0 && stderror) { + se_size += r; + *stderror = realloc (*stderror, se_size); + if (*stderror == NULL) { + perror ("realloc"); + *stderror = NULL; + continue; + } + memcpy (*stderror + se_size - r, buf, r); + } + } + } + + /* Make sure the output buffers are \0-terminated. Also remove any + * trailing \n characters. + */ + if (stdoutput) { + *stdoutput = realloc (*stdoutput, so_size+1); + if (*stdoutput == NULL) { + perror ("realloc"); + *stdoutput = NULL; + } else { + (*stdoutput)[so_size] = '\0'; + so_size--; + while (so_size >= 0 && (*stdoutput)[so_size] == '\n') + (*stdoutput)[so_size--] = '\0'; + } + } + if (stderror) { + *stderror = realloc (*stderror, se_size+1); + if (*stderror == NULL) { + perror ("realloc"); + *stderror = NULL; + } else { + (*stderror)[se_size] = '\0'; + se_size--; + while (se_size >= 0 && (*stderror)[se_size] == '\n') + (*stderror)[se_size--] = '\0'; + } + } + + /* Get the exit status of the command. */ + waitpid (pid, &r, 0); + + if (WIFEXITED (r)) { + if (WEXITSTATUS (r) == 0) + return 0; + else + return -1; + } else + return -1; } diff --git a/daemon/mount.c b/daemon/mount.c new file mode 100644 index 0000000..440ec0d --- /dev/null +++ b/daemon/mount.c @@ -0,0 +1,76 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include +#include + +#include "daemon.h" +#include "actions.h" + +/* You must mount something on "/" first, hence: */ +int root_mounted = 0; + +/* The "simple mount" call offers no complex options, you can just + * mount a device on a mountpoint. + * + * It's tempting to try a direct mount(2) syscall, but that doesn't + * do any autodetection, so we are better off calling out to + * /bin/mount. + */ + +int +do_mount (const char *device, const char *mountpoint) +{ + int len, r, is_root; + char *mp; + char *error; + + is_root = strcmp (mountpoint, "/") == 0; + + if (!root_mounted && !is_root) { + reply_with_error ("mount: you must mount something on / first"); + return -1; + } + + len = strlen (mountpoint) + 9; + + mp = malloc (len); + if (!mp) { + reply_with_perror ("malloc"); + return -1; + } + + snprintf (mp, len, "/sysroot%s", mountpoint); + + r = command (NULL, &error, + "mount", "-o", "sync,noatime", device, mp, NULL); + if (r == -1) { + reply_with_error ("mount: %s on %s: %s", device, mountpoint, error); + free (error); + return -1; + } + + if (is_root) + root_mounted = 1; + + return 0; +} diff --git a/make-initramfs.sh.in b/make-initramfs.sh.in index 60e2ee2..9856fe5 100755 --- a/make-initramfs.sh.in +++ b/make-initramfs.sh.in @@ -40,6 +40,9 @@ rm -f $koutput # Create the basic initramfs. @FEBOOTSTRAP@ $modules @REPO@ initramfs @MIRROR@ +# /sysroot is where the guest root filesystem will be mounted. +mkdir initramfs/sysroot + # Make a safe 'install_file' function to install files into the # filesystem. XXX We need a 'febootstrap-install' utility. # Usage: install_file mode owner[.group] diff --git a/src/generator.ml b/src/generator.ml index a54ac3f..29f7438 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -53,7 +53,13 @@ names can be used. The rules are the same as for L: A filesystem must first be mounted on C before others can be mounted. Other filesystems can only be mounted on directories which already -exist."); +exist. + +The mounted filesystem is writable, if we have sufficient permissions +on the underlying device. + +The filesystem options C and C are set with this +call, in order to improve reliability."); ("sync", (Err, P0), 2, "Sync disks, writes are flushed through to the disk image", diff --git a/src/guestfs.c b/src/guestfs.c index 47af20a..0c0a37f 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -67,6 +67,8 @@ static void stdout_event (void *data, int watch, int fd, int events); static void sock_read_event (void *data, int watch, int fd, int events); static void sock_write_event (void *data, int watch, int fd, int events); +static void close_handles (void); + static int select_add_handle (guestfs_h *g, int fd, int events, guestfs_handle_event_cb cb, void *data); static int select_remove_handle (guestfs_h *g, int watch); static int select_add_timeout (guestfs_h *g, int interval, guestfs_handle_timeout_cb cb, void *data); @@ -95,6 +97,8 @@ enum state { CONFIG, LAUNCHING, READY, BUSY, NO_HANDLE }; struct guestfs_h { + struct guestfs_h *next; /* Linked list of open handles. */ + /* State: see the state machine diagram in the man page guestfs(3). */ enum state state; @@ -144,6 +148,9 @@ struct guestfs_h int msg_next_serial; }; +static guestfs_h *handles = NULL; +static int atexit_handler_set = 0; + guestfs_h * guestfs_create (void) { @@ -175,6 +182,21 @@ guestfs_create (void) */ g->msg_next_serial = 0x00123400; + /* Link the handles onto a global list. This is the one area + * where the library needs to be made thread-safe. (XXX) + */ + /* acquire mutex (XXX) */ + g->next = handles; + handles = g; + if (!atexit_handler_set) { + atexit (close_handles); + atexit_handler_set = 1; + } + /* release mutex (XXX) */ + + if (g->verbose) + fprintf (stderr, "new guestfs handle %p\n", g); + return g; } @@ -183,6 +205,7 @@ guestfs_close (guestfs_h *g) { int i; char filename[256]; + guestfs_h *gg; if (g->state == NO_HANDLE) { /* Not safe to call 'error' here, so ... */ @@ -190,6 +213,9 @@ guestfs_close (guestfs_h *g) return; } + if (g->verbose) + fprintf (stderr, "closing guestfs handle %p (state %d)\n", g, g->state); + /* Remove any handlers that might be called back before we kill the * subprocess. */ @@ -216,9 +242,26 @@ guestfs_close (guestfs_h *g) /* Mark the handle as dead before freeing it. */ g->state = NO_HANDLE; + /* acquire mutex (XXX) */ + if (handles == g) + handles = g->next; + else { + for (gg = handles; gg->next != g; gg = gg->next) + ; + gg->next = g->next; + } + /* release mutex (XXX) */ + free (g); } +/* Close all open handles (called from atexit(3)). */ +static void +close_handles (void) +{ + while (handles) guestfs_close (handles); +} + static void default_error_cb (guestfs_h *g, void *data, const char *msg) { @@ -531,6 +574,8 @@ guestfs_launch (guestfs_h *g) close (rfd[0]); dup (wfd[0]); dup (rfd[1]); + close (wfd[0]); + close (rfd[1]); #if 0 /* Set up a new process group, so we can signal this process -- 1.8.3.1