'guestfish edit' commands and several bugfixes.
authorRichard Jones <rjones@redhat.com>
Wed, 15 Apr 2009 22:54:51 +0000 (23:54 +0100)
committerRichard Jones <rjones@redhat.com>
Wed, 15 Apr 2009 22:54:51 +0000 (23:54 +0100)
TODO
daemon/file.c
fish/Makefile.am
fish/alloc.c
fish/edit.c [new file with mode: 0644]
fish/fish.c
fish/fish.h
guestfish.pod
src/generator.ml
tests.c

diff --git a/TODO b/TODO
index 598cdd8..3d60f1d 100644 (file)
--- 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 <filename>
index c09de71..16dfb00 100644 (file)
@@ -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) {
index 8efa5a5..4164103 100644 (file)
@@ -21,6 +21,7 @@ guestfish_SOURCES = \
        alloc.c \
        cmds.c \
        completion.c \
+       edit.c \
        fish.c \
        fish.h
 guestfish_CFLAGS = \
index 8979acf..594d0a5 100644 (file)
@@ -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 (file)
index 0000000..c27f100
--- /dev/null
@@ -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 <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <inttypes.h>
+
+#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;
+}
index b96d1d9..41776ca 100644 (file)
@@ -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 <filename> <size>\n"
            "\n"
@@ -534,11 +541,29 @@ display_builtin_command (const char *cmd)
            "    <nn>M or <nn>MB  number of megabytes\n"
            "    <nn>G or <nn>GB  number of gigabytes\n"
            "    <nn>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 <filename>\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
index e45f034..c4450a7 100644 (file)
@@ -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 */
index 1dc7730..f2e4591 100644 (file)
@@ -18,7 +18,7 @@ Create a new C</etc/motd> 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:
index 154614e..4653d65 100755 (executable)
@@ -914,12 +914,24 @@ pass C<lines> 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<path>.  The contents of the
diff --git a/tests.c b/tests.c
index b3d2f49..5f05e6b 100644 (file)
--- 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");