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#DetermineIfPower...
- */
-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#DetermineIfPower...
+ */
+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