Quoting in guestfish.
[libguestfs.git] / fish / fish.c
index 5c89335..b825626 100644 (file)
@@ -18,6 +18,8 @@
 
 #include <config.h>
 
+#define _GNU_SOURCE // for strchrnul
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <inttypes.h>
+#include <assert.h>
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
 
 #include <guestfs.h>
 
@@ -43,6 +51,9 @@ static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
 static int issue_command (const char *cmd, char *argv[]);
 static int parse_size (const char *str, off_t *size_rtn);
+static void initialize_readline (void);
+static void cleanup_readline (void);
+static void add_history_line (const char *);
 
 /* Currently open libguestfs handle. */
 guestfs_h *g;
@@ -52,8 +63,10 @@ int quit = 0;
 int verbose = 0;
 
 int
-launch (void)
+launch (guestfs_h *_g)
 {
+  assert (_g == g);
+
   if (!g_launched) {
     if (guestfs_launch (g) == -1)
       return -1;
@@ -109,6 +122,8 @@ main (int argc, char *argv[])
   char *p;
   int c;
 
+  initialize_readline ();
+
   /* guestfs_create is meant to be a lightweight operation, so
    * it's OK to do it early here.
    */
@@ -165,7 +180,7 @@ main (int argc, char *argv[])
        mp->mountpoint = "/";
       mp->device = optarg;
       mp->next = mps;
-      mps = mp->next;
+      mps = mp;
       break;
 
     case 'n':
@@ -189,7 +204,7 @@ main (int argc, char *argv[])
 
   /* If we've got mountpoints, we must launch the guest and mount them. */
   if (mps != NULL) {
-    if (launch () == -1) exit (1);
+    if (launch (g) == -1) exit (1);
     mount_mps (mps);
   }
 
@@ -203,6 +218,8 @@ main (int argc, char *argv[])
   else
     cmdline (argv, optind, argc);
 
+  cleanup_readline ();
+
   exit (0);
 }
 
@@ -249,13 +266,51 @@ shell_script (void)
   script (0);
 }
 
+#define FISH "><fs> "
+
+static char *line_read = NULL;
+
+static char *
+rl_gets (int prompt)
+{
+#ifdef HAVE_LIBREADLINE
+
+  if (line_read) {
+    free (line_read);
+    line_read = NULL;
+  }
+
+  line_read = readline (prompt ? FISH : "");
+
+  if (prompt && line_read && *line_read)
+    add_history_line (line_read);
+
+#else /* !HAVE_LIBREADLINE */
+
+  static char buf[8192];
+  int len;
+
+  if (prompt) printf (FISH);
+  line_read = fgets (buf, sizeof buf, stdin);
+
+  if (line_read) {
+    len = strlen (line_read);
+    if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
+  }
+
+#endif /* !HAVE_LIBREADLINE */
+
+  return line_read;
+}
+
 static void
 script (int prompt)
 {
-  char buf[8192];
+  char *buf;
   char *cmd;
+  char *p, *pend;
   char *argv[64];
-  int len, i;
+  int i, len;
 
   if (prompt)
     printf ("\n"
@@ -267,32 +322,124 @@ script (int prompt)
            "\n");
 
   while (!quit) {
-    if (prompt) printf ("><fs> ");
-    if (fgets (buf, sizeof buf, stdin) == NULL) {
+    buf = rl_gets (prompt);
+    if (!buf) {
       quit = 1;
       break;
     }
 
-    len = strlen (buf);
-    if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
+    /* Skip any initial whitespace before the command. */
+    while (*buf && isspace (*buf))
+      buf++;
 
-    /* Split the buffer up at whitespace. */
-    cmd = strtok (buf, " \t");
-    if (cmd == NULL)
-      continue;
+    /* Get the command (cannot be quoted). */
+    len = strcspn (buf, " \t");
 
+    if (len == 0) continue;
+
+    cmd = buf;
     i = 0;
-    while (i < sizeof argv / sizeof argv[0] &&
-          (argv[i] = strtok (NULL, " \t")) != NULL)
-      i++;
+    if (buf[len] == '\0') {
+      argv[0] = NULL;
+      goto got_command;
+    }
+
+    buf[len] = '\0';
+    p = &buf[len+1];
+    p += strspn (p, " \t");
+
+    /* Get the parameters. */
+    while (*p && i < sizeof argv / sizeof argv[0]) {
+      /* Parameters which start with quotes or square brackets
+       * are treated specially.  Bare parameters are delimited
+       * by whitespace.
+       */
+      if (*p == '"') {
+       p++;
+       len = strcspn (p, "\"");
+       if (p[len] == '\0') {
+         fprintf (stderr, "guestfish: unterminated double quote\n");
+         if (!prompt) exit (1);
+         goto next_command;
+       }
+       if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
+         fprintf (stderr, "guestfish: command arguments not separated by whitespace\n");
+         if (!prompt) exit (1);
+         goto next_command;
+       }
+       p[len] = '\0';
+       pend = &p[len+2];
+      } else if (*p == '\'') {
+       p++;
+       len = strcspn (p, "'");
+       if (p[len] == '\0') {
+         fprintf (stderr, "guestfish: unterminated single quote\n");
+         if (!prompt) exit (1);
+         goto next_command;
+       }
+       if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
+         fprintf (stderr, "guestfish: command arguments not separated by whitespace\n");
+         if (!prompt) exit (1);
+         goto next_command;
+       }
+       p[len] = '\0';
+       pend = &p[len+2];
+       /*
+      } else if (*p == '[') {
+       int c = 1;
+       p++;
+       pend = p;
+       while (*pend && c != 0) {
+         if (*pend == '[') c++;
+         else if (*pend == ']') c--;
+         pend++;
+       }
+       if (c != 0) {
+         fprintf (stderr, "guestfish: unterminated \"[...]\" sequence\n");
+         if (!prompt) exit (1);
+         goto next_command;
+       }
+       if (*pend && (*pend != ' ' && *pend != '\t')) {
+         fprintf (stderr, "guestfish: command arguments not separated by whitespace\n");
+         if (!prompt) exit (1);
+         goto next_command;
+       }
+       *(pend-1) = '\0';
+       */
+      } else if (*p != ' ' && *p != '\t') {
+       len = strcspn (p, " \t");
+       if (p[len]) {
+         p[len] = '\0';
+         pend = &p[len+1];
+       } else
+         pend = &p[len];
+      } else {
+       fprintf (stderr, "guestfish: internal error parsing string at '%s'\n",
+                p);
+       abort ();
+      }
+
+      argv[i++] = p;
+      p = pend;
+
+      if (*p)
+       p += strspn (p, " \t");
+    }
+
     if (i == sizeof argv / sizeof argv[0]) {
-      fprintf (stderr, "guestfish: too many arguments in command\n");
-      exit (1);
+      fprintf (stderr, "guestfish: too many arguments\n");
+      if (!prompt) exit (1);
+      goto next_command;
     }
 
+    argv[i] = NULL;
+
+  got_command:
     if (issue_command (cmd, argv) == -1) {
       if (!prompt) exit (1);
     }
+
+  next_command:;
   }
   if (prompt) printf ("\n");
 }
@@ -346,27 +493,6 @@ issue_command (const char *cmd, char *argv[])
     quit = 1;
     return 0;
   }
-  else if (strcasecmp (cmd, "add") == 0 ||
-          strcasecmp (cmd, "drive") == 0 ||
-          strcasecmp (cmd, "add-drive") == 0 ||
-          strcasecmp (cmd, "add_drive") == 0) {
-    if (argc != 1) {
-      fprintf (stderr, "use 'add image' to add a guest image\n");
-      return -1;
-    }
-    else
-      return guestfs_add_drive (g, argv[0]);
-  }
-  else if (strcasecmp (cmd, "cdrom") == 0 ||
-          strcasecmp (cmd, "add-cdrom") == 0 ||
-          strcasecmp (cmd, "add_cdrom") == 0) {
-    if (argc != 1) {
-      fprintf (stderr, "use 'cdrom image' to add a CD-ROM image\n");
-      return -1;
-    }
-    else
-      return guestfs_add_cdrom (g, argv[0]);
-  }
   else if (strcasecmp (cmd, "alloc") == 0 ||
           strcasecmp (cmd, "allocate") == 0) {
     if (argc != 2) {
@@ -412,15 +538,6 @@ issue_command (const char *cmd, char *argv[])
       return 0;
     }
   }
-  else if (strcasecmp (cmd, "launch") == 0 ||
-          strcasecmp (cmd, "run") == 0) {
-    if (argc != 0) {
-      fprintf (stderr, "'launch' command takes no parameters\n");
-      return -1;
-    }
-    else
-      return launch ();
-  }
   else
     return run_action (cmd, argc, argv);
 }
@@ -434,15 +551,8 @@ list_builtin_commands (void)
   printf ("%-20s %s\n",
          "quit", "quit guestfish");
 
-  /* then the non-actions, in alphabetical order */
-  printf ("%-20s %s\n",
-         "add", "add a guest image to be examined or modified");
   printf ("%-20s %s\n",
          "alloc", "allocate an image");
-  printf ("%-20s %s\n",
-         "cdrom", "add a CD-ROM image to be examined");
-  printf ("%-20s %s\n",
-         "launch", "launch the subprocess");
 
   /* actions are printed after this (see list_commands) */
 }
@@ -452,10 +562,7 @@ display_builtin_command (const char *cmd)
 {
   /* help for actions is auto-generated, see display_command */
 
-  if (strcasecmp (cmd, "add") == 0)
-    printf ("add - add a guest image to be examined or modified\n"
-           "     add <image>\n");
-  else if (strcasecmp (cmd, "alloc") == 0)
+  if (strcasecmp (cmd, "alloc") == 0)
     printf ("alloc - allocate an image\n"
            "     alloc <filename> <size>\n"
            "\n"
@@ -471,9 +578,6 @@ 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, "cdrom") == 0)
-    printf ("cdrom - add a CD-ROM image to be examined\n"
-           "     cdrom <iso-file>\n");
   else if (strcasecmp (cmd, "help") == 0)
     printf ("help - display a list of commands or help on a command\n"
            "     help cmd\n"
@@ -481,9 +585,6 @@ display_builtin_command (const char *cmd)
   else if (strcasecmp (cmd, "quit") == 0)
     printf ("quit - quit guestfish\n"
            "     quit\n");
-  else if (strcasecmp (cmd, "launch") == 0)
-    printf ("launch - launch the subprocess\n"
-           "     launch\n");
   else
     fprintf (stderr, "%s: command not known, use -h to list all commands\n",
             cmd);
@@ -517,8 +618,6 @@ parse_size (const char *str, off_t *size_rtn)
     return -1;
   }
 
-  printf ("size = %lu\n", size);
-
   /* XXX 32 bit file offsets, if anyone uses them?  GCC should give
    * a warning here anyhow.
    */
@@ -526,3 +625,112 @@ parse_size (const char *str, off_t *size_rtn)
 
   return 0;
 }
+
+void
+free_strings (char **argv)
+{
+  int argc;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    free (argv[argc]);
+  free (argv);
+}
+
+void
+print_strings (char * const * const argv)
+{
+  int argc;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    printf ("%s\n", argv[argc]);
+}
+
+int
+is_true (const char *str)
+{
+  return
+    strcasecmp (str, "0") != 0 &&
+    strcasecmp (str, "f") != 0 &&
+    strcasecmp (str, "false") != 0 &&
+    strcasecmp (str, "n") != 0 &&
+    strcasecmp (str, "no") != 0;
+}
+
+/* XXX We could improve list parsing. */
+char **
+parse_string_list (const char *str)
+{
+  char **argv;
+  const char *p, *pend;
+  int argc, i;
+
+  argc = 1;
+  for (i = 0; str[i]; ++i)
+    if (str[i] == ' ') argc++;
+
+  argv = malloc (sizeof (char *) * (argc+1));
+  if (argv == NULL) { perror ("malloc"); exit (1); }
+
+  p = str;
+  i = 0;
+  while (*p) {
+    pend = strchrnul (p, ' ');
+    argv[i] = strndup (p, pend-p);
+    i++;
+    p = *pend == ' ' ? pend+1 : pend;
+  }
+  argv[i] = NULL;
+
+  return argv;
+}
+
+#ifdef HAVE_LIBREADLINE
+static char histfile[1024];
+static int nr_history_lines = 0;
+#endif
+
+static void
+initialize_readline (void)
+{
+#ifdef HAVE_LIBREADLINE
+  const char *home;
+
+  home = getenv ("HOME");
+  if (home) {
+    snprintf (histfile, sizeof histfile, "%s/.guestfish", home);
+    using_history ();
+    (void) read_history (histfile);
+  }
+
+  rl_readline_name = "guestfish";
+  rl_attempted_completion_function = do_completion;
+#endif
+}
+
+static void
+cleanup_readline (void)
+{
+#ifdef HAVE_LIBREADLINE
+  int fd;
+
+  if (histfile[0] != '\0') {
+    fd = open (histfile, O_WRONLY|O_CREAT, 0644);
+    if (fd == -1) {
+      perror (histfile);
+      return;
+    }
+    close (fd);
+
+    (void) append_history (nr_history_lines, histfile);
+  }
+#endif
+}
+
+static void
+add_history_line (const char *line)
+{
+#ifdef HAVE_LIBREADLINE
+  add_history (line);
+  nr_history_lines++;
+#endif
+}