Add a new simple internal API to check the availability of installed
commands in the system at runtime, and cache the result of of the search
(the full path, if found).
This API will ease the usage of optional external applications used by
the library, as it will be possible to avoid using a tool if it is not
available in the system.
---
lib/Makefile.am | 1 +
lib/external-apps.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++
lib/guestfs-internal.h | 3 +
3 files changed, 194 insertions(+)
create mode 100644 lib/external-apps.c
diff --git a/lib/Makefile.am b/lib/Makefile.am
index e1ab1bf..2f53e83 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -85,6 +85,7 @@ libguestfs_la_SOURCES = \
dbdump.c \
drives.c \
errors.c \
+ external-apps.c \
event-string.c \
events.c \
file.c \
diff --git a/lib/external-apps.c b/lib/external-apps.c
new file mode 100644
index 0000000..2ed2bc2
--- /dev/null
+++ b/lib/external-apps.c
@@ -0,0 +1,190 @@
+/* libguestfs
+ * Copyright (C) 2017 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <assert.h>
+
+#include "glthread/lock.h"
+#include "hash.h"
+#include "hash-pjw.h"
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+
+static void free_cache (void) __attribute__((destructor));
+static bool hash_compare_strings (const void *a, const void *b);
+static void hash_free_strings (void *s);
+
+/* The actual cache of available applications. */
+static Hash_table *apps_ht;
+/* This lock protects access to the applications cache. */
+gl_lock_define_initialized (static, apps_lock);
+
+/* Dummy static object, representing a "not found" result
+ * (Hash_table does not accept NULL values for keys).
+ */
+static const char NOT_FOUND[] = "not_found";
+
+#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>.
+ */
+static int
+prog_exists (guestfs_h *g, const char *prog, char **ret_path)
+{
+ 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) {
+ *ret_path = safe_strdup (g, testprog);
+ return 1;
+ }
+
+ elem = strtok_r (NULL, ":", &saveptr);
+ }
+
+ /* Not found. */
+ return 0;
+}
+
+/**
+ * Check whether C<cmd> is installed.
+ *
+ * This function checks in C<$PATH> whether the specified command
+ * C<cmd> is available. If C<ret_path> is not C<NULL>, on success
+ * it will contain a new string with the full path of the command.
+ *
+ * Returns C<-1> on failures, C<0> if C<cmd> is not available, and
+ * C<1> when C<cmd> is available.
+ */
+int
+guestfs_int_find_cmd (guestfs_h *g, const char *cmd, char **ret_path)
+{
+ int ret = -1;
+ const void *value;
+ int r;
+ CLEANUP_FREE char *path = NULL;
+
+ if (ret_path)
+ *ret_path = NULL;
+
+ gl_lock_lock (apps_lock);
+
+ /* Create the hash table, if not existing already. */
+ if (apps_ht == NULL) {
+ /* We don't expect many applications searched, so start with
+ * a low value of potential items.
+ */
+ apps_ht = hash_initialize (10, NULL, hash_pjw, hash_compare_strings,
+ hash_free_strings);
+ if (apps_ht == NULL) {
+ error (g, "failed to create the hash table for caching applications");
+ goto out;
+ }
+ }
+
+ /* Check whether we already looked for CMD, setting the proper
+ * values depending on the results in the hash table.
+ */
+ value = hash_lookup (apps_ht, cmd);
+ if (value) {
+ if (value == NOT_FOUND) {
+ path = NULL;
+ ret = 0;
+ } else {
+ path = safe_strdup (g, (char *) value);
+ ret = 1;
+ }
+ goto out;
+ }
+
+ /* We didn't search for CMD yet, so look for it, and store the result
+ * in the hash table.
+ */
+ r = prog_exists (g, cmd, &path);
+ if (r < 0)
+ goto out;
+ else if (r == 0) {
+ value = hash_insert (apps_ht, NOT_FOUND);
+ ret = 0;
+ } else {
+ value = hash_insert (apps_ht, safe_strdup (g, path));
+ ret = 1;
+ }
+ assert (value);
+
+ out:
+ gl_lock_unlock (apps_lock);
+
+ debug (g, "external app: %s, result = %d, %s", cmd, ret, ret > 0 ? path :
"-");
+
+ if (ret > 0 && ret_path) {
+ assert (path);
+ *ret_path = path;
+ path = NULL;
+ }
+
+ return ret;
+}
+
+#if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */
+#pragma GCC diagnostic pop
+#endif
+
+void
+free_cache (void)
+{
+ if (apps_ht)
+ hash_free (apps_ht);
+}
+
+bool
+hash_compare_strings (const void *a, const void *b)
+{
+ return STREQ (a, b) ? true : false;
+}
+
+void
+hash_free_strings (void *s)
+{
+ free (s);
+}
diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h
index 04d087b..b50b86f 100644
--- a/lib/guestfs-internal.h
+++ b/lib/guestfs-internal.h
@@ -1007,4 +1007,7 @@ extern bool guestfs_int_version_cmp_ge (const struct version *a,
const struct ve
#define version_init_null(v) guestfs_int_version_from_values (v, 0, 0, 0)
#define version_is_null(v) ((v)->v_major == 0 && (v)->v_minor == 0
&& (v)->v_micro == 0)
+/* external-apps.c */
+extern int guestfs_int_find_cmd (guestfs_h *g, const char *cmd, char **ret_path);
+
#endif /* GUESTFS_INTERNAL_H_ */
--
2.9.3