From 2635a9c70e5f8aab85bbcb6b7a645f720d1e4466 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 9 Sep 2010 18:49:10 +0100 Subject: [PATCH] fish: Implement copy-in and copy-out commands. --- fish/Makefile.am | 1 + fish/copy.c | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fish/fish.c | 30 ++++++ fish/fish.h | 5 + fish/guestfish.pod | 34 ++++++ po/POTFILES.in | 1 + 6 files changed, 378 insertions(+) create mode 100644 fish/copy.c diff --git a/fish/Makefile.am b/fish/Makefile.am index 4c5d76f..906d362 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -40,6 +40,7 @@ EXTRA_DIST = \ guestfish_SOURCES = \ $(generator_built) \ alloc.c \ + copy.c \ destpaths.c \ echo.c \ edit.c \ diff --git a/fish/copy.c b/fish/copy.c new file mode 100644 index 0000000..789e430 --- /dev/null +++ b/fish/copy.c @@ -0,0 +1,307 @@ +/* guestfish - the filesystem interactive shell + * Copyright (C) 2010 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 +#include +#include +#include + +#include "fish.h" + +static int make_tar_from_local (const char *local); +static int make_tar_output (const char *local, const char *basename); +static int split_path (char *buf, size_t buf_size, const char *path, char **dirname, char **basename); + +int +do_copy_in (const char *cmd, int argc, char *argv[]) +{ + if (argc < 2) { + fprintf (stderr, + _("use 'copy-in [...] ' to copy files into the image\n")); + return -1; + } + + /* Remote directory is always the last arg. */ + const char *remote = argv[argc-1]; + int nr_locals = argc-1; + + int remote_is_dir = guestfs_is_dir (g, remote); + if (remote_is_dir == -1) + return -1; + + if (!remote_is_dir) { + fprintf (stderr, _("copy-in: target '%s' is not a directory\n"), remote); + return -1; + } + + /* Upload each local one at a time using tar-in. */ + int i; + for (i = 0; i < nr_locals; ++i) { + int fd = make_tar_from_local (argv[i]); + if (fd == -1) + return -1; + + char fdbuf[64]; + snprintf (fdbuf, sizeof fdbuf, "/dev/fd/%d", fd); + + int r = guestfs_tar_in (g, fdbuf, remote); + + if (close (fd) == -1) { + perror ("close (tar-from-local subprocess)"); + r = -1; + } + + int status; + if (wait (&status) == -1) { + perror ("wait (tar-from-local subprocess)"); + return -1; + } + if (!(WIFEXITED (status) && WEXITSTATUS (status) == 0)) + return -1; + + if (r == -1) + return -1; + } + + return 0; +} + +static void tar_create (const char *dir, const char *path) __attribute__((noreturn)); + +/* This creates a subprocess which feeds a tar file back to the + * main guestfish process. + */ +static int +make_tar_from_local (const char *local) +{ + int fd[2]; + + if (pipe (fd) == -1) { + perror ("pipe"); + return -1; + } + + pid_t pid = fork (); + if (pid == -1) { + perror ("fork"); + return -1; + } + + if (pid > 0) { /* Parent */ + close (fd[1]); + return fd[0]; + } + + /* Child. */ + close (fd[0]); + dup2 (fd[1], 1); + close (fd[1]); + + char buf[PATH_MAX]; + char *dirname, *basename; + if (split_path (buf, sizeof buf, local, &dirname, &basename) == -1) + _exit (EXIT_FAILURE); + + tar_create (dirname, basename); +} + +/* Split path into directory name and base name, using the buffer + * provided as a working area. If there is no directory name + * (eg. path == "/") then this can return dirname as NULL. + */ +static int +split_path (char *buf, size_t buf_size, + const char *path, char **dirname, char **basename) +{ + size_t len = strlen (path); + if (len == 0 || len > buf_size - 1) { + fprintf (stderr, _("error: argument is zero length or longer than maximum permitted\n")); + return -1; + } + + strcpy (buf, path); + + if (len >= 2 && buf[len-1] == '/') { + buf[len-1] = '\0'; + len--; + } + + char *p = strrchr (buf, '/'); + if (p && p > buf) { + *p = '\0'; + p++; + if (dirname) *dirname = buf; + if (basename) *basename = p; + } else { + if (dirname) *dirname = NULL; + if (basename) *basename = buf; + } + + return 0; +} + +static void +tar_create (const char *dir, const char *path) +{ + if (dir) + execlp ("tar", "tar", "-C", dir, "-cf", "-", path, NULL); + else + execlp ("tar", "tar", "-cf", "-", path, NULL); + + perror ("execlp: tar"); + _exit (EXIT_FAILURE); +} + +int +do_copy_out (const char *cmd, int argc, char *argv[]) +{ + if (argc < 2) { + fprintf (stderr, + _("use 'copy-out [...] ' to copy files out of the image\n")); + return -1; + } + + /* Local directory is always the last arg. */ + const char *local = argv[argc-1]; + int nr_remotes = argc-1; + + struct stat statbuf; + if (stat (local, &statbuf) == -1 || + ! (S_ISDIR (statbuf.st_mode))) { + fprintf (stderr, _("copy-in: target '%s' is not a directory\n"), local); + return -1; + } + + /* Download each remote one at a time using tar-out. */ + int i, r; + for (i = 0; i < nr_remotes; ++i) { + /* If the remote is a file, download it. If it's a directory, + * create the directory in local first before using tar-out. + */ + r = guestfs_is_file (g, argv[i]); + if (r == -1) + return -1; + if (r == 1) { /* is file */ + char buf[PATH_MAX]; + char *basename; + if (split_path (buf, sizeof buf, argv[i], NULL, &basename) == -1) + return -1; + + char filename[PATH_MAX]; + snprintf (filename, sizeof filename, "%s/%s", local, basename); + if (guestfs_download (g, argv[i], filename) == -1) + return -1; + } + else { /* not a regular file */ + r = guestfs_is_dir (g, argv[i]); + if (r == -1) + return -1; + + if (r == 0) { + fprintf (stderr, _("copy-out: '%s' is not a file or directory\n"), + argv[i]); + return -1; + } + + char buf[PATH_MAX]; + char *basename; + if (split_path (buf, sizeof buf, argv[i], NULL, &basename) == -1) + return -1; + + int fd = make_tar_output (local, basename); + if (fd == -1) + return -1; + + char fdbuf[64]; + snprintf (fdbuf, sizeof fdbuf, "/dev/fd/%d", fd); + + int r = guestfs_tar_out (g, argv[i], fdbuf); + + if (close (fd) == -1) { + perror ("close (tar-output subprocess)"); + r = -1; + } + + int status; + if (wait (&status) == -1) { + perror ("wait (tar-output subprocess)"); + return -1; + } + if (!(WIFEXITED (status) && WEXITSTATUS (status) == 0)) + return -1; + + if (r == -1) + return -1; + } + } + + return 0; +} + +/* This creates a subprocess which takes a tar file from the main + * guestfish process and unpacks it into the 'local/basename' + * directory. + */ +static int +make_tar_output (const char *local, const char *basename) +{ + int fd[2]; + + if (pipe (fd) == -1) { + perror ("pipe"); + return -1; + } + + pid_t pid = fork (); + if (pid == -1) { + perror ("fork"); + return -1; + } + + if (pid > 0) { /* Parent */ + close (fd[0]); + return fd[1]; + } + + /* Child. */ + close (fd[1]); + dup2 (fd[0], 0); + close (fd[0]); + + if (chdir (local) == -1) { + perror (local); + _exit (EXIT_FAILURE); + } + + mkdir (basename, 0777); + + if (chdir (basename) == -1) { + perror (basename); + _exit (EXIT_FAILURE); + } + + execlp ("tar", "tar", "-xf", "-", NULL); + perror ("execlp: tar"); + _exit (EXIT_FAILURE); +} diff --git a/fish/fish.c b/fish/fish.c index 6433942..cf547c4 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -1061,6 +1061,12 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) else if (STRCASEEQ (cmd, "alloc") || STRCASEEQ (cmd, "allocate")) r = do_alloc (cmd, argc, argv); + else if (STRCASEEQ (cmd, "copy-in") || + STRCASEEQ (cmd, "copy_in")) + r = do_copy_in (cmd, argc, argv); + else if (STRCASEEQ (cmd, "copy-out") || + STRCASEEQ (cmd, "copy_out")) + r = do_copy_out (cmd, argc, argv); else if (STRCASEEQ (cmd, "echo")) r = do_echo (cmd, argc, argv); else if (STRCASEEQ (cmd, "edit") || @@ -1126,6 +1132,10 @@ list_builtin_commands (void) printf ("%-20s %s\n", "alloc", _("allocate an image")); printf ("%-20s %s\n", + "copy-in", _("copy files into an image")); + printf ("%-20s %s\n", + "copy-out", _("copy files out of an image")); + printf ("%-20s %s\n", "echo", _("display a line of text")); printf ("%-20s %s\n", "edit", _("edit a file in the image")); @@ -1166,6 +1176,26 @@ display_builtin_command (const char *cmd) )); return 0; } + else if (STRCASEEQ (cmd, "copy-in") || + STRCASEEQ (cmd, "copy_in")) { + printf (_("copy-in - copy files into an image\n" + " copy-in [ ...] \n" + "\n" + " Copy local files or directories recursively into the\n" + " image, placing them on a remote directory.\n" + )); + return 0; + } + else if (STRCASEEQ (cmd, "copy-out") || + STRCASEEQ (cmd, "copy_out")) { + printf (_("copy-out - copy files out of an image\n" + " copy-out [ ...] \n" + "\n" + " Copy remote files or directories recursively out of the\n" + " image, placing them in a local directory.\n" + )); + return 0; + } else if (STRCASEEQ (cmd, "echo")) { printf (_("echo - display a line of text\n" " echo [ ...]\n" diff --git a/fish/fish.h b/fish/fish.h index ec1c82f..f908a22 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -94,6 +94,10 @@ extern int alloc_disk (const char *filename, const char *size, int add, int sparse); extern int parse_size (const char *str, off_t *size_rtn); +/* in copy.c */ +extern int do_copy_in (const char *cmd, int argc, char *argv[]); +extern int do_copy_out (const char *cmd, int argc, char *argv[]); + /* in echo.c */ extern int do_echo (const char *cmd, int argc, char *argv[]); @@ -164,6 +168,7 @@ extern int add_libvirt_drives (const char *guest); "help", \ "quit", "exit", "q", \ "alloc", "allocate", \ + "copy-in", "copy-out", \ "echo", \ "edit", "vi", "emacs", \ "lcd", \ diff --git a/fish/guestfish.pod b/fish/guestfish.pod index aeb4c8d..70de8e3 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -781,6 +781,40 @@ For more advanced image creation, see L utility. Size can be specified using standard suffixes, eg. C<1M>. +=head2 copy-in + + copy-in local [local ...] /remotedir + +C copies local files or directories recursively into the disk +image, placing them in the directory called C (which must +exist). This guestfish meta-command turns into a sequence of +L and other commands as necessary. + +Multiple local files and directories can be specified, but the last +parameter must always be a remote directory. Wildcards cannot be +used. + +=head2 copy-out + + copy-out remote [remote ...] localdir + +C copies remote files or directories recursively out of the +disk image, placing them on the host disk in a local directory called +C (which must exist). This guestfish meta-command turns +into a sequence of L, L and other commands as +necessary. + +Multiple remote files and directories can be specified, but the last +parameter must always be a local directory. To download to the +current directory, use C<.> as in: + + copy-out /home . + +Wildcards cannot be used in the ordinary command, but you can use +this with the help of L like this: + + glob copy-out /home/* . + =head2 echo echo [params ...] diff --git a/po/POTFILES.in b/po/POTFILES.in index f591374..4518eab 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -72,6 +72,7 @@ daemon/zerofree.c fish/alloc.c fish/cmds.c fish/completion.c +fish/copy.c fish/destpaths.c fish/echo.c fish/edit.c -- 1.8.3.1