Blame SOURCES/0029-Update-common-submodule-to-latest-upstream.patch

15d06e
From 9292a4637e8f4d534f4dde70e8e5451f61ad0162 Mon Sep 17 00:00:00 2001
15d06e
From: "Richard W.M. Jones" <rjones@redhat.com>
15d06e
Date: Tue, 19 Jan 2021 14:22:33 +0000
15d06e
Subject: [PATCH] Update common/ submodule to latest upstream.
15d06e
15d06e
Only for RHEL AV 8.4.0, allowing this branch to be compiled
15d06e
from git with libguestfs 1.44.
15d06e
---
15d06e
 common | 2 +-
15d06e
 1 file changed, 1 insertion(+), 1 deletion(-)
15d06e
15d06e
Submodule common 9338df5e...be09523d:
15d06e
diff --git a/common/mlcustomize/SELinux_relabel.ml b/common/mlcustomize/SELinux_relabel.ml
15d06e
index 44995df6..5ecf7bd7 100644
15d06e
--- a/common/mlcustomize/SELinux_relabel.ml
15d06e
+++ b/common/mlcustomize/SELinux_relabel.ml
15d06e
@@ -28,65 +28,80 @@ module G = Guestfs
15d06e
 let array_find a l =
15d06e
   List.mem a (Array.to_list l)
15d06e
 
15d06e
-let relabel (g : G.guestfs) =
15d06e
-  (* Is the guest using SELinux? *)
15d06e
-  if g#is_file ~followsymlinks:true "/usr/sbin/load_policy" &&
15d06e
-     g#is_file ~followsymlinks:true "/etc/selinux/config" then (
15d06e
-    (* Is setfiles / SELinux relabelling functionality available? *)
15d06e
-    if g#feature_available [| "selinuxrelabel" |] then (
15d06e
-      (* Use Augeas to parse /etc/selinux/config. *)
15d06e
-      g#aug_init "/" (16+32) (* AUG_SAVE_NOOP | AUG_NO_LOAD *);
15d06e
-      (* See: https://bugzilla.redhat.com/show_bug.cgi?id=975412#c0 *)
15d06e
-      ignore (g#aug_rm "/augeas/load/*[\"/etc/selinux/config/\" !~ regexp('^') + glob(incl) + regexp('/.*')]");
15d06e
-      g#aug_load ();
15d06e
-      debug_augeas_errors g;
15d06e
-
15d06e
-      (* Get the SELinux policy name, eg. "targeted", "minimum".
15d06e
-       * Use "targeted" if not specified, just like libselinux does.
15d06e
-       *)
15d06e
-      let policy =
15d06e
-        let config_path = "/files/etc/selinux/config" in
15d06e
-        let selinuxtype_path = config_path ^ "/SELINUXTYPE" in
15d06e
-        let keys = g#aug_ls config_path in
15d06e
-        if array_find selinuxtype_path keys then
15d06e
-          g#aug_get selinuxtype_path
15d06e
-        else
15d06e
-          "targeted" in
15d06e
-
15d06e
-      g#aug_close ();
15d06e
-
15d06e
-      (* Get the spec file name. *)
15d06e
-      let specfile =
15d06e
-        sprintf "/etc/selinux/%s/contexts/files/file_contexts" policy in
15d06e
-
15d06e
-      (* RHEL 6.2 - 6.5 had a malformed specfile that contained the
15d06e
-       * invalid regular expression "/var/run/spice-vdagentd.\pid"
15d06e
-       * (instead of "\.p").  This stops setfiles from working on
15d06e
-       * the guest.
15d06e
-       *
15d06e
-       * Because an SELinux relabel writes all over the filesystem,
15d06e
-       * it seems reasonable to fix this problem in the specfile
15d06e
-       * at the same time.  (RHBZ#1374232)
15d06e
-       *)
15d06e
-      if g#grep ~fixed:true "vdagentd.\\pid" specfile <> [||] then (
15d06e
-        debug "fixing invalid regular expression in %s" specfile;
15d06e
-        let old_specfile = specfile ^ "~" in
15d06e
-        g#mv specfile old_specfile;
15d06e
-        let content = g#read_file old_specfile in
15d06e
-        let content =
15d06e
-          String.replace content "vdagentd.\\pid" "vdagentd\\.pid" in
15d06e
-        g#write specfile content;
15d06e
-        g#copy_attributes ~all:true old_specfile specfile
15d06e
-      );
15d06e
-
15d06e
-      (* Relabel everything. *)
15d06e
-      g#selinux_relabel ~force:true specfile "/";
15d06e
-
15d06e
-      (* If that worked, we don't need to autorelabel. *)
15d06e
+let rec relabel (g : G.guestfs) =
15d06e
+  (* Is the guest using SELinux?  (Otherwise this is a no-op). *)
15d06e
+  if is_selinux_guest g then (
15d06e
+    try
15d06e
+      use_setfiles g;
15d06e
+      (* That worked, so we don't need to autorelabel. *)
15d06e
       g#rm_f "/.autorelabel"
15d06e
-    )
15d06e
-    else (
15d06e
-      (* SELinux guest, but not SELinux host.  Fallback to this. *)
15d06e
+    with Failure _ ->
15d06e
+      (* This is the fallback in case something in the setfiles
15d06e
+       * method didn't work.  That includes the case where a non-SELinux
15d06e
+       * host is processing an SELinux guest, and other things.
15d06e
+       *)
15d06e
       g#touch "/.autorelabel"
15d06e
-    )
15d06e
   )
15d06e
+
15d06e
+and is_selinux_guest g =
15d06e
+  g#is_file ~followsymlinks:true "/usr/sbin/load_policy" &&
15d06e
+  g#is_file ~followsymlinks:true "/etc/selinux/config"
15d06e
+
15d06e
+and use_setfiles g =
15d06e
+  (* Is setfiles / SELinux relabelling functionality available? *)
15d06e
+  if not (g#feature_available [| "selinuxrelabel" |]) then
15d06e
+    failwith "no selinux relabel feature";
15d06e
+
15d06e
+  (* Use Augeas to parse /etc/selinux/config. *)
15d06e
+  g#aug_init "/" (16+32) (* AUG_SAVE_NOOP | AUG_NO_LOAD *);
15d06e
+  (* See: https://bugzilla.redhat.com/show_bug.cgi?id=975412#c0 *)
15d06e
+  ignore (g#aug_rm "/augeas/load/*[\"/etc/selinux/config/\" !~ regexp('^') + glob(incl) + regexp('/.*')]");
15d06e
+  g#aug_load ();
15d06e
+  debug_augeas_errors g;
15d06e
+
15d06e
+  (* Get the SELinux policy name, eg. "targeted", "minimum".
15d06e
+   * Use "targeted" if not specified, just like libselinux does.
15d06e
+   *)
15d06e
+  let policy =
15d06e
+    let config_path = "/files/etc/selinux/config" in
15d06e
+    let selinuxtype_path = config_path ^ "/SELINUXTYPE" in
15d06e
+    let keys = g#aug_ls config_path in
15d06e
+    if array_find selinuxtype_path keys then
15d06e
+      g#aug_get selinuxtype_path
15d06e
+    else
15d06e
+      "targeted" in
15d06e
+
15d06e
+  g#aug_close ();
15d06e
+
15d06e
+  (* Get the spec file name. *)
15d06e
+  let specfile =
15d06e
+    sprintf "/etc/selinux/%s/contexts/files/file_contexts" policy in
15d06e
+
15d06e
+  (* If the spec file doesn't exist then fall back to using
15d06e
+   * autorelabel (RHBZ#1828952).
15d06e
+   *)
15d06e
+  if not (g#is_file ~followsymlinks:true specfile) then
15d06e
+    failwith "no spec file";
15d06e
+
15d06e
+  (* RHEL 6.2 - 6.5 had a malformed specfile that contained the
15d06e
+   * invalid regular expression "/var/run/spice-vdagentd.\pid"
15d06e
+   * (instead of "\.p").  This stops setfiles from working on
15d06e
+   * the guest.
15d06e
+   *
15d06e
+   * Because an SELinux relabel writes all over the filesystem,
15d06e
+   * it seems reasonable to fix this problem in the specfile
15d06e
+   * at the same time.  (RHBZ#1374232)
15d06e
+   *)
15d06e
+  if g#grep ~fixed:true "vdagentd.\\pid" specfile <> [||] then (
15d06e
+    debug "fixing invalid regular expression in %s" specfile;
15d06e
+    let old_specfile = specfile ^ "~" in
15d06e
+    g#mv specfile old_specfile;
15d06e
+    let content = g#read_file old_specfile in
15d06e
+    let content =
15d06e
+      String.replace content "vdagentd.\\pid" "vdagentd\\.pid" in
15d06e
+    g#write specfile content;
15d06e
+    g#copy_attributes ~all:true old_specfile specfile
15d06e
+  );
15d06e
+
15d06e
+  (* Relabel everything. *)
15d06e
+  g#selinux_relabel ~force:true specfile "/"
15d06e
diff --git a/common/mltools/Makefile.am b/common/mltools/Makefile.am
15d06e
index 3b4172db..aea2dce9 100644
15d06e
--- a/common/mltools/Makefile.am
15d06e
+++ b/common/mltools/Makefile.am
15d06e
@@ -95,6 +95,7 @@ libmltools_a_CPPFLAGS = \
15d06e
 	-I$(shell $(OCAMLC) -where) \
15d06e
 	-I$(top_srcdir)/common/utils \
15d06e
 	-I$(top_srcdir)/lib \
15d06e
+	$(INCLUDE_DIRECTORY) \
15d06e
 	-I$(top_srcdir)/common/options \
15d06e
 	-I$(top_srcdir)/common/mlgettext \
15d06e
 	-I$(top_srcdir)/common/mlpcre \
15d06e
diff --git a/common/mltools/tools_utils.ml b/common/mltools/tools_utils.ml
15d06e
index 12718022..d54ec581 100644
15d06e
--- a/common/mltools/tools_utils.ml
15d06e
+++ b/common/mltools/tools_utils.ml
15d06e
@@ -679,3 +679,53 @@ let with_timeout op timeout ?(sleep = 2) fn =
15d06e
        loop ()
15d06e
   in
15d06e
   loop ()
15d06e
+
15d06e
+let run_in_guest_command g root ?logfile ?incompatible_fn cmd =
15d06e
+  (* Is the host_cpu compatible with the guest arch?  ie. Can we
15d06e
+   * run commands in this guest?
15d06e
+   *)
15d06e
+  let guest_arch = g#inspect_get_arch root in
15d06e
+  let guest_arch_compatible = guest_arch_compatible guest_arch in
15d06e
+  if not guest_arch_compatible then (
15d06e
+    match incompatible_fn with
15d06e
+    | None -> ()
15d06e
+    | Some fn -> fn ()
15d06e
+  )
15d06e
+  else (
15d06e
+    (* Add a prologue to the scripts:
15d06e
+     * - Pass environment variables through from the host.
15d06e
+     * - Optionally send stdout and stderr to a log file so we capture
15d06e
+     *   all output in error messages.
15d06e
+     * - Use setarch when running x86_64 host + i686 guest.
15d06e
+     *)
15d06e
+    let env_vars =
15d06e
+      List.filter_map (
15d06e
+        fun name ->
15d06e
+          try Some (sprintf "export %s=%s" name (quote (Sys.getenv name)))
15d06e
+          with Not_found -> None
15d06e
+      ) [ "http_proxy"; "https_proxy"; "ftp_proxy"; "no_proxy" ] in
15d06e
+    let env_vars = String.concat "\n" env_vars ^ "\n" in
15d06e
+
15d06e
+    let cmd =
15d06e
+      match Guestfs_config.host_cpu, guest_arch with
15d06e
+      | "x86_64", ("i386"|"i486"|"i586"|"i686") ->
15d06e
+        sprintf "setarch i686 <<\"__EOCMD\"
15d06e
+%s
15d06e
+__EOCMD
15d06e
+" cmd
15d06e
+      | _ -> cmd in
15d06e
+
15d06e
+    let logfile_redirect =
15d06e
+      match logfile with
15d06e
+      | None -> ""
15d06e
+      | Some logfile -> sprintf "exec >>%s 2>&1" (quote logfile) in
15d06e
+
15d06e
+    let cmd = sprintf "\
15d06e
+%s
15d06e
+%s
15d06e
+%s
15d06e
+" (logfile_redirect) env_vars cmd in
15d06e
+
15d06e
+    debug "running command:\n%s" cmd;
15d06e
+    ignore (g#sh cmd)
15d06e
+  )
15d06e
diff --git a/common/mltools/tools_utils.mli b/common/mltools/tools_utils.mli
15d06e
index ab70f583..1d1ac8a8 100644
15d06e
--- a/common/mltools/tools_utils.mli
15d06e
+++ b/common/mltools/tools_utils.mli
15d06e
@@ -195,9 +195,8 @@ val is_btrfs_subvolume : Guestfs.guestfs -> string -> bool
15d06e
 (** Checks if a filesystem is a btrfs subvolume. *)
15d06e
 
15d06e
 val inspect_decrypt : Guestfs.guestfs -> key_store -> unit
15d06e
-(** Simple implementation of decryption: look for any [crypto_LUKS]
15d06e
-    partitions and decrypt them, then rescan for VGs.  This only works
15d06e
-    for Fedora whole-disk encryption. *)
15d06e
+(** Simple implementation of decryption: look for any encrypted
15d06e
+    partitions and decrypt them, then rescan for VGs. *)
15d06e
 
15d06e
 val with_timeout : string -> int -> ?sleep:int -> (unit -> 'a option) -> 'a
15d06e
 (** [with_timeout op timeout ?sleep fn] implements a timeout loop.
15d06e
@@ -212,3 +211,13 @@ val with_timeout : string -> int -> ?sleep:int -> (unit -> 'a option) -> 'a
15d06e
     calls {!error} and the program exits.  The error message will
15d06e
     contain the diagnostic string [op] to identify the operation
15d06e
     which timed out. *)
15d06e
+
15d06e
+val run_in_guest_command : Guestfs.guestfs -> string -> ?logfile:string -> ?incompatible_fn:(unit -> unit) -> string -> unit
15d06e
+(** [run_in_guest_command g root ?incompatible_archs_fn cmd]
15d06e
+    runs a command in the guest, which is already mounted for the
15d06e
+    specified [root].  The command is run directly in case the
15d06e
+    architecture of the host and the guest are compatible, optionally
15d06e
+    calling [?incompatible_fn] in case they are not.
15d06e
+
15d06e
+    [?logfile] is an optional file in the guest to where redirect
15d06e
+    stdout and stderr of the command. *)
15d06e
diff --git a/common/mlutils/unix_utils-c.c b/common/mlutils/unix_utils-c.c
15d06e
index 33099611..8acf0395 100644
15d06e
--- a/common/mlutils/unix_utils-c.c
15d06e
+++ b/common/mlutils/unix_utils-c.c
15d06e
@@ -77,6 +77,7 @@ extern value guestfs_int_mllib_mkdtemp (value val_pattern);
15d06e
 extern value guestfs_int_mllib_realpath (value pathv);
15d06e
 extern value guestfs_int_mllib_statvfs_statvfs (value pathv);
15d06e
 extern value guestfs_int_mllib_statvfs_is_network_filesystem (value pathv);
15d06e
+extern value guestfs_int_mllib_sysconf_nr_processors_online (value unitv);
15d06e
 
15d06e
 /* NB: This is a "noalloc" call. */
15d06e
 value
15d06e
@@ -368,3 +369,17 @@ guestfs_int_mllib_statvfs_is_network_filesystem (value pathv)
15d06e
   return Val_bool (0);
15d06e
 #endif
15d06e
 }
15d06e
+
15d06e
+/* NB: This is a "noalloc" call. */
15d06e
+value
15d06e
+guestfs_int_mllib_sysconf_nr_processors_online (value unitv)
15d06e
+{
15d06e
+#ifdef _SC_NPROCESSORS_ONLN
15d06e
+  long n;
15d06e
+
15d06e
+  n = sysconf (_SC_NPROCESSORS_ONLN);
15d06e
+  if (n > 0) return Val_int (n);
15d06e
+#endif
15d06e
+  /* Return a safe value so that callers don't need to deal with errors. */
15d06e
+  return Val_int (1);
15d06e
+}
15d06e
diff --git a/common/mlutils/unix_utils.ml b/common/mlutils/unix_utils.ml
15d06e
index 52eb824d..2bdda12a 100644
15d06e
--- a/common/mlutils/unix_utils.ml
15d06e
+++ b/common/mlutils/unix_utils.ml
15d06e
@@ -84,3 +84,8 @@ module StatVFS = struct
15d06e
   external is_network_filesystem : string -> bool =
15d06e
     "guestfs_int_mllib_statvfs_is_network_filesystem" "noalloc"
15d06e
 end
15d06e
+
15d06e
+module Sysconf = struct
15d06e
+  external nr_processors_online : unit -> int =
15d06e
+    "guestfs_int_mllib_sysconf_nr_processors_online" "noalloc"
15d06e
+end
15d06e
diff --git a/common/mlutils/unix_utils.mli b/common/mlutils/unix_utils.mli
15d06e
index 4fcea4a3..aead4df2 100644
15d06e
--- a/common/mlutils/unix_utils.mli
15d06e
+++ b/common/mlutils/unix_utils.mli
15d06e
@@ -121,3 +121,12 @@ module StatVFS : sig
15d06e
   (** [is_network_filesystem path] returns true if [path] is located on
15d06e
       a network filesystem such as NFS or CIFS. *)
15d06e
 end
15d06e
+
15d06e
+module Sysconf : sig
15d06e
+  val nr_processors_online : unit -> int
15d06e
+  (** [nr_processors_online ()] returns the number of processors
15d06e
+      currently online, from [sysconf (_SC_NPROCESSORS_ONLN)].
15d06e
+
15d06e
+      Note this never fails.  In case we cannot get the number of
15d06e
+      cores it returns 1. *)
15d06e
+end
15d06e
diff --git a/common/options/Makefile.am b/common/options/Makefile.am
15d06e
index f7ea7493..162d143b 100644
15d06e
--- a/common/options/Makefile.am
15d06e
+++ b/common/options/Makefile.am
15d06e
@@ -41,8 +41,9 @@ liboptions_la_SOURCES = \
15d06e
 liboptions_la_CPPFLAGS = \
15d06e
 	-DGUESTFS_NO_DEPRECATED=1 \
15d06e
 	-I$(top_srcdir)/common/utils -I$(top_builddir)/common/utils \
15d06e
+	-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
15d06e
 	-I$(top_srcdir)/lib -I$(top_builddir)/lib \
15d06e
-	-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib
15d06e
+	$(INCLUDE_DIRECTORY)
15d06e
 liboptions_la_CFLAGS = \
15d06e
 	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
15d06e
 	$(LIBCONFIG_CFLAGS) \
15d06e
diff --git a/common/options/decrypt.c b/common/options/decrypt.c
15d06e
index 683cf5ed..434b7d58 100644
15d06e
--- a/common/options/decrypt.c
15d06e
+++ b/common/options/decrypt.c
15d06e
@@ -25,6 +25,7 @@
15d06e
 
15d06e
 #include <stdio.h>
15d06e
 #include <stdlib.h>
15d06e
+#include <stdbool.h>
15d06e
 #include <string.h>
15d06e
 #include <libintl.h>
15d06e
 #include <error.h>
15d06e
@@ -38,18 +39,18 @@
15d06e
 
15d06e
 /**
15d06e
  * Make a LUKS map name from the partition name,
15d06e
- * eg. C<"/dev/vda2" =E<gt> "luksvda2">
15d06e
+ * eg. C<"/dev/vda2" =E<gt> "cryptvda2">
15d06e
  */
15d06e
 static void
15d06e
 make_mapname (const char *device, char *mapname, size_t len)
15d06e
 {
15d06e
   size_t i = 0;
15d06e
 
15d06e
-  if (len < 5)
15d06e
+  if (len < 6)
15d06e
     abort ();
15d06e
-  strcpy (mapname, "luks");
15d06e
-  mapname += 4;
15d06e
-  len -= 4;
15d06e
+  strcpy (mapname, "crypt");
15d06e
+  mapname += 5;
15d06e
+  len -= 5;
15d06e
 
15d06e
   if (STRPREFIX (device, "/dev/"))
15d06e
     i = 5;
15d06e
@@ -65,10 +66,8 @@ make_mapname (const char *device, char *mapname, size_t len)
15d06e
 }
15d06e
 
15d06e
 /**
15d06e
- * Simple implementation of decryption: look for any C<crypto_LUKS>
15d06e
- * partitions and decrypt them, then rescan for VGs.  This only works
15d06e
- * for Fedora whole-disk encryption.  WIP to make this work for other
15d06e
- * encryption schemes.
15d06e
+ * Simple implementation of decryption: look for any encrypted
15d06e
+ * partitions and decrypt them, then rescan for VGs.
15d06e
  */
15d06e
 void
15d06e
 inspect_do_decrypt (guestfs_h *g, struct key_store *ks)
15d06e
@@ -82,12 +81,21 @@ inspect_do_decrypt (guestfs_h *g, struct key_store *ks)
15d06e
 
15d06e
   for (i = 0; partitions[i] != NULL; ++i) {
15d06e
     CLEANUP_FREE char *type = guestfs_vfs_type (g, partitions[i]);
15d06e
-    if (type && STREQ (type, "crypto_LUKS")) {
15d06e
+    if (type &&
15d06e
+        (STREQ (type, "crypto_LUKS") || STREQ (type, "BitLocker"))) {
15d06e
+      bool is_bitlocker = STREQ (type, "BitLocker");
15d06e
       char mapname[32];
15d06e
       make_mapname (partitions[i], mapname, sizeof mapname);
15d06e
 
15d06e
 #ifdef GUESTFS_HAVE_LUKS_UUID
15d06e
-      CLEANUP_FREE char *uuid = guestfs_luks_uuid (g, partitions[i]);
15d06e
+      CLEANUP_FREE char *uuid = NULL;
15d06e
+
15d06e
+      /* This fails for Windows BitLocker disks because cryptsetup
15d06e
+       * luksUUID cannot read a UUID (unclear if this is a limitation
15d06e
+       * of the format or cryptsetup).
15d06e
+       */
15d06e
+      if (!is_bitlocker)
15d06e
+        uuid = guestfs_luks_uuid (g, partitions[i]);
15d06e
 #else
15d06e
       const char *uuid = NULL;
15d06e
 #endif
15d06e
@@ -97,11 +105,15 @@ inspect_do_decrypt (guestfs_h *g, struct key_store *ks)
15d06e
 
15d06e
       /* Try each key in turn. */
15d06e
       for (j = 0; keys[j] != NULL; ++j) {
15d06e
-        /* XXX Should we call guestfs_luks_open_ro if readonly flag
15d06e
+        /* XXX Should we set GUESTFS_CRYPTSETUP_OPEN_READONLY if readonly
15d06e
          * is set?  This might break 'mount_ro'.
15d06e
          */
15d06e
         guestfs_push_error_handler (g, NULL, NULL);
15d06e
+#ifdef GUESTFS_HAVE_CRYPTSETUP_OPEN
15d06e
+        r = guestfs_cryptsetup_open (g, partitions[i], keys[j], mapname, -1);
15d06e
+#else
15d06e
         r = guestfs_luks_open (g, partitions[i], keys[j], mapname);
15d06e
+#endif
15d06e
         guestfs_pop_error_handler (g);
15d06e
         if (r == 0)
15d06e
           goto opened;
15d06e
diff --git a/common/options/uri.c b/common/options/uri.c
15d06e
index ac36bccb..6b696fc2 100644
15d06e
--- a/common/options/uri.c
15d06e
+++ b/common/options/uri.c
15d06e
@@ -194,6 +194,7 @@ parse (const char *arg, char **path_ret, char **protocol_ret,
15d06e
   if (path && path[0] == '/' &&
15d06e
       (STREQ (uri->scheme, "gluster") ||
15d06e
        STREQ (uri->scheme, "iscsi") ||
15d06e
+       STREQ (uri->scheme, "nbd") ||
15d06e
        STREQ (uri->scheme, "rbd") ||
15d06e
        STREQ (uri->scheme, "sheepdog")))
15d06e
     path++;
15d06e
diff --git a/common/utils/guestfs-stringlists-utils.h b/common/utils/guestfs-stringlists-utils.h
15d06e
index 0bac1587..ade3b6f3 100644
15d06e
--- a/common/utils/guestfs-stringlists-utils.h
15d06e
+++ b/common/utils/guestfs-stringlists-utils.h
15d06e
@@ -21,7 +21,8 @@
15d06e
 
15d06e
 /* stringlists-utils.c */
15d06e
 extern void guestfs_int_free_string_list (char **);
15d06e
-extern size_t guestfs_int_count_strings (char *const *);
15d06e
+extern size_t guestfs_int_count_strings (char *const *)
15d06e
+  __attribute__((__nonnull__ (1)));
15d06e
 extern char *guestfs_int_concat_strings (char *const *);
15d06e
 extern char **guestfs_int_copy_string_list (char *const *);
15d06e
 extern char *guestfs_int_join_strings (const char *sep, char *const *);