diff options
author | Richard Jones <rjones@trick.home.annexia.org> | 2009-07-13 13:06:26 +0100 |
---|---|---|
committer | Richard Jones <rjones@trick.home.annexia.org> | 2009-07-14 14:08:31 +0100 |
commit | a86aa7d152ed996170714a3a4516eab58bf8b59d (patch) | |
tree | b29a2081d1951b13c0da03d01973a0e8c507ba5b /fish | |
parent | 5d6cc25900df4e13e4085f13d4f846cc9719ce61 (diff) | |
download | libguestfs-a86aa7d152ed996170714a3a4516eab58bf8b59d.tar.gz libguestfs-a86aa7d152ed996170714a3a4516eab58bf8b59d.tar.xz libguestfs-a86aa7d152ed996170714a3a4516eab58bf8b59d.zip |
Guestfish feature: remote control of guestfish over a pipe.
The use case is to have a long-running guestfish process in
a shell script, and thus to avoid the overhead of starting
guestfish each time. Do:
eval `guestfish --listen`
guestfish --remote somecmd
guestfish --remote someothercmd
guestfish --remote exit
This patch also supports having multiple guestfish processes
at the same time.
The protocol is simple XDR messages over a Unix domain socket.
Diffstat (limited to 'fish')
-rw-r--r-- | fish/Makefile.am | 19 | ||||
-rw-r--r-- | fish/fish.c | 68 | ||||
-rw-r--r-- | fish/fish.h | 7 | ||||
-rw-r--r-- | fish/rc.c | 272 | ||||
-rw-r--r-- | fish/rc_protocol.x | 36 |
5 files changed, 395 insertions, 7 deletions
diff --git a/fish/Makefile.am b/fish/Makefile.am index 03619f36..5255e853 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -29,10 +29,29 @@ guestfish_SOURCES = \ glob.c \ lcd.c \ more.c \ + rc.c \ + rc_protocol.c \ + rc_protocol.h \ reopen.c \ time.c +BUILT_SOURCES = \ + rc_protocol.c \ + rc_protocol.h + guestfish_CFLAGS = \ -I$(top_srcdir)/src -I$(top_builddir)/src -Wall \ -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' guestfish_LDADD = $(top_builddir)/src/libguestfs.la $(LIBREADLINE) + +if HAVE_RPCGEN +rc_protocol.c: rc_protocol.x + rm -f $@-t + $(RPCGEN) -c -o $@-t $< + mv $@-t $@ + +rc_protocol.h: rc_protocol.x + rm -f $@-t + $(RPCGEN) -h -o $@-t $< + mv $@-t $@ +endif diff --git a/fish/fish.c b/fish/fish.c index a093395b..4042bbcf 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -69,6 +69,9 @@ int read_only = 0; int quit = 0; int verbose = 0; int echo_commands = 0; +int remote_control_listen = 0; +int remote_control = 0; +int exit_on_error = 1; int launch (guestfs_h *_g) @@ -109,8 +112,10 @@ usage (void) " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" " -f|--file file Read commands from file\n" " -i|--inspector Run virt-inspector to get disk mountpoints\n" + " --listen Listen for remote commands\n" " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" " -n|--no-sync Don't autosync\n" + " --remote[=pid] Send commands to remote guestfish\n" " -r|--ro Mount read-only\n" " -v|--verbose Verbose messages\n" " -x Echo each command before executing it\n" @@ -128,9 +133,11 @@ main (int argc, char *argv[]) { "file", 1, 0, 'f' }, { "help", 0, 0, '?' }, { "inspector", 0, 0, 'i' }, + { "listen", 0, 0, 0 }, { "mount", 1, 0, 'm' }, { "no-dest-paths", 0, 0, 'D' }, { "no-sync", 0, 0, 'n' }, + { "remote", 2, 0, 0 }, { "ro", 0, 0, 'r' }, { "verbose", 0, 0, 'v' }, { "version", 0, 0, 'V' }, @@ -141,7 +148,9 @@ main (int argc, char *argv[]) struct mp *mps = NULL; struct mp *mp; char *p, *file = NULL; - int c, inspector = 0; + int c; + int inspector = 0; + int option_index; struct sigaction sa; initialize_readline (); @@ -176,10 +185,33 @@ main (int argc, char *argv[]) guestfs_set_path (g, "appliance:" GUESTFS_DEFAULT_PATH); for (;;) { - c = getopt_long (argc, argv, options, long_options, NULL); + c = getopt_long (argc, argv, options, long_options, &option_index); if (c == -1) break; switch (c) { + case 0: /* options which are long only */ + if (strcmp (long_options[option_index].name, "listen") == 0) + remote_control_listen = 1; + else if (strcmp (long_options[option_index].name, "remote") == 0) { + if (optarg) { + if (sscanf (optarg, "%d", &remote_control) != 1) { + fprintf (stderr, _("guestfish: --listen=PID: PID was not a number: %s\n"), optarg); + exit (1); + } + } else { + p = getenv ("GUESTFISH_PID"); + if (!p || sscanf (p, "%d", &remote_control) != 1) { + fprintf (stderr, _("guestfish: remote: $GUESTFISH_PID must be set to the PID of the remote process\n")); + exit (1); + } + } + } else { + fprintf (stderr, _("guestfish: unknown long option: %s (%d)\n"), + long_options[option_index].name, option_index); + exit (1); + } + break; + case 'a': if (access (optarg, R_OK) != 0) { perror (optarg); @@ -274,8 +306,8 @@ main (int argc, char *argv[]) char cmd[1024]; int r; - if (drvs || mps) { - fprintf (stderr, _("guestfish: cannot use -i option with -a or -m\n")); + if (drvs || mps || remote_control_listen || remote_control) { + fprintf (stderr, _("guestfish: cannot use -i option with -a, -m, --listen or --remote\n")); exit (1); } if (optind >= argc) { @@ -327,6 +359,24 @@ main (int argc, char *argv[]) mount_mps (mps); } + /* Remote control? */ + if (remote_control_listen && remote_control) { + fprintf (stderr, _("guestfish: cannot use --listen and --remote options at the same time\n")); + exit (1); + } + + if (remote_control_listen) { + if (optind < argc) { + fprintf (stderr, _("guestfish: extra parameters on the command line with --listen flag\n")); + exit (1); + } + if (file) { + fprintf (stderr, _("guestfish: cannot use --listen and --file options at the same time\n")); + exit (1); + } + rc_listen (); + } + /* -f (file) parameter? */ if (file) { close (0); @@ -464,7 +514,6 @@ script (int prompt) char *argv[64]; int i, len; int global_exit_on_error = !prompt; - int exit_on_error; if (prompt) printf (_("\n" @@ -641,6 +690,8 @@ cmdline (char *argv[], int optind, int argc) const char *cmd; char **params; + exit_on_error = 1; + if (optind >= argc) return; cmd = argv[optind++]; @@ -711,7 +762,12 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) for (argc = 0; argv[argc] != NULL; ++argc) ; - if (strcasecmp (cmd, "help") == 0) { + /* If --remote was set, then send this command to a remote process. */ + if (remote_control) + r = rc_remote (remote_control, cmd, argc, argv, exit_on_error); + + /* Otherwise execute it locally. */ + else if (strcasecmp (cmd, "help") == 0) { if (argc == 0) list_commands (); else diff --git a/fish/fish.h b/fish/fish.h index d0dc7a90..f911eed7 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -1,4 +1,4 @@ -/* libguestfs - the guestfsd daemon +/* libguestfs - guestfish shell * Copyright (C) 2009 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify @@ -77,6 +77,11 @@ extern int do_glob (const char *cmd, int argc, char *argv[]); /* in more.c */ extern int do_more (const char *cmd, int argc, char *argv[]); +/* in rc.c (remote control) */ +extern void rc_listen (void); +extern int rc_remote (int pid, const char *cmd, int argc, char *argv[], + int exit_on_error); + /* in reopen.c */ extern int do_reopen (const char *cmd, int argc, char *argv[]); diff --git a/fish/rc.c b/fish/rc.c new file mode 100644 index 00000000..0d67a62b --- /dev/null +++ b/fish/rc.c @@ -0,0 +1,272 @@ +/* guestfish - the filesystem interactive shell + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <signal.h> + +#include <rpc/types.h> +#include <rpc/xdr.h> + +#include "fish.h" +#include "rc_protocol.h" + +#define UNIX_PATH_MAX 108 + +static void +create_sockpath (pid_t pid, char *sockpath, int len, struct sockaddr_un *addr) +{ + char dir[128]; + uid_t euid = geteuid (); + + snprintf (dir, sizeof dir, "/tmp/.guestfish-%d", euid); + mkdir (dir, 0700); + + snprintf (sockpath, len, "/tmp/.guestfish-%d/socket-%d", euid, pid); + + addr->sun_family = AF_UNIX; + strcpy (addr->sun_path, sockpath); +} + +/* Remote control server. */ +void +rc_listen (void) +{ + char sockpath[128]; + pid_t pid; + struct sockaddr_un addr; + int sock, s, i, fd; + FILE *fp; + XDR xdr, xdr2; + guestfish_hello hello; + guestfish_call call; + guestfish_reply reply; + char **argv; + int argc; + + memset (&hello, 0, sizeof hello); + memset (&call, 0, sizeof call); + + pid = fork (); + if (pid == -1) { + perror ("fork"); + exit (1); + } + + if (pid > 0) { + /* Parent process. */ + printf ("export GUESTFISH_PID=%d\n", pid); + fflush (stdout); + _exit (0); + } + + /* Child process. + * + * Create the listening socket for accepting commands. + * + * Unfortunately there is a small but unavoidable race here. We + * don't know the PID until after we've forked, so we cannot be + * sure the socket is created from the point of view of the parent + * (if the child is very slow). + */ + pid = getpid (); + create_sockpath (pid, sockpath, sizeof sockpath, &addr); + + sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + perror ("socket"); + exit (1); + } + unlink (sockpath); + if (bind (sock, (struct sockaddr *) &addr, sizeof addr) == -1) { + perror (sockpath); + exit (1); + } + if (listen (sock, 4) == -1) { + perror ("listen"); + exit (1); + } + + /* Now close stdout and substitute /dev/null. This is necessary + * so that eval `guestfish --listen` doesn't block forever. + */ + fd = open ("/dev/null", O_WRONLY); + if (fd == -1) + perror ("/dev/null"); + else { + dup2 (fd, 1); + close (fd); + } + + /* Read commands and execute them. */ + while (!quit) { + s = accept (sock, NULL, NULL); + if (s == -1) + perror ("accept"); + else { + fp = fdopen (s, "r+"); + xdrstdio_create (&xdr, fp, XDR_DECODE); + + if (!xdr_guestfish_hello (&xdr, &hello)) { + fprintf (stderr, _("guestfish: protocol error: could not read 'hello' message\n")); + goto error; + } + + if (strcmp (hello.vers, PACKAGE_VERSION) != 0) { + fprintf (stderr, _("guestfish: protocol error: version mismatch, server version '%s' does not match client version '%s'. The two versions must match exactly.\n"), + PACKAGE_VERSION, + hello.vers); + xdr_free ((xdrproc_t) xdr_guestfish_hello, (char *) &hello); + goto error; + } + xdr_free ((xdrproc_t) xdr_guestfish_hello, (char *) &hello); + + while (xdr_guestfish_call (&xdr, &call)) { + /* We have to extend and NULL-terminate the argv array. */ + argc = call.args.args_len; + argv = realloc (call.args.args_val, (argc+1) * sizeof (char *)); + if (argv == NULL) { + perror ("realloc"); + exit (1); + } + call.args.args_val = argv; + argv[argc] = NULL; + + if (verbose) { + fprintf (stderr, "guestfish(%d): %s", pid, call.cmd); + for (i = 0; i < argc; ++i) + fprintf (stderr, " %s", argv[i]); + fprintf (stderr, "\n"); + } + + /* Run the command. */ + reply.r = issue_command (call.cmd, argv, NULL); + + xdr_free ((xdrproc_t) xdr_guestfish_call, (char *) &call); + + /* Send the reply. */ + xdrstdio_create (&xdr2, fp, XDR_ENCODE); + (void) xdr_guestfish_reply (&xdr2, &reply); + xdr_destroy (&xdr2); + + /* Exit on error? */ + if (call.exit_on_error && reply.r == -1) { + unlink (sockpath); + exit (1); + } + } + + error: + xdr_destroy (&xdr); /* NB. This doesn't close 'fp'. */ + fclose (fp); /* Closes the underlying socket 's'. */ + } + } + + unlink (sockpath); + exit (0); +} + +/* Remote control client. */ +int +rc_remote (int pid, const char *cmd, int argc, char *argv[], + int exit_on_error) +{ + guestfish_hello hello; + guestfish_call call; + guestfish_reply reply; + char sockpath[128]; + struct sockaddr_un addr; + int sock; + FILE *fp; + XDR xdr; + + memset (&reply, 0, sizeof reply); + + /* This is fine as long as we never try to xdr_free this struct. */ + hello.vers = (char *) PACKAGE_VERSION; + + /* Check the other end is still running. */ + if (kill (pid, 0) == -1) { + fprintf (stderr, _("guestfish: remote: looks like the server is not running\n")); + return -1; + } + + create_sockpath (pid, sockpath, sizeof sockpath, &addr); + + sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + perror ("socket"); + return -1; + } + + if (connect (sock, (struct sockaddr *) &addr, sizeof addr) == -1) { + perror (sockpath); + fprintf (stderr, _("guestfish: remote: looks like the server is not running\n")); + close (sock); + return -1; + } + + /* Send the greeting. */ + fp = fdopen (sock, "r+"); + xdrstdio_create (&xdr, fp, XDR_ENCODE); + + if (!xdr_guestfish_hello (&xdr, &hello)) { + fprintf (stderr, _("guestfish: protocol error: could not send initial greeting to server\n")); + fclose (fp); + xdr_destroy (&xdr); + return -1; + } + + /* Send the command. The server supports reading multiple commands + * per connection, but this code only ever sends one command. + */ + call.cmd = (char *) cmd; + call.args.args_len = argc; + call.args.args_val = argv; + call.exit_on_error = exit_on_error; + if (!xdr_guestfish_call (&xdr, &call)) { + fprintf (stderr, _("guestfish: protocol error: could not send initial greeting to server\n")); + fclose (fp); + xdr_destroy (&xdr); + return -1; + } + xdr_destroy (&xdr); + + /* Wait for the reply. */ + xdrstdio_create (&xdr, fp, XDR_DECODE); + + if (!xdr_guestfish_reply (&xdr, &reply)) { + fprintf (stderr, _("guestfish: protocol error: could not decode reply from server\n")); + fclose (fp); + xdr_destroy (&xdr); + return -1; + } + + fclose (fp); + xdr_destroy (&xdr); + + return reply.r; +} diff --git a/fish/rc_protocol.x b/fish/rc_protocol.x new file mode 100644 index 00000000..9d8f0e9f --- /dev/null +++ b/fish/rc_protocol.x @@ -0,0 +1,36 @@ +/* libguestfs - guestfish remote control protocol -*- c -*- + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +typedef string str<>; + +struct guestfish_hello { + /* Client and server version strings must match exactly. We change + * this protocol whenever we want to. + */ + string vers<>; +}; + +struct guestfish_call { + string cmd<>; + str args<>; + bool exit_on_error; +}; + +struct guestfish_reply { + int r; /* 0 or -1 only. */ +}; |