fish: Allow suffixes on number parameters (eg. 1M)
[libguestfs.git] / fish / fish.c
index db3149e..d38d1c1 100644 (file)
@@ -1,5 +1,5 @@
 /* guestfish - the filesystem interactive shell
 /* guestfish - the filesystem interactive shell
- * Copyright (C) 2009 Red Hat Inc.
+ * Copyright (C) 2009-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
  *
  * 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
 #include "closeout.h"
 #include "progname.h"
 
 #include "closeout.h"
 #include "progname.h"
 
+struct drv {
+  struct drv *next;
+  char *filename;               /* disk filename (for -a or -N options) */
+  prep_data *data;              /* prepared type (for -N option only) */
+  char *device;                 /* device inside the appliance */
+};
+
 struct mp {
   struct mp *next;
   char *device;
   char *mountpoint;
 };
 
 struct mp {
   struct mp *next;
   char *device;
   char *mountpoint;
 };
 
-struct drv {
-  struct drv *next;
-  char *filename;
-};
-
 static void add_drives (struct drv *drv);
 static void add_drives (struct drv *drv);
+static void prepare_drives (struct drv *drv);
 static void mount_mps (struct mp *mp);
 static void mount_mps (struct mp *mp);
+static int launch (void);
 static void interactive (void);
 static void shell_script (void);
 static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
 static void initialize_readline (void);
 static void cleanup_readline (void);
 static void interactive (void);
 static void shell_script (void);
 static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
 static void initialize_readline (void);
 static void cleanup_readline (void);
+#ifdef HAVE_LIBREADLINE
 static void add_history_line (const char *);
 static void add_history_line (const char *);
+#endif
+static void print_shell_quote (FILE *stream, const char *str);
 
 /* Currently open libguestfs handle. */
 guestfs_h *g;
 
 /* Currently open libguestfs handle. */
 guestfs_h *g;
@@ -69,24 +76,11 @@ guestfs_h *g;
 int read_only = 0;
 int quit = 0;
 int verbose = 0;
 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 command_num = 0;
 
 int remote_control_listen = 0;
 int remote_control = 0;
 int exit_on_error = 1;
 int command_num = 0;
 
-int
-launch (guestfs_h *_g)
-{
-  assert (_g == g);
-
-  if (guestfs_is_config (g)) {
-    if (guestfs_launch (g) == -1)
-      return -1;
-  }
-  return 0;
-}
-
 static void __attribute__((noreturn))
 usage (int status)
 {
 static void __attribute__((noreturn))
 usage (int status)
 {
@@ -119,6 +113,7 @@ usage (int status)
              "  --listen             Listen for remote commands\n"
              "  -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n"
              "  -n|--no-sync         Don't autosync\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"
+             "  -N|--new type        Create prepared disk (test1.img, ...)\n"
              "  --remote[=pid]       Send commands to remote %s\n"
              "  -r|--ro              Mount read-only\n"
              "  --selinux            Enable SELinux support\n"
              "  --remote[=pid]       Send commands to remote %s\n"
              "  -r|--ro              Mount read-only\n"
              "  --selinux            Enable SELinux support\n"
@@ -147,7 +142,7 @@ main (int argc, char *argv[])
 
   enum { HELP_OPTION = CHAR_MAX + 1 };
 
 
   enum { HELP_OPTION = CHAR_MAX + 1 };
 
-  static const char *options = "a:Df:h::im:nrv?Vx";
+  static const char *options = "a:Df:h::im:nN:rv?Vx";
   static const struct option long_options[] = {
     { "add", 1, 0, 'a' },
     { "cmd-help", 2, 0, 'h' },
   static const struct option long_options[] = {
     { "add", 1, 0, 'a' },
     { "cmd-help", 2, 0, 'h' },
@@ -156,6 +151,7 @@ main (int argc, char *argv[])
     { "inspector", 0, 0, 'i' },
     { "listen", 0, 0, 0 },
     { "mount", 1, 0, 'm' },
     { "inspector", 0, 0, 'i' },
     { "listen", 0, 0, 0 },
     { "mount", 1, 0, 'm' },
+    { "new", 1, 0, 'N' },
     { "no-dest-paths", 0, 0, 'D' },
     { "no-sync", 0, 0, 'n' },
     { "remote", 2, 0, 0 },
     { "no-dest-paths", 0, 0, 'D' },
     { "no-sync", 0, 0, 'n' },
     { "remote", 2, 0, 0 },
@@ -174,6 +170,8 @@ main (int argc, char *argv[])
   int inspector = 0;
   int option_index;
   struct sigaction sa;
   int inspector = 0;
   int option_index;
   struct sigaction sa;
+  char next_drive = 'a';
+  int next_prepared_drive = 1;
 
   initialize_readline ();
 
 
   initialize_readline ();
 
@@ -259,6 +257,36 @@ main (int argc, char *argv[])
         exit (EXIT_FAILURE);
       }
       drv->filename = optarg;
         exit (EXIT_FAILURE);
       }
       drv->filename = optarg;
+      drv->data = NULL;
+      /* We could fill the device field in, but in fact we
+       * only use it for the -N option at present.
+       */
+      drv->device = NULL;
+      drv->next = drvs;
+      drvs = drv;
+      next_drive++;
+      break;
+
+    case 'N':
+      if (STRCASEEQ (optarg, "list")) {
+        list_prepared_drives ();
+        exit (EXIT_SUCCESS);
+      }
+      drv = malloc (sizeof (struct drv));
+      if (!drv) {
+        perror ("malloc");
+        exit (EXIT_FAILURE);
+      }
+      if (asprintf (&drv->filename, "test%d.img",
+                    next_prepared_drive++) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
+      drv->data = create_prepared_file (optarg, drv->filename);
+      if (asprintf (&drv->device, "/dev/sd%c", next_drive++) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
       drv->next = drvs;
       drvs = drv;
       break;
       drv->next = drvs;
       drvs = drv;
       break;
@@ -324,7 +352,7 @@ main (int argc, char *argv[])
       exit (EXIT_SUCCESS);
 
     case 'x':
       exit (EXIT_SUCCESS);
 
     case 'x':
-      echo_commands = 1;
+      guestfs_set_trace (g, 1);
       break;
 
     case HELP_OPTION:
       break;
 
     case HELP_OPTION:
@@ -337,13 +365,10 @@ main (int argc, char *argv[])
 
   /* Inspector mode invalidates most of the other arguments. */
   if (inspector) {
 
   /* Inspector mode invalidates most of the other arguments. */
   if (inspector) {
-    char cmd[1024];
-    int r;
-
     if (drvs || mps || remote_control_listen || remote_control ||
         guestfs_get_selinux (g)) {
     if (drvs || mps || remote_control_listen || remote_control ||
         guestfs_get_selinux (g)) {
-      fprintf (stderr, _("%s: cannot use -i option with -a, -m,"
-                         " --listen, --remote or --selinux\n"),
+      fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, "
+                         "--listen, --remote or --selinux\n"),
                program_name);
       exit (EXIT_FAILURE);
     }
                program_name);
       exit (EXIT_FAILURE);
     }
@@ -354,51 +379,101 @@ main (int argc, char *argv[])
       exit (EXIT_FAILURE);
     }
 
       exit (EXIT_FAILURE);
     }
 
-    strcpy (cmd, "a=`virt-inspector");
+    char *cmd;
+    size_t cmdlen;
+    FILE *fp = open_memstream (&cmd, &cmdlen);
+    if (fp == NULL) {
+      perror ("open_memstream");
+      exit (EXIT_FAILURE);
+    }
+
+    fprintf (fp, "virt-inspector");
     while (optind < argc) {
     while (optind < argc) {
-      if (strlen (cmd) + strlen (argv[optind]) + strlen (real_argv0) + 60
-          >= sizeof cmd) {
-        fprintf (stderr,
-                 _("%s: virt-inspector command too long for fixed-size buffer\n"),
-                 program_name);
-        exit (EXIT_FAILURE);
-      }
-      strcat (cmd, " '");
-      strcat (cmd, argv[optind]);
-      strcat (cmd, "'");
+      fputc (' ', fp);
+      print_shell_quote (fp, argv[optind]);
       optind++;
     }
 
     if (read_only)
       optind++;
     }
 
     if (read_only)
-      strcat (cmd, " --ro-fish");
+      fprintf (fp, " --ro-fish");
     else
     else
-      strcat (cmd, " --fish");
+      fprintf (fp, " --fish");
 
 
-    sprintf (&cmd[strlen(cmd)], "` && %s $a", real_argv0);
+    if (fclose (fp) == -1) {
+      perror ("fclose");
+      exit (EXIT_FAILURE);
+    }
+
+    if (verbose)
+      fprintf (stderr,
+               "%s -i: running: %s\n", program_name, cmd);
+
+    FILE *pp = popen (cmd, "r");
+    if (pp == NULL) {
+      perror (cmd);
+      exit (EXIT_FAILURE);
+    }
+
+    char *cmd2;
+    fp = open_memstream (&cmd2, &cmdlen);
+    if (fp == NULL) {
+      perror ("open_memstream");
+      exit (EXIT_FAILURE);
+    }
+
+    fprintf (fp, "%s", real_argv0);
 
     if (guestfs_get_verbose (g))
 
     if (guestfs_get_verbose (g))
-      strcat (cmd, " -v");
+      fprintf (fp, " -v");
     if (!guestfs_get_autosync (g))
     if (!guestfs_get_autosync (g))
-      strcat (cmd, " -n");
+      fprintf (fp, " -n");
+    if (guestfs_get_trace (g))
+      fprintf (fp, " -x");
+
+    char *insp = NULL;
+    size_t insplen;
+    if (getline (&insp, &insplen, pp) == -1) {
+      perror (cmd);
+      exit (EXIT_FAILURE);
+    }
+    fprintf (fp, " %s", insp);
+
+    if (pclose (pp) == -1) {
+      perror (cmd);
+      exit (EXIT_FAILURE);
+    }
+
+    if (fclose (fp) == -1) {
+      perror ("fclose");
+      exit (EXIT_FAILURE);
+    }
 
     if (verbose)
       fprintf (stderr,
 
     if (verbose)
       fprintf (stderr,
-               "%s -i: running virt-inspector command:\n%s\n", program_name, cmd);
+               "%s -i: running: %s\n", program_name, cmd2);
 
 
-    r = system (cmd);
+    int r = system (cmd2);
     if (r == -1) {
     if (r == -1) {
-      perror ("system");
+      perror (cmd2);
       exit (EXIT_FAILURE);
     }
       exit (EXIT_FAILURE);
     }
+
+    free (cmd);
+    free (cmd2);
+    free (insp);
+
     exit (WEXITSTATUS (r));
   }
 
   /* If we've got drives to add, add them now. */
   add_drives (drvs);
 
     exit (WEXITSTATUS (r));
   }
 
   /* If we've got drives to add, add them now. */
   add_drives (drvs);
 
-  /* If we've got mountpoints, we must launch the guest and mount them. */
-  if (mps != NULL) {
-    if (launch (g) == -1) exit (EXIT_FAILURE);
+  /* If we've got mountpoints or prepared drives, we must launch the
+   * guest and mount them.
+   */
+  if (next_prepared_drive > 1 || mps != NULL) {
+    if (launch () == -1) exit (EXIT_FAILURE);
+    prepare_drives (drvs);
     mount_mps (mps);
   }
 
     mount_mps (mps);
   }
 
@@ -495,7 +570,8 @@ add_drives (struct drv *drv)
 
   if (drv) {
     add_drives (drv->next);
 
   if (drv) {
     add_drives (drv->next);
-    if (!read_only)
+
+    if (drv->data /* -N option is not affected by --ro */ || !read_only)
       r = guestfs_add_drive (g, drv->filename);
     else
       r = guestfs_add_drive_ro (g, drv->filename);
       r = guestfs_add_drive (g, drv->filename);
     else
       r = guestfs_add_drive_ro (g, drv->filename);
@@ -505,6 +581,26 @@ add_drives (struct drv *drv)
 }
 
 static void
 }
 
 static void
+prepare_drives (struct drv *drv)
+{
+  if (drv) {
+    prepare_drives (drv->next);
+    if (drv->data)
+      prepare_drive (drv->filename, drv->data, drv->device);
+  }
+}
+
+static int
+launch (void)
+{
+  if (guestfs_is_config (g)) {
+    if (guestfs_launch (g) == -1)
+      return -1;
+  }
+  return 0;
+}
+
+static void
 interactive (void)
 {
   script (1);
 interactive (void)
 {
   script (1);
@@ -571,7 +667,8 @@ script (int prompt)
               "Welcome to guestfish, the libguestfs filesystem interactive shell for\n"
               "editing virtual machine filesystems.\n"
               "\n"
               "Welcome to guestfish, the libguestfs filesystem interactive shell for\n"
               "editing virtual machine filesystems.\n"
               "\n"
-              "Type: 'help' for help with commands\n"
+              "Type: 'help' for a list of commands\n"
+              "      'man' to read the manual\n"
               "      'quit' to quit the shell\n"
               "\n"));
 
               "      'quit' to quit the shell\n"
               "\n"));
 
@@ -805,13 +902,6 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd)
   /* This counts the commands issued, starting at 1. */
   command_num++;
 
   /* This counts the commands issued, starting at 1. */
   command_num++;
 
-  if (echo_commands) {
-    printf ("%s", cmd);
-    for (i = 0; argv[i] != NULL; ++i)
-      printf (" %s", argv[i]);
-    printf ("\n");
-  }
-
   /* For | ... commands.  Annoyingly we can't use popen(3) here. */
   if (pipecmd) {
     int fd[2];
   /* For | ... commands.  Annoyingly we can't use popen(3) here. */
   if (pipecmd) {
     int fd[2];
@@ -892,6 +982,9 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd)
     r = do_lcd (cmd, argc, argv);
   else if (STRCASEEQ (cmd, "glob"))
     r = do_glob (cmd, argc, argv);
     r = do_lcd (cmd, argc, argv);
   else if (STRCASEEQ (cmd, "glob"))
     r = do_glob (cmd, argc, argv);
+  else if (STRCASEEQ (cmd, "man") ||
+           STRCASEEQ (cmd, "manual"))
+    r = do_man (cmd, argc, argv);
   else if (STRCASEEQ (cmd, "more") ||
            STRCASEEQ (cmd, "less"))
     r = do_more (cmd, argc, argv);
   else if (STRCASEEQ (cmd, "more") ||
            STRCASEEQ (cmd, "less"))
     r = do_more (cmd, argc, argv);
@@ -931,10 +1024,12 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd)
 void
 list_builtin_commands (void)
 {
 void
 list_builtin_commands (void)
 {
-  /* help and quit should appear at the top */
+  /* help, man and quit should appear at the top */
   printf ("%-20s %s\n",
           "help", _("display a list of commands or help on a command"));
   printf ("%-20s %s\n",
   printf ("%-20s %s\n",
           "help", _("display a list of commands or help on a command"));
   printf ("%-20s %s\n",
+          "man", _("read the manual"));
+  printf ("%-20s %s\n",
           "quit", _("quit guestfish"));
 
   printf ("%-20s %s\n",
           "quit", _("quit guestfish"));
 
   printf ("%-20s %s\n",
@@ -974,16 +1069,8 @@ display_builtin_command (const char *cmd)
               "\n"
               "    For more advanced image creation, see qemu-img utility.\n"
               "\n"
               "\n"
               "    For more advanced image creation, see qemu-img utility.\n"
               "\n"
-              "    Size can be specified (where <nn> means a number):\n"
-              "    <nn>             number of kilobytes\n"
-              "      eg: 1440       standard 3.5\" floppy\n"
-              "    <nn>K or <nn>KB  number of kilobytes\n"
-              "    <nn>M or <nn>MB  number of megabytes\n"
-              "    <nn>G or <nn>GB  number of gigabytes\n"
-              "    <nn>T or <nn>TB  number of terabytes\n"
-              "    <nn>P or <nn>PB  number of petabytes\n"
-              "    <nn>E or <nn>EB  number of exabytes\n"
-              "    <nn>sects        number of 512 byte sectors\n"));
+              "    Size can be specified using standard suffixes, eg. '1M'.\n"
+              ));
   else if (STRCASEEQ (cmd, "echo"))
     printf (_("echo - display a line of text\n"
               "     echo [<params> ...]\n"
   else if (STRCASEEQ (cmd, "echo"))
     printf (_("echo - display a line of text\n"
               "     echo [<params> ...]\n"
@@ -998,7 +1085,7 @@ display_builtin_command (const char *cmd)
               "    This is used to edit a file.\n"
               "\n"
               "    It is the equivalent of (and is implemented by)\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"
+              "    running \"cat\", editing locally, and then \"write\".\n"
               "\n"
               "    Normally it uses $EDITOR, but if you use the aliases\n"
               "    \"vi\" or \"emacs\" you will get those editors.\n"
               "\n"
               "    Normally it uses $EDITOR, but if you use the aliases\n"
               "    \"vi\" or \"emacs\" you will get those editors.\n"
@@ -1019,6 +1106,12 @@ display_builtin_command (const char *cmd)
               "    Glob runs <command> with wildcards expanded in any\n"
               "    command args.  Note that the command is run repeatedly\n"
               "    once for each expanded argument.\n"));
               "    Glob runs <command> with wildcards expanded in any\n"
               "    command args.  Note that the command is run repeatedly\n"
               "    once for each expanded argument.\n"));
+  else if (STRCASEEQ (cmd, "man") ||
+           STRCASEEQ (cmd, "manual"))
+    printf (_("man - read the manual\n"
+              "    man\n"
+              "\n"
+              "    Opens the manual page for guestfish.\n"));
   else if (STRCASEEQ (cmd, "help"))
     printf (_("help - display a list of commands or help on a command\n"
               "     help cmd\n"
   else if (STRCASEEQ (cmd, "help"))
     printf (_("help - display a list of commands or help on a command\n"
               "     help cmd\n"
@@ -1067,16 +1160,8 @@ display_builtin_command (const char *cmd)
               "\n"
               "    For more advanced image creation, see qemu-img utility.\n"
               "\n"
               "\n"
               "    For more advanced image creation, see qemu-img utility.\n"
               "\n"
-              "    Size can be specified (where <nn> means a number):\n"
-              "    <nn>             number of kilobytes\n"
-              "      eg: 1440       standard 3.5\" floppy\n"
-              "    <nn>K or <nn>KB  number of kilobytes\n"
-              "    <nn>M or <nn>MB  number of megabytes\n"
-              "    <nn>G or <nn>GB  number of gigabytes\n"
-              "    <nn>T or <nn>TB  number of terabytes\n"
-              "    <nn>P or <nn>PB  number of petabytes\n"
-              "    <nn>E or <nn>EB  number of exabytes\n"
-              "    <nn>sects        number of 512 byte sectors\n"));
+              "    Size can be specified using standard suffixes, eg. '1M'.\n"
+              ));
   else if (STRCASEEQ (cmd, "time"))
     printf (_("time - measure time taken to run command\n"
               "    time <command> [<args> ...]\n"
   else if (STRCASEEQ (cmd, "time"))
     printf (_("time - measure time taken to run command\n"
               "    time <command> [<args> ...]\n"
@@ -1344,14 +1429,14 @@ cleanup_readline (void)
 #endif
 }
 
 #endif
 }
 
+#ifdef HAVE_LIBREADLINE
 static void
 add_history_line (const char *line)
 {
 static void
 add_history_line (const char *line)
 {
-#ifdef HAVE_LIBREADLINE
   add_history (line);
   nr_history_lines++;
   add_history (line);
   nr_history_lines++;
-#endif
 }
 }
+#endif
 
 int
 xwrite (int fd, const void *v_buf, size_t len)
 
 int
 xwrite (int fd, const void *v_buf, size_t len)
@@ -1560,3 +1645,17 @@ file_out (const char *arg)
   }
   return ret;
 }
   }
   return ret;
 }
+
+static void
+print_shell_quote (FILE *stream, const char *str)
+{
+#define SAFE(c) (c_isalnum((c)) ||                                     \
+                 (c) == '/' || (c) == '-' || (c) == '_' || (c) == '.')
+  int i;
+
+  for (i = 0; str[i]; ++i) {
+    if (!SAFE(str[i]))
+      putc ('\\', stream);
+    putc (str[i], stream);
+  }
+}