Add 'glob' command for guestfish.
authorRichard Jones <rjones@trick.home.annexia.org>
Mon, 22 Jun 2009 08:35:43 +0000 (09:35 +0100)
committerRichard Jones <rjones@trick.home.annexia.org>
Mon, 22 Jun 2009 08:35:43 +0000 (09:35 +0100)
fish/Makefile.am
fish/fish.c
fish/fish.h
fish/glob.c [new file with mode: 0644]
guestfish.pod

index c3fbc6f..1e5b108 100644 (file)
@@ -26,6 +26,7 @@ guestfish_SOURCES = \
        edit.c \
        fish.c \
        fish.h \
+       glob.c \
        lcd.c
 
 guestfish_CFLAGS = \
index e66880f..bf82b8a 100644 (file)
@@ -56,7 +56,6 @@ static void interactive (void);
 static void shell_script (void);
 static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
-static int issue_command (const char *cmd, char *argv[]);
 static void initialize_readline (void);
 static void cleanup_readline (void);
 static void add_history_line (const char *);
@@ -569,7 +568,7 @@ cmdline (char *argv[], int optind, int argc)
   }
 }
 
-static int
+int
 issue_command (const char *cmd, char *argv[])
 {
   int argc;
@@ -601,6 +600,8 @@ issue_command (const char *cmd, char *argv[])
     return do_edit (cmd, argc, argv);
   else if (strcasecmp (cmd, "lcd") == 0)
     return do_lcd (cmd, argc, argv);
+  else if (strcasecmp (cmd, "glob") == 0)
+    return do_glob (cmd, argc, argv);
   else
     return run_action (cmd, argc, argv);
 }
@@ -622,6 +623,8 @@ list_builtin_commands (void)
          "edit", _("edit a file in the image"));
   printf ("%-20s %s\n",
          "lcd", _("local change directory"));
+  printf ("%-20s %s\n",
+         "glob", _("expand wildcards in command"));
 
   /* actions are printed after this (see list_commands) */
 }
@@ -676,6 +679,13 @@ display_builtin_command (const char *cmd)
              "    Change guestfish's current directory. This command is\n"
              "    useful if you want to download files to a particular\n"
              "    place.\n"));
+  else if (strcasecmp (cmd, "glob") == 0)
+    printf (_("glob - expand wildcards in command\n"
+             "    glob <command> [<args> ...]\n"
+             "\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 (strcasecmp (cmd, "help") == 0)
     printf (_("help - display a list of commands or help on a command\n"
              "     help cmd\n"
index 028deec..8815807 100644 (file)
@@ -34,6 +34,7 @@
 extern guestfs_h *g;
 extern int quit;
 extern int verbose;
+extern int issue_command (const char *cmd, char *argv[]);
 extern void pod2text (const char *heading, const char *body);
 extern void list_builtin_commands (void);
 extern void display_builtin_command (const char *cmd);
@@ -69,6 +70,9 @@ extern int do_edit (const char *cmd, int argc, char *argv[]);
 /* in lcd.c */
 extern int do_lcd (const char *cmd, int argc, char *argv[]);
 
+/* in glob.c */
+extern int do_glob (const char *cmd, int argc, char *argv[]);
+
 /* This should just list all the built-in commands so they can
  * be added to the generated auto-completion code.
  */
@@ -77,7 +81,8 @@ extern int do_lcd (const char *cmd, int argc, char *argv[]);
   "quit", "exit", "q", \
   "alloc", "allocate", \
   "echo", \
-  "edit", "vi", "emacs" \
-  "lcd"
+  "edit", "vi", "emacs", \
+  "lcd", \
+  "glob"
 
 #endif /* FISH_H */
diff --git a/fish/glob.c b/fish/glob.c
new file mode 100644 (file)
index 0000000..827e062
--- /dev/null
@@ -0,0 +1,163 @@
+/* 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 "fish.h"
+
+/* A bit tricky because in the case where there are multiple
+ * paths we have to perform a Cartesian product.
+ */
+static void glob_issue (char *cmd, int argc, char ***globs, int *posn, int *count, int *r);
+
+int
+do_glob (const char *cmd, int argc, char *argv[])
+{
+  /* For 'glob cmd foo /s* /usr/s*' this could be:
+   *
+   * (globs[0]) globs[1]  globs[1]  globs[2]
+   * (cmd)      foo       /sbin     /usr/sbin
+   *                      /srv      /usr/share
+   *                      /sys      /usr/src
+   *
+   * and then we call every combination (ie. 1x3x3) of
+   * argv[1-].
+   */
+  char **globs[argc];
+  int posn[argc];
+  int count[argc];
+  int i, r = 0;
+
+  if (argc < 1) {
+    fprintf (stderr, _("use 'glob command [args...]'\n"));
+    return -1;
+  }
+
+  /* This array will record the current execution position
+   * in the Cartesian product.
+   * NB. globs[0], posn[0], count[0] are ignored.
+   */
+  for (i = 1; i < argc; ++i)
+    posn[i] = 0;
+  for (i = 1; i < argc; ++i)
+    globs[i] = NULL;
+
+  for (i = 1; i < argc; ++i) {
+    char **pp;
+
+    /* Only if it begins with '/' can it possibly be a globbable path. */
+    if (argv[i][0] == '/') {
+      pp = guestfs_glob_expand (g, argv[i]);
+      if (pp == NULL) {                /* real error in glob_expand */
+       fprintf (stderr, _("glob: guestfs_glob_expand call failed: %s\n"),
+                argv[i]);
+       goto error0;
+      }
+
+      /* If there were no matches, then we add a single element list
+       * containing just the original argv[i] string.
+       */
+      if (pp[0] == NULL) {
+       char **pp2;
+
+       pp2 = realloc (pp, sizeof (char *) * 2);
+       if (pp2 == NULL) {
+         perror ("realloc");
+         free (pp);
+         goto error0;
+       }
+       pp = pp2;
+
+       pp[0] = strdup (argv[i]);
+       if (pp[0] == NULL) {
+         perror ("strdup");
+         free (pp);
+         goto error0;
+       }
+       pp[1] = NULL;
+      }
+    }
+    /* Doesn't begin with '/' */
+    else {
+      pp = malloc (sizeof (char *) * 2);
+      if (pp == NULL) {
+       perror ("malloc");
+       goto error0;
+      }
+      pp[0] = strdup (argv[i]);
+      if (pp[0] == NULL) {
+       perror ("strdup");
+       free (pp);
+       goto error0;
+      }
+      pp[1] = NULL;
+    }
+
+    globs[i] = pp;
+    count[i] = count_strings (pp);
+  }
+
+  /* Issue the commands. */
+  glob_issue (argv[0], argc, globs, posn, count, &r);
+
+  /* Free resources. */
+ error0:
+  for (i = 1; i < argc; ++i)
+    if (globs[i])
+      free_strings (globs[i]);
+  return r;
+}
+
+static void
+glob_issue (char *cmd, int argc,
+           char ***globs, int *posn, int *count,
+           int *r)
+{
+  int i;
+  char *argv[argc+1];
+
+  argv[0] = cmd;
+  argv[argc] = NULL;
+
+ again:
+  printf ("%s", argv[0]);
+  for (i = 1; i < argc; ++i) {
+    argv[i] = globs[i][posn[i]];
+    printf (" %s", argv[i]);
+  }
+  printf ("\n");
+
+  if (issue_command (argv[0], &argv[1]) == -1)
+    r = -1;                    /* ... but don't exit */
+
+  for (i = argc-1; i >= 1; --i) {
+    posn[i]++;
+    if (posn[i] < count[i])
+      break;
+    posn[i] = 0;
+  }
+  if (i == 0)                  /* All done. */
+    return;
+
+  goto again;
+}
index 19c9d43..d83a61c 100644 (file)
@@ -184,6 +184,33 @@ a space-separated list, enclosed in quotes.  For example:
 
  vgcreate VG "/dev/sda1 /dev/sdb1"
 
+=head1 WILDCARDS AND GLOBBING
+
+Neither guestfish nor the underlying guestfs API performs
+wildcard expansion (globbing) by default.  So for example the
+following will not do what you expect:
+
+ rm-rf /home/*
+
+Assuming you don't have a directory literally called C</home/*>
+then the above command will return an error.
+
+To perform wildcard expansion, use the C<glob> command.
+
+ glob rm-rf /home/*
+
+runs C<rm-rf> on each path that matches (ie. potentially running
+the command many times), equivalent to:
+
+ rm-rf /home/jim
+ rm-rf /home/joe
+ rm-rf /home/mary
+
+C<glob> only works on simple guest paths and not on device names.
+
+If you have several parameters, each containing a wildcard, then glob
+will perform a cartesian product.
+
 =head1 COMMENTS
 
 Any line which starts with a I<#> character is treated as a comment
@@ -294,6 +321,15 @@ itself.
 
 Note that C<!cd> won't do what you might expect.
 
+=head2 glob
+
+ glob command args...
+
+Expand wildcards in any paths in the args list, and run C<command>
+repeatedly on each matching path.
+
+See section WILDCARDS AND GLOBBING.
+
 @ACTIONS@
 
 =head1 ENVIRONMENT VARIABLES