[Libguestfs] [PATCH 1/6] daemon: Move utility functions to a separate file.

Richard W.M. Jones rjones at redhat.com
Thu Aug 3 17:13:46 UTC 2017


This allows us to share certain utility functions with OCaml code.
---
 daemon/Makefile.am |   1 +
 daemon/daemon.h    |   5 +-
 daemon/guestfsd.c  | 733 +--------------------------------------------------
 daemon/utils.c     | 757 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 766 insertions(+), 730 deletions(-)

diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index fc1389f67..5bfe409a4 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -173,6 +173,7 @@ guestfsd_SOURCES = \
 	truncate.c \
 	umask.c \
 	upload.c \
+	utils.c \
 	utimens.c \
 	utsname.c \
 	uuids.c \
diff --git a/daemon/daemon.h b/daemon/daemon.h
index 0f7ead258..a40dc3834 100644
--- a/daemon/daemon.h
+++ b/daemon/daemon.h
@@ -50,13 +50,14 @@ typedef struct {
   char *volume;
 } mountable_t;
 
-/* guestfsd.c */
+/* utils.c */
 extern int verbose;
 extern int enable_network;
 extern int autosync_umount;
 extern int test_mode;
 extern const char *sysroot;
 extern size_t sysroot_len;
+extern dev_t root_device;
 
 extern char *sysroot_path (const char *path);
 extern char *sysroot_realpath (const char *path);
@@ -83,7 +84,7 @@ extern char *get_random_uuid (void);
 extern char *make_exclude_from_file (const char *function, char *const *excludes);
 extern int asprintf_nowarn (char **strp, const char *fmt, ...);
 
-/* mountable functions (in guestfsd.c) */
+/* mountable functions (in utils.c) */
 extern char *mountable_to_string (const mountable_t *mountable);
 extern void cleanup_free_mountable (mountable_t *mountable);
 
diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c
index 2ceaccbee..94b4839bd 100644
--- a/daemon/guestfsd.c
+++ b/daemon/guestfsd.c
@@ -18,8 +18,7 @@
 
 /**
  * This is the guestfs daemon which runs inside the guestfs appliance.
- * This file handles start up, connecting back to the library, and has
- * several utility functions.
+ * This file handles start up and connecting back to the library.
  */
 
 #include <config.h>
@@ -32,57 +31,28 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <rpc/types.h>
-#include <rpc/xdr.h>
-#include <getopt.h>
-#include <sys/param.h>
-#include <sys/types.h>
-#include <sys/stat.h>
 #include <fcntl.h>
 #include <signal.h>
-#include <netdb.h>
-#include <sys/select.h>
-#include <sys/wait.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
+#include <getopt.h>
 #include <errno.h>
 #include <error.h>
 #include <assert.h>
 #include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #ifdef HAVE_PRINTF_H
 # include <printf.h>
 #endif
 
-#include <augeas.h>
-
 #include <caml/callback.h> /* for caml_startup */
 
-#include "sockets.h"
 #include "c-ctype.h"
 #include "ignore-value.h"
-#include "error.h"
+#include "sockets.h"
 
 #include "daemon.h"
 
-#ifndef MAX
-# define MAX(a,b) ((a)>(b)?(a):(b))
-#endif
-
-/* Not the end of the world if this open flag is not defined. */
-#ifndef O_CLOEXEC
-# define O_CLOEXEC 0
-#endif
-
-/* If root device is an ext2 filesystem, this is the major and minor.
- * This is so we can ignore this device from the point of view of the
- * user, eg. in guestfs_list_devices and many other places.
- */
-static dev_t root_device = 0;
-
-int verbose = 0;
-int enable_network = 0;
-
 static void makeraw (const char *channel, int fd);
 static int print_shell_quote (FILE *stream, const struct printf_info *info, const void *const *args);
 static int print_sysroot_shell_quote (FILE *stream, const struct printf_info *info, const void *const *args);
@@ -114,16 +84,6 @@ winsock_init (void)
 }
 #endif /* !WIN32 */
 
-/* Location to mount root device. */
-const char *sysroot = "/sysroot"; /* No trailing slash. */
-size_t sysroot_len = 8;
-
-/* If set (the default), do 'umount-all' when performing autosync. */
-int autosync_umount = 1;
-
-/* If set, we are testing the daemon as part of the libguestfs tests. */
-int test_mode = 0;
-
 /* Name of the virtio-serial channel. */
 #define VIRTIO_SERIAL_CHANNEL "/dev/virtio-ports/org.libguestfs.channel.0"
 
@@ -393,366 +353,6 @@ makeraw (const char *channel, int fd)
 }
 
 /**
- * Return true iff device is the root device (and therefore should be
- * ignored from the point of view of user calls).
- */
-static int
-is_root_device_stat (struct stat *statbuf)
-{
-  if (statbuf->st_rdev == root_device) return 1;
-  return 0;
-}
-
-int
-is_root_device (const char *device)
-{
-  struct stat statbuf;
-
-  udev_settle_file (device);
-
-  if (stat (device, &statbuf) == -1) {
-    perror (device);
-    return 0;
-  }
-
-  return is_root_device_stat (&statbuf);
-}
-
-/**
- * Turn C<"/path"> into C<"/sysroot/path">.
- *
- * Returns C<NULL> on failure.  The caller I<must> check for this and
- * call S<C<reply_with_perror ("malloc")>>.  The caller must also free
- * the returned string.
- *
- * See also the custom C<%R> printf formatter which does shell quoting too.
- */
-char *
-sysroot_path (const char *path)
-{
-  char *r;
-  const size_t len = strlen (path) + sysroot_len + 1;
-
-  r = malloc (len);
-  if (r == NULL)
-    return NULL;
-
-  snprintf (r, len, "%s%s", sysroot, path);
-  return r;
-}
-
-/**
- * Resolve path within sysroot, calling C<sysroot_path> on the
- * resolved path.
- *
- * Returns C<NULL> on failure.  The caller I<must> check for this and
- * call S<C<reply_with_perror ("malloc")>>.  The caller must also free
- * the returned string.
- *
- * See also the custom C<%R> printf formatter which does shell quoting too.
- */
-char *
-sysroot_realpath (const char *path)
-{
-  CLEANUP_FREE char *rp = NULL;
-
-  CHROOT_IN;
-  rp = realpath (path, NULL);
-  CHROOT_OUT;
-  if (rp == NULL)
-    return NULL;
-
-  return sysroot_path (rp);
-}
-
-int
-xwrite (int sock, const void *v_buf, size_t len)
-{
-  ssize_t r;
-  const char *buf = v_buf;
-
-  while (len > 0) {
-    r = write (sock, buf, len);
-    if (r == -1) {
-      perror ("write");
-      return -1;
-    }
-    buf += r;
-    len -= r;
-  }
-
-  return 0;
-}
-
-int
-xread (int sock, void *v_buf, size_t len)
-{
-  int r;
-  char *buf = v_buf;
-
-  while (len > 0) {
-    r = read (sock, buf, len);
-    if (r == -1) {
-      perror ("read");
-      return -1;
-    }
-    if (r == 0) {
-      fprintf (stderr, "read: unexpected end of file on fd %d\n", sock);
-      return -1;
-    }
-    buf += r;
-    len -= r;
-  }
-
-  return 0;
-}
-
-int
-add_string_nodup (struct stringsbuf *sb, char *str)
-{
-  char **new_argv;
-
-  if (sb->size >= sb->alloc) {
-    sb->alloc += 64;
-    new_argv = realloc (sb->argv, sb->alloc * sizeof (char *));
-    if (new_argv == NULL) {
-      reply_with_perror ("realloc");
-      free (str);
-      return -1;
-    }
-    sb->argv = new_argv;
-  }
-
-  sb->argv[sb->size] = str;
-  sb->size++;
-
-  return 0;
-}
-
-int
-add_string (struct stringsbuf *sb, const char *str)
-{
-  char *new_str = NULL;
-
-  if (str) {
-    new_str = strdup (str);
-    if (new_str == NULL) {
-      reply_with_perror ("strdup");
-      return -1;
-    }
-  }
-
-  return add_string_nodup (sb, new_str);
-}
-
-int
-add_sprintf (struct stringsbuf *sb, const char *fs, ...)
-{
-  va_list args;
-  char *str;
-  int r;
-
-  va_start (args, fs);
-  r = vasprintf (&str, fs, args);
-  va_end (args);
-  if (r == -1) {
-    reply_with_perror ("vasprintf");
-    return -1;
-  }
-
-  return add_string_nodup (sb, str);
-}
-
-int
-end_stringsbuf (struct stringsbuf *sb)
-{
-  return add_string_nodup (sb, NULL);
-}
-
-void
-free_stringsbuf (struct stringsbuf *sb)
-{
-  if (sb->argv != NULL)
-    free_stringslen (sb->argv, sb->size);
-}
-
-/* Take the ownership of the strings of the strings buffer,
- * resetting it to a null buffer.
- */
-char **
-take_stringsbuf (struct stringsbuf *sb)
-{
-  DECLARE_STRINGSBUF (null);
-  char **ret = sb->argv;
-  *sb = null;
-  return ret;
-}
-
-/**
- * Returns true if C<v> is a power of 2.
- *
- * Uses the algorithm described at
- * L<http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2>
- */
-int
-is_power_of_2 (unsigned long v)
-{
-  return v && ((v & (v - 1)) == 0);
-}
-
-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);
-}
-
-void
-sort_strings (char **argv, size_t len)
-{
-  qsort (argv, len, sizeof (char *), compare);
-}
-
-void
-free_stringslen (char **argv, size_t len)
-{
-  size_t i;
-
-  if (!argv)
-    return;
-
-  for (i = 0; i < len; ++i)
-    free (argv[i]);
-  free (argv);
-}
-
-/**
- * Split an output string into a NULL-terminated list of lines,
- * wrapped into a stringsbuf.
- *
- * Typically this is used where we have run an external command
- * which has printed out a list of things, and we want to return
- * an actual list.
- *
- * The corner cases here are quite tricky.  Note in particular:
- *
- * =over 4
- *
- * =item C<"">
- *
- * returns C<[]>
- *
- * =item C<"\n">
- *
- * returns C<[""]>
- *
- * =item C<"a\nb">
- *
- * returns C<["a"; "b"]>
- *
- * =item C<"a\nb\n">
- *
- * returns C<["a"; "b"]>
- *
- * =item C<"a\nb\n\n">
- *
- * returns C<["a"; "b"; ""]>
- *
- * =back
- *
- * The original string is written over and destroyed by this function
- * (which is usually OK because it's the 'out' string from
- * C<command*()>).  You can free the original string, because
- * C<add_string()> strdups the strings.
- *
- * C<argv> in the C<struct stringsbuf> will be C<NULL> in case of errors.
- */
-struct stringsbuf
-split_lines_sb (char *str)
-{
-  DECLARE_STRINGSBUF (lines);
-  DECLARE_STRINGSBUF (null);
-  char *p, *pend;
-
-  if (STREQ (str, "")) {
-    /* No need to check the return value, as the stringsbuf will be
-     * returned as it is anyway.
-     */
-    end_stringsbuf (&lines);
-    return lines;
-  }
-
-  p = str;
-  while (p) {
-    /* Empty last line? */
-    if (p[0] == '\0')
-      break;
-
-    pend = strchr (p, '\n');
-    if (pend) {
-      *pend = '\0';
-      pend++;
-    }
-
-    if (add_string (&lines, p) == -1) {
-      free_stringsbuf (&lines);
-      return null;
-    }
-
-    p = pend;
-  }
-
-  if (end_stringsbuf (&lines) == -1) {
-    free_stringsbuf (&lines);
-    return null;
-  }
-
-  return lines;
-}
-
-char **
-split_lines (char *str)
-{
-  struct stringsbuf sb = split_lines_sb (str);
-  return take_stringsbuf (&sb);
-}
-
-char **
-empty_list (void)
-{
-  DECLARE_STRINGSBUF (ret);
-
-  if (end_stringsbuf (&ret) == -1)
-    return NULL;
-
-  return ret.argv;
-}
-
-/**
- * Skip leading and trailing whitespace, updating the original string
- * in-place.
- */
-void
-trim (char *str)
-{
-  size_t len = strlen (str);
-
-  while (len > 0 && c_isspace (str[len-1])) {
-    str[len-1] = '\0';
-    len--;
-  }
-
-  const char *p = str;
-  while (*p && c_isspace (*p)) {
-    p++;
-    len--;
-  }
-
-  memmove (str, p, len+1);
-}
-
-/**
  * printf helper function so we can use C<%Q> ("quoted") and C<%R> to
  * print shell-quoted strings.  See L<guestfs-hacking(1)> for more
  * details.
@@ -812,326 +412,3 @@ print_arginfo (const struct printf_info *info, size_t n, int *argtypes)
 #error "HAVE_REGISTER_PRINTF_{SPECIFIER|FUNCTION} not defined"
 #endif
 #endif
-
-/**
- * Parse the mountable descriptor for a btrfs subvolume.  Don't call
- * this directly; it is only used from the stubs.
- *
- * A btrfs subvolume is given as:
- *
- *  btrfsvol:/dev/sda3/root
- *
- * where F</dev/sda3> is a block device containing a btrfs filesystem,
- * and root is the name of a subvolume on it. This function is passed
- * the string following C<"btrfsvol:">.
- *
- * On success, C<mountable-E<gt>device> and C<mountable-E<gt>volume>
- * must be freed by the caller.
- */
-int
-parse_btrfsvol (const char *desc_orig, mountable_t *mountable)
-{
-  CLEANUP_FREE char *desc = NULL;
-  CLEANUP_FREE char *device = NULL;
-  const char *volume = NULL;
-  char *slash;
-  struct stat statbuf;
-
-  mountable->type = MOUNTABLE_BTRFSVOL;
-
-  if (!STRPREFIX (desc_orig, "/dev/"))
-    return -1;
-
-  desc = strdup (desc_orig);
-  if (desc == NULL) {
-    perror ("strdup");
-    return -1;
-  }
-
-  slash = desc + strlen ("/dev/") - 1;
-  while ((slash = strchr (slash + 1, '/'))) {
-    *slash = '\0';
-
-    free (device);
-    device = device_name_translation (desc);
-    if (!device) {
-      perror (desc);
-      continue;
-    }
-
-    if (stat (device, &statbuf) == -1) {
-      perror (device);
-      return -1;
-    }
-
-    if (!S_ISDIR (statbuf.st_mode) &&
-        !is_root_device_stat (&statbuf)) {
-      volume = slash + 1;
-      break;
-    }
-
-    *slash = '/';
-  }
-
-  if (!device) return -1;
-
-  if (!volume) return -1;
-
-  mountable->volume = strdup (volume);
-  if (!mountable->volume) {
-    perror ("strdup");
-    return -1;
-  }
-
-  mountable->device = device;
-  device = NULL; /* to stop CLEANUP_FREE from freeing it */
-
-  return 0;
-}
-
-/**
- * Convert a C<mountable_t> back to its string representation
- *
- * This function can be used in an error path, so must not call
- * C<reply_with_error>.
- */
-char *
-mountable_to_string (const mountable_t *mountable)
-{
-  char *desc;
-
-  switch (mountable->type) {
-  case MOUNTABLE_DEVICE:
-  case MOUNTABLE_PATH:
-    return strdup (mountable->device);
-
-  case MOUNTABLE_BTRFSVOL:
-    if (asprintf (&desc, "btrfsvol:%s/%s",
-		  mountable->device, mountable->volume) == -1)
-      return NULL;
-    return desc;
-
-  default:
-    return NULL;
-  }
-}
-
-#if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstack-usage="
-#endif
-
-/**
- * Check program exists and is executable on C<$PATH>.
- */
-int
-prog_exists (const char *prog)
-{
-  const char *pathc = getenv ("PATH");
-
-  if (!pathc)
-    return 0;
-
-  const size_t proglen = strlen (prog);
-  const char *elem;
-  char *saveptr;
-  const size_t len = strlen (pathc) + 1;
-  char path[len];
-  strcpy (path, pathc);
-
-  elem = strtok_r (path, ":", &saveptr);
-  while (elem) {
-    const size_t n = strlen (elem) + proglen + 2;
-    char testprog[n];
-
-    snprintf (testprog, n, "%s/%s", elem, prog);
-    if (access (testprog, X_OK) == 0)
-      return 1;
-
-    elem = strtok_r (NULL, ":", &saveptr);
-  }
-
-  /* Not found. */
-  return 0;
-}
-
-#if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */
-#pragma GCC diagnostic pop
-#endif
-
-/**
- * Pass a template such as C<"/sysroot/XXXXXXXX.XXX">.  This updates
- * the template to contain a randomly named file.  Any C<'X'>
- * characters after the final C<'/'> in the template are replaced with
- * random characters.
- *
- * Notes: You should probably use an 8.3 path, so it's compatible with
- * all filesystems including basic FAT.  Also this only substitutes
- * lowercase ASCII letters and numbers, again for compatibility with
- * lowest common denominator filesystems.
- *
- * This doesn't create a file or check whether or not the file exists
- * (it would be extremely unlikely to exist as long as the RNG is
- * working).
- *
- * If there is an error, C<-1> is returned.
- */
-int
-random_name (char *template)
-{
-  int fd;
-  unsigned char c;
-  char *p;
-
-  fd = open ("/dev/urandom", O_RDONLY|O_CLOEXEC);
-  if (fd == -1)
-    return -1;
-
-  p = strrchr (template, '/');
-  if (p == NULL)
-    abort ();                   /* internal error - bad template */
-
-  while (*p) {
-    if (*p == 'X') {
-      if (read (fd, &c, 1) != 1) {
-        close (fd);
-        return -1;
-      }
-      *p = "0123456789abcdefghijklmnopqrstuvwxyz"[c % 36];
-    }
-
-    p++;
-  }
-
-  close (fd);
-  return 0;
-}
-
-/**
- * LVM and other commands aren't synchronous, especially when udev is
- * involved.  eg. You can create or remove some device, but the
- * C</dev> device node won't appear until some time later.  This means
- * that you get an error if you run one command followed by another.
- *
- * Use C<udevadm settle> after certain commands, but don't be too
- * fussed if it fails.
- */
-void
-udev_settle_file (const char *file)
-{
-  const size_t MAX_ARGS = 64;
-  const char *argv[MAX_ARGS];
-  CLEANUP_FREE char *err = NULL;
-  size_t i = 0;
-  int r;
-
-  ADD_ARG (argv, i, "udevadm");
-  if (verbose)
-    ADD_ARG (argv, i, "--debug");
-
-  ADD_ARG (argv, i, "settle");
-  if (file) {
-    ADD_ARG (argv, i, "-E");
-    ADD_ARG (argv, i, file);
-  }
-  ADD_ARG (argv, i, NULL);
-
-  r = commandv (NULL, &err, argv);
-  if (r == -1)
-    fprintf (stderr, "udevadm settle: %s\n", err);
-}
-
-void
-udev_settle (void)
-{
-  udev_settle_file (NULL);
-}
-
-char *
-get_random_uuid (void)
-{
-  int r;
-  char *out;
-  CLEANUP_FREE char *err = NULL;
-
-  r = command (&out, &err, "uuidgen", NULL);
-  if (r == -1) {
-    reply_with_error ("%s", err);
-    return NULL;
-  }
-
-  /* caller free */
-  return out;
-
-}
-
-/**
- * Turn list C<excludes> into a temporary file, and return a string
- * containing the temporary file name.  Caller must unlink the file
- * and free the string.
- *
- * C<function> is the function that invoked this helper, and it is
- * used mainly for errors/debugging.
- */
-char *
-make_exclude_from_file (const char *function, char *const *excludes)
-{
-  size_t i;
-  int fd;
-  char template[] = "/tmp/excludesXXXXXX";
-  char *ret;
-
-  fd = mkstemp (template);
-  if (fd == -1) {
-    reply_with_perror ("mkstemp");
-    return NULL;
-  }
-
-  for (i = 0; excludes[i] != NULL; ++i) {
-    if (strchr (excludes[i], '\n')) {
-      reply_with_error ("%s: excludes file patterns cannot contain \\n character",
-                        function);
-      goto error;
-    }
-
-    if (xwrite (fd, excludes[i], strlen (excludes[i])) == -1 ||
-        xwrite (fd, "\n", 1) == -1) {
-      reply_with_perror ("write");
-      goto error;
-    }
-
-    if (verbose)
-      fprintf (stderr, "%s: adding excludes pattern '%s'\n",
-               function, excludes[i]);
-  }
-
-  if (close (fd) == -1) {
-    reply_with_perror ("close");
-    fd = -1;
-    goto error;
-  }
-  fd = -1;
-
-  ret = strdup (template);
-  if (ret == NULL) {
-    reply_with_perror ("strdup");
-    goto error;
-  }
-
-  return ret;
-
- error:
-  if (fd >= 0)
-    close (fd);
-  unlink (template);
-  return NULL;
-}
-
-void
-cleanup_free_mountable (mountable_t *mountable)
-{
-  if (mountable) {
-    free (mountable->device);
-    free (mountable->volume);
-  }
-}
diff --git a/daemon/utils.c b/daemon/utils.c
new file mode 100644
index 000000000..6ec232252
--- /dev/null
+++ b/daemon/utils.c
@@ -0,0 +1,757 @@
+/* libguestfs - the guestfsd daemon
+ * Copyright (C) 2009-2017 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * Miscellaneous utility functions used by the daemon.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <rpc/types.h>
+#include <rpc/xdr.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <error.h>
+#include <assert.h>
+
+#include "c-ctype.h"
+
+#include "daemon.h"
+
+#ifndef MAX
+# define MAX(a,b) ((a)>(b)?(a):(b))
+#endif
+
+/* Not the end of the world if this open flag is not defined. */
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
+
+/* If root device is an ext2 filesystem, this is the major and minor.
+ * This is so we can ignore this device from the point of view of the
+ * user, eg. in guestfs_list_devices and many other places.
+ */
+dev_t root_device = 0;
+
+int verbose = 0;
+int enable_network = 0;
+
+/* Location to mount root device. */
+const char *sysroot = "/sysroot"; /* No trailing slash. */
+size_t sysroot_len = 8;
+
+/* If set (the default), do 'umount-all' when performing autosync. */
+int autosync_umount = 1;
+
+/* If set, we are testing the daemon as part of the libguestfs tests. */
+int test_mode = 0;
+
+/**
+ * Return true iff device is the root device (and therefore should be
+ * ignored from the point of view of user calls).
+ */
+static int
+is_root_device_stat (struct stat *statbuf)
+{
+  if (statbuf->st_rdev == root_device) return 1;
+  return 0;
+}
+
+int
+is_root_device (const char *device)
+{
+  struct stat statbuf;
+
+  udev_settle_file (device);
+
+  if (stat (device, &statbuf) == -1) {
+    perror (device);
+    return 0;
+  }
+
+  return is_root_device_stat (&statbuf);
+}
+
+/**
+ * Turn C<"/path"> into C<"/sysroot/path">.
+ *
+ * Returns C<NULL> on failure.  The caller I<must> check for this and
+ * call S<C<reply_with_perror ("malloc")>>.  The caller must also free
+ * the returned string.
+ *
+ * See also the custom C<%R> printf formatter which does shell quoting too.
+ */
+char *
+sysroot_path (const char *path)
+{
+  char *r;
+  const size_t len = strlen (path) + sysroot_len + 1;
+
+  r = malloc (len);
+  if (r == NULL)
+    return NULL;
+
+  snprintf (r, len, "%s%s", sysroot, path);
+  return r;
+}
+
+/**
+ * Resolve path within sysroot, calling C<sysroot_path> on the
+ * resolved path.
+ *
+ * Returns C<NULL> on failure.  The caller I<must> check for this and
+ * call S<C<reply_with_perror ("malloc")>>.  The caller must also free
+ * the returned string.
+ *
+ * See also the custom C<%R> printf formatter which does shell quoting too.
+ */
+char *
+sysroot_realpath (const char *path)
+{
+  CLEANUP_FREE char *rp = NULL;
+
+  CHROOT_IN;
+  rp = realpath (path, NULL);
+  CHROOT_OUT;
+  if (rp == NULL)
+    return NULL;
+
+  return sysroot_path (rp);
+}
+
+int
+xwrite (int sock, const void *v_buf, size_t len)
+{
+  ssize_t r;
+  const char *buf = v_buf;
+
+  while (len > 0) {
+    r = write (sock, buf, len);
+    if (r == -1) {
+      perror ("write");
+      return -1;
+    }
+    buf += r;
+    len -= r;
+  }
+
+  return 0;
+}
+
+int
+xread (int sock, void *v_buf, size_t len)
+{
+  int r;
+  char *buf = v_buf;
+
+  while (len > 0) {
+    r = read (sock, buf, len);
+    if (r == -1) {
+      perror ("read");
+      return -1;
+    }
+    if (r == 0) {
+      fprintf (stderr, "read: unexpected end of file on fd %d\n", sock);
+      return -1;
+    }
+    buf += r;
+    len -= r;
+  }
+
+  return 0;
+}
+
+int
+add_string_nodup (struct stringsbuf *sb, char *str)
+{
+  char **new_argv;
+
+  if (sb->size >= sb->alloc) {
+    sb->alloc += 64;
+    new_argv = realloc (sb->argv, sb->alloc * sizeof (char *));
+    if (new_argv == NULL) {
+      reply_with_perror ("realloc");
+      free (str);
+      return -1;
+    }
+    sb->argv = new_argv;
+  }
+
+  sb->argv[sb->size] = str;
+  sb->size++;
+
+  return 0;
+}
+
+int
+add_string (struct stringsbuf *sb, const char *str)
+{
+  char *new_str = NULL;
+
+  if (str) {
+    new_str = strdup (str);
+    if (new_str == NULL) {
+      reply_with_perror ("strdup");
+      return -1;
+    }
+  }
+
+  return add_string_nodup (sb, new_str);
+}
+
+int
+add_sprintf (struct stringsbuf *sb, const char *fs, ...)
+{
+  va_list args;
+  char *str;
+  int r;
+
+  va_start (args, fs);
+  r = vasprintf (&str, fs, args);
+  va_end (args);
+  if (r == -1) {
+    reply_with_perror ("vasprintf");
+    return -1;
+  }
+
+  return add_string_nodup (sb, str);
+}
+
+int
+end_stringsbuf (struct stringsbuf *sb)
+{
+  return add_string_nodup (sb, NULL);
+}
+
+void
+free_stringsbuf (struct stringsbuf *sb)
+{
+  if (sb->argv != NULL)
+    free_stringslen (sb->argv, sb->size);
+}
+
+/* Take the ownership of the strings of the strings buffer,
+ * resetting it to a null buffer.
+ */
+char **
+take_stringsbuf (struct stringsbuf *sb)
+{
+  DECLARE_STRINGSBUF (null);
+  char **ret = sb->argv;
+  *sb = null;
+  return ret;
+}
+
+/**
+ * Returns true if C<v> is a power of 2.
+ *
+ * Uses the algorithm described at
+ * L<http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2>
+ */
+int
+is_power_of_2 (unsigned long v)
+{
+  return v && ((v & (v - 1)) == 0);
+}
+
+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);
+}
+
+void
+sort_strings (char **argv, size_t len)
+{
+  qsort (argv, len, sizeof (char *), compare);
+}
+
+void
+free_stringslen (char **argv, size_t len)
+{
+  size_t i;
+
+  if (!argv)
+    return;
+
+  for (i = 0; i < len; ++i)
+    free (argv[i]);
+  free (argv);
+}
+
+/**
+ * Split an output string into a NULL-terminated list of lines,
+ * wrapped into a stringsbuf.
+ *
+ * Typically this is used where we have run an external command
+ * which has printed out a list of things, and we want to return
+ * an actual list.
+ *
+ * The corner cases here are quite tricky.  Note in particular:
+ *
+ * =over 4
+ *
+ * =item C<"">
+ *
+ * returns C<[]>
+ *
+ * =item C<"\n">
+ *
+ * returns C<[""]>
+ *
+ * =item C<"a\nb">
+ *
+ * returns C<["a"; "b"]>
+ *
+ * =item C<"a\nb\n">
+ *
+ * returns C<["a"; "b"]>
+ *
+ * =item C<"a\nb\n\n">
+ *
+ * returns C<["a"; "b"; ""]>
+ *
+ * =back
+ *
+ * The original string is written over and destroyed by this function
+ * (which is usually OK because it's the 'out' string from
+ * C<command*()>).  You can free the original string, because
+ * C<add_string()> strdups the strings.
+ *
+ * C<argv> in the C<struct stringsbuf> will be C<NULL> in case of errors.
+ */
+struct stringsbuf
+split_lines_sb (char *str)
+{
+  DECLARE_STRINGSBUF (lines);
+  DECLARE_STRINGSBUF (null);
+  char *p, *pend;
+
+  if (STREQ (str, "")) {
+    /* No need to check the return value, as the stringsbuf will be
+     * returned as it is anyway.
+     */
+    end_stringsbuf (&lines);
+    return lines;
+  }
+
+  p = str;
+  while (p) {
+    /* Empty last line? */
+    if (p[0] == '\0')
+      break;
+
+    pend = strchr (p, '\n');
+    if (pend) {
+      *pend = '\0';
+      pend++;
+    }
+
+    if (add_string (&lines, p) == -1) {
+      free_stringsbuf (&lines);
+      return null;
+    }
+
+    p = pend;
+  }
+
+  if (end_stringsbuf (&lines) == -1) {
+    free_stringsbuf (&lines);
+    return null;
+  }
+
+  return lines;
+}
+
+char **
+split_lines (char *str)
+{
+  struct stringsbuf sb = split_lines_sb (str);
+  return take_stringsbuf (&sb);
+}
+
+char **
+empty_list (void)
+{
+  DECLARE_STRINGSBUF (ret);
+
+  if (end_stringsbuf (&ret) == -1)
+    return NULL;
+
+  return ret.argv;
+}
+
+/**
+ * Skip leading and trailing whitespace, updating the original string
+ * in-place.
+ */
+void
+trim (char *str)
+{
+  size_t len = strlen (str);
+
+  while (len > 0 && c_isspace (str[len-1])) {
+    str[len-1] = '\0';
+    len--;
+  }
+
+  const char *p = str;
+  while (*p && c_isspace (*p)) {
+    p++;
+    len--;
+  }
+
+  memmove (str, p, len+1);
+}
+
+/**
+ * Parse the mountable descriptor for a btrfs subvolume.  Don't call
+ * this directly; it is only used from the stubs.
+ *
+ * A btrfs subvolume is given as:
+ *
+ *  btrfsvol:/dev/sda3/root
+ *
+ * where F</dev/sda3> is a block device containing a btrfs filesystem,
+ * and root is the name of a subvolume on it. This function is passed
+ * the string following C<"btrfsvol:">.
+ *
+ * On success, C<mountable-E<gt>device> and C<mountable-E<gt>volume>
+ * must be freed by the caller.
+ */
+int
+parse_btrfsvol (const char *desc_orig, mountable_t *mountable)
+{
+  CLEANUP_FREE char *desc = NULL;
+  CLEANUP_FREE char *device = NULL;
+  const char *volume = NULL;
+  char *slash;
+  struct stat statbuf;
+
+  mountable->type = MOUNTABLE_BTRFSVOL;
+
+  if (!STRPREFIX (desc_orig, "/dev/"))
+    return -1;
+
+  desc = strdup (desc_orig);
+  if (desc == NULL) {
+    perror ("strdup");
+    return -1;
+  }
+
+  slash = desc + strlen ("/dev/") - 1;
+  while ((slash = strchr (slash + 1, '/'))) {
+    *slash = '\0';
+
+    free (device);
+    device = device_name_translation (desc);
+    if (!device) {
+      perror (desc);
+      continue;
+    }
+
+    if (stat (device, &statbuf) == -1) {
+      perror (device);
+      return -1;
+    }
+
+    if (!S_ISDIR (statbuf.st_mode) &&
+        !is_root_device_stat (&statbuf)) {
+      volume = slash + 1;
+      break;
+    }
+
+    *slash = '/';
+  }
+
+  if (!device) return -1;
+
+  if (!volume) return -1;
+
+  mountable->volume = strdup (volume);
+  if (!mountable->volume) {
+    perror ("strdup");
+    return -1;
+  }
+
+  mountable->device = device;
+  device = NULL; /* to stop CLEANUP_FREE from freeing it */
+
+  return 0;
+}
+
+/**
+ * Convert a C<mountable_t> back to its string representation
+ *
+ * This function can be used in an error path, so must not call
+ * C<reply_with_error>.
+ */
+char *
+mountable_to_string (const mountable_t *mountable)
+{
+  char *desc;
+
+  switch (mountable->type) {
+  case MOUNTABLE_DEVICE:
+  case MOUNTABLE_PATH:
+    return strdup (mountable->device);
+
+  case MOUNTABLE_BTRFSVOL:
+    if (asprintf (&desc, "btrfsvol:%s/%s",
+		  mountable->device, mountable->volume) == -1)
+      return NULL;
+    return desc;
+
+  default:
+    return NULL;
+  }
+}
+
+#if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstack-usage="
+#endif
+
+/**
+ * Check program exists and is executable on C<$PATH>.
+ */
+int
+prog_exists (const char *prog)
+{
+  const char *pathc = getenv ("PATH");
+
+  if (!pathc)
+    return 0;
+
+  const size_t proglen = strlen (prog);
+  const char *elem;
+  char *saveptr;
+  const size_t len = strlen (pathc) + 1;
+  char path[len];
+  strcpy (path, pathc);
+
+  elem = strtok_r (path, ":", &saveptr);
+  while (elem) {
+    const size_t n = strlen (elem) + proglen + 2;
+    char testprog[n];
+
+    snprintf (testprog, n, "%s/%s", elem, prog);
+    if (access (testprog, X_OK) == 0)
+      return 1;
+
+    elem = strtok_r (NULL, ":", &saveptr);
+  }
+
+  /* Not found. */
+  return 0;
+}
+
+#if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */
+#pragma GCC diagnostic pop
+#endif
+
+/**
+ * Pass a template such as C<"/sysroot/XXXXXXXX.XXX">.  This updates
+ * the template to contain a randomly named file.  Any C<'X'>
+ * characters after the final C<'/'> in the template are replaced with
+ * random characters.
+ *
+ * Notes: You should probably use an 8.3 path, so it's compatible with
+ * all filesystems including basic FAT.  Also this only substitutes
+ * lowercase ASCII letters and numbers, again for compatibility with
+ * lowest common denominator filesystems.
+ *
+ * This doesn't create a file or check whether or not the file exists
+ * (it would be extremely unlikely to exist as long as the RNG is
+ * working).
+ *
+ * If there is an error, C<-1> is returned.
+ */
+int
+random_name (char *template)
+{
+  int fd;
+  unsigned char c;
+  char *p;
+
+  fd = open ("/dev/urandom", O_RDONLY|O_CLOEXEC);
+  if (fd == -1)
+    return -1;
+
+  p = strrchr (template, '/');
+  if (p == NULL)
+    abort ();                   /* internal error - bad template */
+
+  while (*p) {
+    if (*p == 'X') {
+      if (read (fd, &c, 1) != 1) {
+        close (fd);
+        return -1;
+      }
+      *p = "0123456789abcdefghijklmnopqrstuvwxyz"[c % 36];
+    }
+
+    p++;
+  }
+
+  close (fd);
+  return 0;
+}
+
+/**
+ * LVM and other commands aren't synchronous, especially when udev is
+ * involved.  eg. You can create or remove some device, but the
+ * C</dev> device node won't appear until some time later.  This means
+ * that you get an error if you run one command followed by another.
+ *
+ * Use C<udevadm settle> after certain commands, but don't be too
+ * fussed if it fails.
+ */
+void
+udev_settle_file (const char *file)
+{
+  const size_t MAX_ARGS = 64;
+  const char *argv[MAX_ARGS];
+  CLEANUP_FREE char *err = NULL;
+  size_t i = 0;
+  int r;
+
+  ADD_ARG (argv, i, "udevadm");
+  if (verbose)
+    ADD_ARG (argv, i, "--debug");
+
+  ADD_ARG (argv, i, "settle");
+  if (file) {
+    ADD_ARG (argv, i, "-E");
+    ADD_ARG (argv, i, file);
+  }
+  ADD_ARG (argv, i, NULL);
+
+  r = commandv (NULL, &err, argv);
+  if (r == -1)
+    fprintf (stderr, "udevadm settle: %s\n", err);
+}
+
+void
+udev_settle (void)
+{
+  udev_settle_file (NULL);
+}
+
+char *
+get_random_uuid (void)
+{
+  int r;
+  char *out;
+  CLEANUP_FREE char *err = NULL;
+
+  r = command (&out, &err, "uuidgen", NULL);
+  if (r == -1) {
+    reply_with_error ("%s", err);
+    return NULL;
+  }
+
+  /* caller free */
+  return out;
+
+}
+
+/**
+ * Turn list C<excludes> into a temporary file, and return a string
+ * containing the temporary file name.  Caller must unlink the file
+ * and free the string.
+ *
+ * C<function> is the function that invoked this helper, and it is
+ * used mainly for errors/debugging.
+ */
+char *
+make_exclude_from_file (const char *function, char *const *excludes)
+{
+  size_t i;
+  int fd;
+  char template[] = "/tmp/excludesXXXXXX";
+  char *ret;
+
+  fd = mkstemp (template);
+  if (fd == -1) {
+    reply_with_perror ("mkstemp");
+    return NULL;
+  }
+
+  for (i = 0; excludes[i] != NULL; ++i) {
+    if (strchr (excludes[i], '\n')) {
+      reply_with_error ("%s: excludes file patterns cannot contain \\n character",
+                        function);
+      goto error;
+    }
+
+    if (xwrite (fd, excludes[i], strlen (excludes[i])) == -1 ||
+        xwrite (fd, "\n", 1) == -1) {
+      reply_with_perror ("write");
+      goto error;
+    }
+
+    if (verbose)
+      fprintf (stderr, "%s: adding excludes pattern '%s'\n",
+               function, excludes[i]);
+  }
+
+  if (close (fd) == -1) {
+    reply_with_perror ("close");
+    fd = -1;
+    goto error;
+  }
+  fd = -1;
+
+  ret = strdup (template);
+  if (ret == NULL) {
+    reply_with_perror ("strdup");
+    goto error;
+  }
+
+  return ret;
+
+ error:
+  if (fd >= 0)
+    close (fd);
+  unlink (template);
+  return NULL;
+}
+
+void
+cleanup_free_mountable (mountable_t *mountable)
+{
+  if (mountable) {
+    free (mountable->device);
+    free (mountable->volume);
+  }
+}
-- 
2.13.1




More information about the Libguestfs mailing list