Implementations of 'cat', 'ls', and some cleanups.
authorRichard Jones <rjones@redhat.com>
Mon, 6 Apr 2009 10:44:48 +0000 (11:44 +0100)
committerRichard Jones <rjones@redhat.com>
Mon, 6 Apr 2009 10:44:48 +0000 (11:44 +0100)
daemon/daemon.h
daemon/file.c
daemon/guestfsd.c
daemon/ls.c
fish/cmds.c
guestfs-actions.pod
src/generator.ml

index 42d7727..3740595 100644 (file)
 /* in guestfsd.c */
 extern void xwrite (int sock, const void *buf, size_t len);
 extern void xread (int sock, void *buf, size_t len);
 /* in guestfsd.c */
 extern void xwrite (int sock, const void *buf, size_t len);
 extern void xread (int sock, void *buf, size_t len);
+
+extern int add_string (char ***argv, int *size, int *alloc, const char *str);
 extern int count_strings (char **argv);
 extern void free_strings (char **argv);
 extern int count_strings (char **argv);
 extern void free_strings (char **argv);
+
 extern int command (char **stdoutput, char **stderror, const char *name, ...);
 
 /* in proto.c */
 extern int command (char **stdoutput, char **stderror, const char *name, ...);
 
 /* in proto.c */
@@ -46,15 +49,23 @@ extern void reply_with_error (const char *fs, ...);
 extern void reply_with_perror (const char *fs, ...);
 extern void reply (xdrproc_t xdrp, char *ret);
 
 extern void reply_with_perror (const char *fs, ...);
 extern void reply (xdrproc_t xdrp, char *ret);
 
-#define NEED_ROOT                                                      \
+#define NEED_ROOT(errcode)                                             \
   do {                                                                 \
     if (!root_mounted) {                                               \
       reply_with_error ("%s: you must call 'mount' first to mount the root filesystem", __func__); \
   do {                                                                 \
     if (!root_mounted) {                                               \
       reply_with_error ("%s: you must call 'mount' first to mount the root filesystem", __func__); \
-      return -1;                                                       \
+      return (errcode);                                                        \
     }                                                                  \
   }                                                                    \
   while (0)
 
     }                                                                  \
   }                                                                    \
   while (0)
 
+#define ABS_PATH(path,errcode)                                         \
+  do {                                                                 \
+    if ((path)[0] != '/') {                                            \
+      reply_with_error ("%s: path must start with a / character", __func__); \
+      return (errcode);                                                        \
+    }                                                                  \
+  } while (0)
+
 /* NB:
  * (1) You must match CHROOT_IN and CHROOT_OUT even along error paths.
  * (2) You must not change directory!  cwd must always be "/", otherwise
 /* NB:
  * (1) You must match CHROOT_IN and CHROOT_OUT even along error paths.
  * (2) You must not change directory!  cwd must always be "/", otherwise
index db19918..537618f 100644 (file)
@@ -27,6 +27,7 @@
 #include <fcntl.h>
 #include <sys/stat.h>
 
 #include <fcntl.h>
 #include <sys/stat.h>
 
+#include "../src/guestfs_protocol.h"
 #include "daemon.h"
 #include "actions.h"
 
 #include "daemon.h"
 #include "actions.h"
 
@@ -35,12 +36,8 @@ do_touch (const char *path)
 {
   int fd;
 
 {
   int fd;
 
-  NEED_ROOT;
-
-  if (path[0] != '/') {
-    reply_with_error ("touch: path must start with a / character");
-    return -1;
-  }
+  NEED_ROOT (-1);
+  ABS_PATH (path, -1);
 
   CHROOT_IN;
   fd = open (path, O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666);
 
   CHROOT_IN;
   fd = open (path, O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666);
@@ -48,7 +45,6 @@ do_touch (const char *path)
 
   if (fd == -1) {
     reply_with_perror ("open: %s", path);
 
   if (fd == -1) {
     reply_with_perror ("open: %s", path);
-    close (fd);
     return -1;
   }
 
     return -1;
   }
 
@@ -65,6 +61,70 @@ do_touch (const char *path)
 char *
 do_cat (const char *path)
 {
 char *
 do_cat (const char *path)
 {
-  reply_with_error ("cat command is not yet implemented");
-  return NULL;
+  int fd;
+  int alloc, size, r, max;
+  char *buf, *buf2;
+
+  NEED_ROOT (NULL);
+  ABS_PATH (path,NULL);
+
+  CHROOT_IN;
+  fd = open (path, O_RDONLY);
+  CHROOT_OUT;
+
+  if (fd == -1) {
+    reply_with_perror ("open: %s", path);
+    return NULL;
+  }
+
+  /* Read up to GUESTFS_MESSAGE_MAX - <overhead> bytes.  If it's
+   * larger than that, we need to return an error instead (for
+   * correctness).
+   */
+  max = GUESTFS_MESSAGE_MAX - 1000;
+  buf = NULL;
+  size = alloc = 0;
+
+  for (;;) {
+    if (size >= alloc) {
+      alloc += 8192;
+      if (alloc > max) {
+       reply_with_error ("cat: %s: file is too large for message buffer",
+                         path);
+       free (buf);
+       close (fd);
+       return NULL;
+      }
+      buf2 = realloc (buf, alloc);
+      if (buf2 == NULL) {
+       reply_with_perror ("realloc");
+       free (buf);
+       close (fd);
+       return NULL;
+      }
+      buf = buf2;
+    }
+
+    r = read (fd, buf + size, alloc - size);
+    if (r == -1) {
+      reply_with_perror ("read: %s", path);
+      free (buf);
+      close (fd);
+      return NULL;
+    }
+    if (r == 0) {
+      buf[size] = '\0';
+      break;
+    }
+    if (r > 0)
+      size += r;
+  }
+
+  if (close (fd) == -1) {
+    reply_with_perror ("close: %s", path);
+    free (buf);
+    return NULL;
+  }
+
+  return buf;                  /* caller will free */
 }
 }
index 9d110d7..6730c1d 100644 (file)
@@ -231,6 +231,38 @@ usage (void)
 }
 
 int
 }
 
 int
+add_string (char ***argv, int *size, int *alloc, const char *str)
+{
+  char **new_argv;
+  char *new_str;
+
+  if (*size >= *alloc) {
+    *alloc += 64;
+    new_argv = realloc (*argv, *alloc * sizeof (char *));
+    if (new_argv == NULL) {
+      reply_with_perror ("realloc");
+      free_strings (*argv);
+      return -1;
+    }
+    *argv = new_argv;
+  }
+
+  if (str) {
+    new_str = strdup (str);
+    if (new_str == NULL) {
+      reply_with_perror ("strdup");
+      free_strings (*argv);
+    }
+  } else
+    new_str = NULL;
+
+  (*argv)[*size] = new_str;
+
+  (*size)++;
+  return 0;
+}
+
+int
 count_strings (char **argv)
 {
   int argc;
 count_strings (char **argv)
 {
   int argc;
index 1bea5f1..05e2cc7 100644 (file)
 #include <string.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <dirent.h>
 #include <sys/stat.h>
 
 #include "daemon.h"
 #include "actions.h"
 
 #include <sys/stat.h>
 
 #include "daemon.h"
 #include "actions.h"
 
+static int
+compare (const void *vp1, const void *vp2)
+{
+  char * const *p1 = (char * const *) vp1;
+  char * const *p2 = (char * const *) vp2;
+  return strcmp (*p1, *p2);
+}
+
 char **
 do_ls (const char *path)
 {
 char **
 do_ls (const char *path)
 {
-  reply_with_error ("ls command is not yet implemented");
-  return NULL;
+  char **r = NULL;
+  int size = 0, alloc = 0;
+  DIR *dir;
+  struct dirent *d;
+
+  NEED_ROOT (NULL);
+  ABS_PATH (path, NULL);
+
+  CHROOT_IN;
+  dir = opendir (path);
+  CHROOT_OUT;
+
+  if (!dir) {
+    reply_with_perror ("opendir: %s", path);
+    return NULL;
+  }
+
+  while ((d = readdir (dir)) != NULL) {
+    if (strcmp (d->d_name, ".") == 0 || strcmp (d->d_name, "..") == 0)
+      continue;
+
+    if (add_string (&r, &size, &alloc, d->d_name) == -1) {
+      closedir (dir);
+      return NULL;
+    }
+  }
+
+  if (add_string (&r, &size, &alloc, NULL) == -1) {
+    closedir (dir);
+    return NULL;
+  }
+
+  if (closedir (dir) == -1) {
+    reply_with_perror ("closedir: %s", path);
+    free_strings (r);
+    return NULL;
+  }
+
+  qsort (r, size-1, sizeof (char *), compare);
+  return r;
 }
 
 char *
 }
 
 char *
@@ -42,10 +89,8 @@ do_ll (const char *path)
   char *out, *err;
   char *spath;
 
   char *out, *err;
   char *spath;
 
-  if (path[0] != '/') {
-    reply_with_error ("ll: path must start with a / character");
-    return NULL;
-  }
+  //NEED_ROOT
+  ABS_PATH (path, NULL);
 
   /* This exposes the /sysroot, because we can't chroot and run the ls
    * command (since 'ls' won't necessarily exist in the chroot).  This
 
   /* This exposes the /sysroot, because we can't chroot and run the ls
    * command (since 'ls' won't necessarily exist in the chroot).  This
index 6ab8e16..3116c94 100644 (file)
@@ -41,13 +41,13 @@ void list_commands (void)
 void display_command (const char *cmd)
 {
   if (strcasecmp (cmd, "cat") == 0)
 void display_command (const char *cmd)
 {
   if (strcasecmp (cmd, "cat") == 0)
-    pod2text ("cat - list the contents of a file", " cat <path>\n\nReturn the contents of the file named C<path>.\n\nBecause of the message protocol, there is a transfer limit \nof somewhere between 2MB and 4MB.  To transfer large files you should use\nFTP.");
+    pod2text ("cat - list the contents of a file", " cat <path>\n\nReturn the contents of the file named C<path>.\n\nNote that this function cannot correctly handle binary files\n(specifically, files containing C<\\0> character which is treated\nas end of string).  For those you need to use the C<guestfs_read>\nfunction which has a more complex interface.\n\nBecause of the message protocol, there is a transfer limit \nof somewhere between 2MB and 4MB.  To transfer large files you should use\nFTP.");
   else
   if (strcasecmp (cmd, "ll") == 0)
     pod2text ("ll - list the files in a directory (long format)", " ll <directory>\n\nList the files in C<directory> (relative to the root directory,\nthere is no cwd) in the format of 'ls -la'.\n\nThis command is mostly useful for interactive sessions.  It\nis I<not> intended that you try to parse the output string.");
   else
   if (strcasecmp (cmd, "ls") == 0)
   else
   if (strcasecmp (cmd, "ll") == 0)
     pod2text ("ll - list the files in a directory (long format)", " ll <directory>\n\nList the files in C<directory> (relative to the root directory,\nthere is no cwd) in the format of 'ls -la'.\n\nThis command is mostly useful for interactive sessions.  It\nis I<not> intended that you try to parse the output string.");
   else
   if (strcasecmp (cmd, "ls") == 0)
-    pod2text ("ls - list the files in a directory", " ls <directory>\n\nList the files in C<directory> (relative to the root directory,\nthere is no cwd).  The '.' and '..' entries are not returned, but\nhidden files are shown.\n\nThis command is mostly useful for interactive sessions.");
+    pod2text ("ls - list the files in a directory", " ls <directory>\n\nList the files in C<directory> (relative to the root directory,\nthere is no cwd).  The '.' and '..' entries are not returned, but\nhidden files are shown.\n\nThis command is mostly useful for interactive sessions.  Programs\nshould probably use C<guestfs_readdir> instead.");
   else
   if (strcasecmp (cmd, "mount") == 0)
     pod2text ("mount - mount a guest disk at a position in the filesystem", " mount <device> <mountpoint>\n\nMount a guest disk at a position in the filesystem.  Block devices\nare named C</dev/sda>, C</dev/sdb> and so on, as they were added to\nthe guest.  If those block devices contain partitions, they will have\nthe usual names (eg. C</dev/sda1>).  Also LVM C</dev/VG/LV>-style\nnames can be used.\n\nThe rules are the same as for L<mount(2)>:  A filesystem must\nfirst be mounted on C</> before others can be mounted.  Other\nfilesystems can only be mounted on directories which already\nexist.\n\nThe mounted filesystem is writable, if we have sufficient permissions\non the underlying device.\n\nThe filesystem options C<sync> and C<noatime> are set with this\ncall, in order to improve reliability.");
   else
   if (strcasecmp (cmd, "mount") == 0)
     pod2text ("mount - mount a guest disk at a position in the filesystem", " mount <device> <mountpoint>\n\nMount a guest disk at a position in the filesystem.  Block devices\nare named C</dev/sda>, C</dev/sdb> and so on, as they were added to\nthe guest.  If those block devices contain partitions, they will have\nthe usual names (eg. C</dev/sda1>).  Also LVM C</dev/VG/LV>-style\nnames can be used.\n\nThe rules are the same as for L<mount(2)>:  A filesystem must\nfirst be mounted on C</> before others can be mounted.  Other\nfilesystems can only be mounted on directories which already\nexist.\n\nThe mounted filesystem is writable, if we have sufficient permissions\non the underlying device.\n\nThe filesystem options C<sync> and C<noatime> are set with this\ncall, in order to improve reliability.");
index 352760d..eec6697 100644 (file)
@@ -5,6 +5,11 @@
 
 Return the contents of the file named C<path>.
 
 
 Return the contents of the file named C<path>.
 
+Note that this function cannot correctly handle binary files
+(specifically, files containing C<\0> character which is treated
+as end of string).  For those you need to use the C<guestfs_read>
+function which has a more complex interface.
+
 This function returns a string or NULL on error.  The caller
 must free the returned string after use.
 
 This function returns a string or NULL on error.  The caller
 must free the returned string after use.
 
@@ -35,7 +40,8 @@ List the files in C<directory> (relative to the root directory,
 there is no cwd).  The '.' and '..' entries are not returned, but
 hidden files are shown.
 
 there is no cwd).  The '.' and '..' entries are not returned, but
 hidden files are shown.
 
-This command is mostly useful for interactive sessions.
+This command is mostly useful for interactive sessions.  Programs
+should probably use C<guestfs_readdir> instead.
 
 This function returns a NULL-terminated array of strings
 (like L<environ(3)>), or NULL if there was an error.
 
 This function returns a NULL-terminated array of strings
 (like L<environ(3)>), or NULL if there was an error.
index 44cc06b..da35a0f 100755 (executable)
@@ -51,7 +51,12 @@ let functions = [
   ("cat", (RString "content", P1 (String "path")), 4, [ProtocolLimitWarning],
    "list the contents of a file",
    "\
   ("cat", (RString "content", P1 (String "path")), 4, [ProtocolLimitWarning],
    "list the contents of a file",
    "\
-Return the contents of the file named C<path>.");
+Return the contents of the file named C<path>.
+
+Note that this function cannot correctly handle binary files
+(specifically, files containing C<\\0> character which is treated
+as end of string).  For those you need to use the C<guestfs_read>
+function which has a more complex interface.");
 
   ("ll", (RString "listing", P1 (String "directory")), 5, [],
    "list the files in a directory (long format)",
 
   ("ll", (RString "listing", P1 (String "directory")), 5, [],
    "list the files in a directory (long format)",
@@ -69,7 +74,8 @@ List the files in C<directory> (relative to the root directory,
 there is no cwd).  The '.' and '..' entries are not returned, but
 hidden files are shown.
 
 there is no cwd).  The '.' and '..' entries are not returned, but
 hidden files are shown.
 
-This command is mostly useful for interactive sessions.");
+This command is mostly useful for interactive sessions.  Programs
+should probably use C<guestfs_readdir> instead.");
 
   ("mount", (Err, P2 (String "device", String "mountpoint")), 1, [],
    "mount a guest disk at a position in the filesystem",
 
   ("mount", (Err, P2 (String "device", String "mountpoint")), 1, [],
    "mount a guest disk at a position in the filesystem",