From 05712b2457a44ee0f0020eced77db03c2aa419a1 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Wed, 15 Apr 2009 23:54:51 +0100 Subject: [PATCH] 'guestfish edit' commands and several bugfixes. --- TODO | 6 - daemon/file.c | 4 +- fish/Makefile.am | 1 + fish/alloc.c | 4 +- fish/edit.c | 185 ++++++++++++++++++++++++++++ fish/fish.c | 31 ++++- fish/fish.h | 5 +- guestfish.pod | 2 +- src/generator.ml | 24 +++- tests.c | 361 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 10 files changed, 599 insertions(+), 24 deletions(-) create mode 100644 fish/edit.c diff --git a/TODO b/TODO index 598cdd8..3d60f1d 100644 --- a/TODO +++ b/TODO @@ -26,9 +26,3 @@ whole filesystems and subdirectories. The current protocol is message-oriented and doesn't support streaming. But it's simple enough that we could add this. - ----------------------------------------------------------------------- - -(From Ján ONDREJ (SAL)) - -guestfish edit diff --git a/daemon/file.c b/daemon/file.c index c09de71..16dfb00 100644 --- a/daemon/file.c +++ b/daemon/file.c @@ -40,7 +40,7 @@ do_touch (const char *path) ABS_PATH (path, -1); CHROOT_IN; - fd = open (path, O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666); + fd = open (path, O_WRONLY | O_CREAT | O_NOCTTY, 0666); CHROOT_OUT; if (fd == -1) { @@ -293,7 +293,7 @@ do_write_file (const char *path, const char *content, int size) size = strlen (content); CHROOT_IN; - fd = open (path, O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666); + fd = open (path, O_WRONLY | O_TRUNC | O_CREAT | O_NOCTTY, 0666); CHROOT_OUT; if (fd == -1) { diff --git a/fish/Makefile.am b/fish/Makefile.am index 8efa5a5..4164103 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -21,6 +21,7 @@ guestfish_SOURCES = \ alloc.c \ cmds.c \ completion.c \ + edit.c \ fish.c \ fish.h guestfish_CFLAGS = \ diff --git a/fish/alloc.c b/fish/alloc.c index 8979acf..594d0a5 100644 --- a/fish/alloc.c +++ b/fish/alloc.c @@ -30,7 +30,7 @@ static int parse_size (const char *str, off_t *size_rtn); int -do_alloc (int argc, char *argv[]) +do_alloc (const char *cmd, int argc, char *argv[]) { off_t size; int fd; @@ -48,7 +48,7 @@ do_alloc (int argc, char *argv[]) return -1; } - fd = open (argv[0], O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666); + fd = open (argv[0], O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC, 0666); if (fd == -1) { perror (argv[0]); return -1; diff --git a/fish/edit.c b/fish/edit.c new file mode 100644 index 0000000..c27f100 --- /dev/null +++ b/fish/edit.c @@ -0,0 +1,185 @@ +/* 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 + +#include +#include +#include +#include +#include +#include + +#include "fish.h" + +/* guestfish edit command, suggested by Ján Ondrej, implemented by RWMJ */ + +static int +xwrite (int fd, const void *buf, size_t len) +{ + int r; + + while (len > 0) { + r = write (fd, buf, len); + if (r == -1) { + perror ("write"); + return -1; + } + buf += r; + len -= r; + } + + return 0; +} + +static char * +load_file (const char *filename, int *len_r) +{ + int fd, r, start; + char *content = NULL, *p; + char buf[65536]; + + *len_r = 0; + + fd = open (filename, O_RDONLY); + if (fd == -1) { + perror (filename); + return NULL; + } + + while ((r = read (fd, buf, sizeof buf)) > 0) { + start = *len_r; + *len_r += r; + p = realloc (content, *len_r + 1); + if (p == NULL) { + perror ("realloc"); + free (content); + return NULL; + } + content = p; + memcpy (content + start, buf, r); + content[start+r] = '\0'; + } + + if (r == -1) { + perror (filename); + free (content); + return NULL; + } + + if (close (fd) == -1) { + perror (filename); + free (content); + return NULL; + } + + return content; +} + +int +do_edit (const char *cmd, int argc, char *argv[]) +{ + char filename[] = "/tmp/guestfishXXXXXX"; + char buf[256]; + const char *editor; + char *content, *content_new; + int r, fd, size; + + if (argc != 1) { + fprintf (stderr, "use '%s filename' to edit a file\n", cmd); + return -1; + } + + /* Choose an editor. */ + if (strcasecmp (cmd, "vi") == 0) + editor = "vi"; + else if (strcasecmp (cmd, "emacs") == 0) + editor = "emacs -nw"; + else { + editor = getenv ("EDITOR"); + if (editor == NULL) + editor = "vi"; /* could be cruel here and choose ed(1) */ + } + + /* Download the file and write it to a temporary. */ + fd = mkstemp (filename); + if (fd == -1) { + perror ("mkstemp"); + return -1; + } + + if ((content = guestfs_cat (g, argv[0])) == NULL) { + close (fd); + unlink (filename); + return -1; + } + + if (xwrite (fd, content, strlen (content)) == -1) { + close (fd); + unlink (filename); + free (content); + return -1; + } + + if (close (fd) == -1) { + perror (filename); + unlink (filename); + free (content); + return -1; + } + + /* Edit it. */ + /* XXX Safe? */ + snprintf (buf, sizeof buf, "%s %s", editor, filename); + + r = system (buf); + if (r != 0) { + perror (buf); + unlink (filename); + free (content); + return -1; + } + + /* Reload it. */ + content_new = load_file (filename, &size); + if (content_new == NULL) { + unlink (filename); + free (content); + return -1; + } + + unlink (filename); + + /* Changed? */ + if (strlen (content) == size && strncmp (content, content_new, size) == 0) { + free (content); + free (content_new); + return 0; + } + + /* Write new content. */ + if (guestfs_write_file (g, argv[0], content_new, size) == -1) { + free (content); + free (content_new); + return -1; + } + + free (content); + free (content_new); + return 0; +} diff --git a/fish/fish.c b/fish/fish.c index b96d1d9..41776ca 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -493,7 +493,11 @@ issue_command (const char *cmd, char *argv[]) } else if (strcasecmp (cmd, "alloc") == 0 || strcasecmp (cmd, "allocate") == 0) - return do_alloc (argc, argv); + return do_alloc (cmd, argc, argv); + else if (strcasecmp (cmd, "edit") == 0 || + strcasecmp (cmd, "vi") == 0 || + strcasecmp (cmd, "emacs") == 0) + return do_edit (cmd, argc, argv); else return run_action (cmd, argc, argv); } @@ -509,6 +513,8 @@ list_builtin_commands (void) printf ("%-20s %s\n", "alloc", "allocate an image"); + printf ("%-20s %s\n", + "edit", "edit a file in the image"); /* actions are printed after this (see list_commands) */ } @@ -518,7 +524,8 @@ display_builtin_command (const char *cmd) { /* help for actions is auto-generated, see display_command */ - if (strcasecmp (cmd, "alloc") == 0) + if (strcasecmp (cmd, "alloc") == 0 || + strcasecmp (cmd, "allocate") == 0) printf ("alloc - allocate an image\n" " alloc \n" "\n" @@ -534,11 +541,29 @@ display_builtin_command (const char *cmd) " M or MB number of megabytes\n" " G or GB number of gigabytes\n" " sects number of 512 byte sectors\n"); + else if (strcasecmp (cmd, "edit") == 0 || + strcasecmp (cmd, "vi") == 0 || + strcasecmp (cmd, "emacs") == 0) + printf ("edit - edit a file in the image\n" + " edit \n" + "\n" + " This is used to edit a file.\n" + "\n" + " It is the equivalent of (and is implemented by)\n" + " running \"cat\", editing locally, and then \"write-file\".\n" + "\n" + " Normally it uses $EDITOR, but if you use the aliases\n" + " \"vi\" or \"emacs\" you will get those editors.\n" + "\n" + " NOTE: This will not work reliably for large files\n" + " (> 2 MB) or binary files containing \\0 bytes.\n"); else if (strcasecmp (cmd, "help") == 0) printf ("help - display a list of commands or help on a command\n" " help cmd\n" " help\n"); - else if (strcasecmp (cmd, "quit") == 0) + else if (strcasecmp (cmd, "quit") == 0 || + strcasecmp (cmd, "exit") == 0 || + strcasecmp (cmd, "q") == 0) printf ("quit - quit guestfish\n" " quit\n"); else diff --git a/fish/fish.h b/fish/fish.h index e45f034..c4450a7 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -45,6 +45,9 @@ extern int run_action (const char *cmd, int argc, char *argv[]); extern char **do_completion (const char *text, int start, int end); /* in alloc.c */ -extern int do_alloc (int argc, char *argv[]); +extern int do_alloc (const char *cmd, int argc, char *argv[]); + +/* in edit.c */ +extern int do_edit (const char *cmd, int argc, char *argv[]); #endif /* FISH_H */ diff --git a/guestfish.pod b/guestfish.pod index 1dc7730..f2e4591 100644 --- a/guestfish.pod +++ b/guestfish.pod @@ -18,7 +18,7 @@ Create a new C file in a guest: add disk.img run mount /dev/VolGroup00/LogVol00 / - upload new_motd /etc/motd + write_file /etc/motd "Hello users" 0 _EOF_ List the LVs in a guest: diff --git a/src/generator.ml b/src/generator.ml index 154614e..4653d65 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -914,12 +914,24 @@ pass C as a single element list, when the single element being the string C<,> (comma)."); ("write_file", (RErr, [String "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning], - [InitEmpty, TestOutput ( - [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; - ["mkfs"; "ext2"; "/dev/sda1"]; - ["mount"; "/dev/sda1"; "/"]; - ["write_file"; "/new"; "new file contents"; "0"]; - ["cat"; "/new"]], "new file contents")], + [InitBasicFS, TestOutput ( + [["write_file"; "/new"; "new file contents"; "0"]; + ["cat"; "/new"]], "new file contents"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "\nnew file contents\n"; "0"]; + ["cat"; "/new"]], "\nnew file contents\n"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "\n\n"; "0"]; + ["cat"; "/new"]], "\n\n"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; ""; "0"]; + ["cat"; "/new"]], ""); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "\n\n\n"; "0"]; + ["cat"; "/new"]], "\n\n\n"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "\n"; "0"]; + ["cat"; "/new"]], "\n")], "create a file", "\ This call creates a file called C. The contents of the diff --git a/tests.c b/tests.c index b3d2f49..5f05e6b 100644 --- a/tests.c +++ b/tests.c @@ -1064,7 +1064,7 @@ static int test_umount_1 (void) static int test_write_file_0 (void) { - /* InitEmpty for write_file (0) */ + /* InitBasicFS for write_file (0): create ext2 on /dev/sda1 */ { int r; suppress_error = 0; @@ -1079,7 +1079,6 @@ static int test_write_file_0 (void) if (r == -1) return -1; } - /* TestOutput for write_file (0) */ { char *lines[] = { ",", @@ -1105,6 +1104,7 @@ static int test_write_file_0 (void) if (r == -1) return -1; } + /* TestOutput for write_file (0) */ { int r; suppress_error = 0; @@ -1127,6 +1127,331 @@ static int test_write_file_0 (void) return 0; } +static int test_write_file_1 (void) +{ + /* InitBasicFS for write_file (1): create ext2 on /dev/sda1 */ + { + int r; + suppress_error = 0; + r = guestfs_umount_all (g); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_lvm_remove_all (g); + if (r == -1) + return -1; + } + { + char *lines[] = { + ",", + NULL + }; + int r; + suppress_error = 0; + r = guestfs_sfdisk (g, "/dev/sda", 0, 0, 0, lines); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mkfs (g, "ext2", "/dev/sda1"); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mount (g, "/dev/sda1", "/"); + if (r == -1) + return -1; + } + /* TestOutput for write_file (1) */ + { + int r; + suppress_error = 0; + r = guestfs_write_file (g, "/new", "\nnew file contents\n", 0); + if (r == -1) + return -1; + } + { + char *r; + suppress_error = 0; + r = guestfs_cat (g, "/new"); + if (r == NULL) + return -1; + if (strcmp (r, "\nnew file contents\n") != 0) { + fprintf (stderr, "test_write_file_1: expected \"\nnew file contents\n\" but got \"%s\"\n", r); + return -1; + } + free (r); + } + return 0; +} + +static int test_write_file_2 (void) +{ + /* InitBasicFS for write_file (2): create ext2 on /dev/sda1 */ + { + int r; + suppress_error = 0; + r = guestfs_umount_all (g); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_lvm_remove_all (g); + if (r == -1) + return -1; + } + { + char *lines[] = { + ",", + NULL + }; + int r; + suppress_error = 0; + r = guestfs_sfdisk (g, "/dev/sda", 0, 0, 0, lines); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mkfs (g, "ext2", "/dev/sda1"); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mount (g, "/dev/sda1", "/"); + if (r == -1) + return -1; + } + /* TestOutput for write_file (2) */ + { + int r; + suppress_error = 0; + r = guestfs_write_file (g, "/new", "\n\n", 0); + if (r == -1) + return -1; + } + { + char *r; + suppress_error = 0; + r = guestfs_cat (g, "/new"); + if (r == NULL) + return -1; + if (strcmp (r, "\n\n") != 0) { + fprintf (stderr, "test_write_file_2: expected \"\n\n\" but got \"%s\"\n", r); + return -1; + } + free (r); + } + return 0; +} + +static int test_write_file_3 (void) +{ + /* InitBasicFS for write_file (3): create ext2 on /dev/sda1 */ + { + int r; + suppress_error = 0; + r = guestfs_umount_all (g); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_lvm_remove_all (g); + if (r == -1) + return -1; + } + { + char *lines[] = { + ",", + NULL + }; + int r; + suppress_error = 0; + r = guestfs_sfdisk (g, "/dev/sda", 0, 0, 0, lines); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mkfs (g, "ext2", "/dev/sda1"); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mount (g, "/dev/sda1", "/"); + if (r == -1) + return -1; + } + /* TestOutput for write_file (3) */ + { + int r; + suppress_error = 0; + r = guestfs_write_file (g, "/new", "", 0); + if (r == -1) + return -1; + } + { + char *r; + suppress_error = 0; + r = guestfs_cat (g, "/new"); + if (r == NULL) + return -1; + if (strcmp (r, "") != 0) { + fprintf (stderr, "test_write_file_3: expected \"\" but got \"%s\"\n", r); + return -1; + } + free (r); + } + return 0; +} + +static int test_write_file_4 (void) +{ + /* InitBasicFS for write_file (4): create ext2 on /dev/sda1 */ + { + int r; + suppress_error = 0; + r = guestfs_umount_all (g); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_lvm_remove_all (g); + if (r == -1) + return -1; + } + { + char *lines[] = { + ",", + NULL + }; + int r; + suppress_error = 0; + r = guestfs_sfdisk (g, "/dev/sda", 0, 0, 0, lines); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mkfs (g, "ext2", "/dev/sda1"); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mount (g, "/dev/sda1", "/"); + if (r == -1) + return -1; + } + /* TestOutput for write_file (4) */ + { + int r; + suppress_error = 0; + r = guestfs_write_file (g, "/new", "\n\n\n", 0); + if (r == -1) + return -1; + } + { + char *r; + suppress_error = 0; + r = guestfs_cat (g, "/new"); + if (r == NULL) + return -1; + if (strcmp (r, "\n\n\n") != 0) { + fprintf (stderr, "test_write_file_4: expected \"\n\n\n\" but got \"%s\"\n", r); + return -1; + } + free (r); + } + return 0; +} + +static int test_write_file_5 (void) +{ + /* InitBasicFS for write_file (5): create ext2 on /dev/sda1 */ + { + int r; + suppress_error = 0; + r = guestfs_umount_all (g); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_lvm_remove_all (g); + if (r == -1) + return -1; + } + { + char *lines[] = { + ",", + NULL + }; + int r; + suppress_error = 0; + r = guestfs_sfdisk (g, "/dev/sda", 0, 0, 0, lines); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mkfs (g, "ext2", "/dev/sda1"); + if (r == -1) + return -1; + } + { + int r; + suppress_error = 0; + r = guestfs_mount (g, "/dev/sda1", "/"); + if (r == -1) + return -1; + } + /* TestOutput for write_file (5) */ + { + int r; + suppress_error = 0; + r = guestfs_write_file (g, "/new", "\n", 0); + if (r == -1) + return -1; + } + { + char *r; + suppress_error = 0; + r = guestfs_cat (g, "/new"); + if (r == NULL) + return -1; + if (strcmp (r, "\n") != 0) { + fprintf (stderr, "test_write_file_5: expected \"\n\" but got \"%s\"\n", r); + return -1; + } + free (r); + } + return 0; +} + static int test_mkfs_0 (void) { /* InitEmpty for mkfs (0) */ @@ -4174,7 +4499,7 @@ int main (int argc, char *argv[]) exit (1); } - nr_tests = 58; + nr_tests = 63; test_num++; printf ("%3d/%3d test_blockdev_rereadpt_0\n", test_num, nr_tests); @@ -4297,6 +4622,36 @@ int main (int argc, char *argv[]) failed++; } test_num++; + printf ("%3d/%3d test_write_file_1\n", test_num, nr_tests); + if (test_write_file_1 () == -1) { + printf ("test_write_file_1 FAILED\n"); + failed++; + } + test_num++; + printf ("%3d/%3d test_write_file_2\n", test_num, nr_tests); + if (test_write_file_2 () == -1) { + printf ("test_write_file_2 FAILED\n"); + failed++; + } + test_num++; + printf ("%3d/%3d test_write_file_3\n", test_num, nr_tests); + if (test_write_file_3 () == -1) { + printf ("test_write_file_3 FAILED\n"); + failed++; + } + test_num++; + printf ("%3d/%3d test_write_file_4\n", test_num, nr_tests); + if (test_write_file_4 () == -1) { + printf ("test_write_file_4 FAILED\n"); + failed++; + } + test_num++; + printf ("%3d/%3d test_write_file_5\n", test_num, nr_tests); + if (test_write_file_5 () == -1) { + printf ("test_write_file_5 FAILED\n"); + failed++; + } + test_num++; printf ("%3d/%3d test_mkfs_0\n", test_num, nr_tests); if (test_mkfs_0 () == -1) { printf ("test_mkfs_0 FAILED\n"); -- 1.8.3.1