Blob Blame History Raw
From efb8a766cac4ba8e413594946136bf91e176bb8c Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Tue, 30 Mar 2021 13:54:22 +0100
Subject: [PATCH 3/3] daemon: Allow xorriso as an alternative to isoinfo.

Currently the guestfs_isoinfo and guestfs_isoinfo_device APIs run
isoinfo inside the appliance to extract the information.

isoinfo is part of genisoimage which is somewhat dead upstream.
xorriso is supposedly the new thing.  (For a summary of the situation
see: https://wiki.debian.org/genisoimage).

This commit rewrites the parsing from C to OCaml to make it easier to
deal with, and allows you to use either isoinfo or xorriso.

Mostly the same fields are available from either tool, but xorriso is
a bit more awkward to parse.
---
 .gitignore                |   1 +
 appliance/packagelist.in  |   2 +
 daemon/Makefile.am        |   4 +-
 daemon/isoinfo.c          | 279 --------------------------------------
 daemon/isoinfo.ml         | 248 +++++++++++++++++++++++++++++++++
 docs/C_SOURCE_FILES       |   1 -
 generator/actions_core.ml |   2 +
 po/POTFILES               |   1 -
 8 files changed, 256 insertions(+), 282 deletions(-)
 delete mode 100644 daemon/isoinfo.c
 create mode 100644 daemon/isoinfo.ml

diff --git a/.gitignore b/.gitignore
index f9b95a6c5..de4abff58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -92,6 +92,7 @@ Makefile.in
 /daemon/guestfsd.exe
 /daemon/inspect.mli
 /daemon/is.mli
+/daemon/isoinfo.mli
 /daemon/ldm.mli
 /daemon/link.mli
 /daemon/listfs.mli
diff --git a/appliance/packagelist.in b/appliance/packagelist.in
index 2f75a1c17..f4ad50b79 100644
--- a/appliance/packagelist.in
+++ b/appliance/packagelist.in
@@ -45,6 +45,7 @@ ifelse(REDHAT,1,
   syslinux-extlinux
   systemd              dnl for /sbin/reboot and udevd
   vim-minimal
+  xorriso              dnl alternative for genisoimage
   xz
   zfs-fuse
 )
@@ -84,6 +85,7 @@ dnl iproute has been renamed to iproute2
   systemd                   dnl alternative for /sbin/reboot
   ufsutils
   vim-tiny
+  xorriso                   dnl alternative for genisoimage
   xz-utils
   zfs-fuse
   uuid-runtime
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 216029b7c..6f13bd43c 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -46,6 +46,7 @@ generator_built = \
 	findfs.mli \
 	inspect.mli \
 	is.mli \
+	isoinfo.mli \
 	ldm.mli \
 	link.mli \
 	listfs.mli \
@@ -142,7 +143,6 @@ guestfsd_SOURCES = \
 	inotify.c \
 	internal.c \
 	is.c \
-	isoinfo.c \
 	journal.c \
 	labels.c \
 	ldm.c \
@@ -291,6 +291,7 @@ SOURCES_MLI = \
 	inspect_types.mli \
 	inspect_utils.mli \
 	is.mli \
+	isoinfo.mli \
 	ldm.mli \
 	link.mli \
 	listfs.mli \
@@ -324,6 +325,7 @@ SOURCES_ML = \
 	devsparts.ml \
 	file.ml \
 	filearch.ml \
+	isoinfo.ml \
 	is.ml \
 	ldm.ml \
 	link.ml \
diff --git a/daemon/isoinfo.c b/daemon/isoinfo.c
deleted file mode 100644
index e616df82f..000000000
--- a/daemon/isoinfo.c
+++ /dev/null
@@ -1,279 +0,0 @@
-/* libguestfs - the guestfsd daemon
- * Copyright (C) 2012 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.
- */
-
-#include <config.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <inttypes.h>
-#include <string.h>
-#include <unistd.h>
-#include <time.h>
-
-#include "guestfs_protocol.h"
-#include "daemon.h"
-#include "actions.h"
-
-static int
-parse_uint32 (uint32_t *ret, const char *str)
-{
-  uint32_t r;
-
-  if (sscanf (str, "%" SCNu32, &r) != 1) {
-    reply_with_error ("cannot parse numeric field from isoinfo: %s", str);
-    return -1;
-  }
-
-  *ret = r;
-  return 0;
-}
-
-/* This is always in a fixed format:
- * "2012 03 16 11:05:46.00"
- * or if the field is not present, then:
- * "0000 00 00 00:00:00.00"
- */
-static int
-parse_time_t (int64_t *ret, const char *str)
-{
-  struct tm tm;
-  time_t r;
-
-  if (STREQ (str, "0000 00 00 00:00:00.00") ||
-      STREQ (str, "             :  :  .  ")) {
-    *ret = -1;
-    return 0;
-  }
-
-  if (sscanf (str, "%04d %02d %02d %02d:%02d:%02d",
-              &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
-              &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
-    reply_with_error ("cannot parse date from isoinfo: %s", str);
-    return -1;
-  }
-
-  /* Adjust fields. */
-  tm.tm_year -= 1900;
-  tm.tm_mon--;
-  tm.tm_isdst = -1;
-
-  /* Convert to time_t. */
-  r = timegm (&tm);
-  if (r == -1) {
-    reply_with_error ("invalid date or time: %s", str);
-    return -1;
-  }
-
-  *ret = r;
-  return 0;
-}
-
-static guestfs_int_isoinfo *
-parse_isoinfo (char **lines)
-{
-  guestfs_int_isoinfo *ret;
-  size_t i;
-
-  ret = calloc (1, sizeof *ret);
-  if (ret == NULL) {
-    reply_with_perror ("calloc");
-    return NULL;
-  }
-
-  /* Default each int field in the struct to -1. */
-  ret->iso_volume_space_size = (uint32_t) -1;
-  ret->iso_volume_set_size = (uint32_t) -1;
-  ret->iso_volume_sequence_number = (uint32_t) -1;
-  ret->iso_logical_block_size = (uint32_t) -1;
-  ret->iso_volume_creation_t = -1;
-  ret->iso_volume_modification_t = -1;
-  ret->iso_volume_expiration_t = -1;
-  ret->iso_volume_effective_t = -1;
-
-  for (i = 0; lines[i] != NULL; ++i) {
-    if (STRPREFIX (lines[i], "System id: ")) {
-      ret->iso_system_id = strdup (&lines[i][11]);
-      if (ret->iso_system_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Volume id: ")) {
-      ret->iso_volume_id = strdup (&lines[i][11]);
-      if (ret->iso_volume_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Volume set id: ")) {
-      ret->iso_volume_set_id = strdup (&lines[i][15]);
-      if (ret->iso_volume_set_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Publisher id: ")) {
-      ret->iso_publisher_id = strdup (&lines[i][14]);
-      if (ret->iso_publisher_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Data preparer id: ")) {
-      ret->iso_data_preparer_id = strdup (&lines[i][18]);
-      if (ret->iso_data_preparer_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Application id: ")) {
-      ret->iso_application_id = strdup (&lines[i][16]);
-      if (ret->iso_application_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Copyright File id: ")) {
-      ret->iso_copyright_file_id = strdup (&lines[i][19]);
-      if (ret->iso_copyright_file_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Abstract File id: ")) {
-      ret->iso_abstract_file_id = strdup (&lines[i][18]);
-      if (ret->iso_abstract_file_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Bibliographic File id: ")) {
-      ret->iso_bibliographic_file_id = strdup (&lines[i][23]);
-      if (ret->iso_bibliographic_file_id == NULL) goto error;
-    }
-    else if (STRPREFIX (lines[i], "Volume size is: ")) {
-      if (parse_uint32 (&ret->iso_volume_space_size, &lines[i][16]) == -1)
-        goto error;
-    }
-    else if (STRPREFIX (lines[i], "Volume set size is: ")) {
-      if (parse_uint32 (&ret->iso_volume_set_size, &lines[i][20]) == -1)
-        goto error;
-    }
-    else if (STRPREFIX (lines[i], "Volume set sequence number is: ")) {
-      if (parse_uint32 (&ret->iso_volume_sequence_number, &lines[i][31]) == -1)
-        goto error;
-    }
-    else if (STRPREFIX (lines[i], "Logical block size is: ")) {
-      if (parse_uint32 (&ret->iso_logical_block_size, &lines[i][23]) == -1)
-        goto error;
-    }
-    else if (STRPREFIX (lines[i], "Creation Date:     ")) {
-      if (parse_time_t (&ret->iso_volume_creation_t, &lines[i][19]) == -1)
-        goto error;
-    }
-    else if (STRPREFIX (lines[i], "Modification Date: ")) {
-      if (parse_time_t (&ret->iso_volume_modification_t, &lines[i][19]) == -1)
-        goto error;
-    }
-    else if (STRPREFIX (lines[i], "Expiration Date:   ")) {
-      if (parse_time_t (&ret->iso_volume_expiration_t, &lines[i][19]) == -1)
-        goto error;
-    }
-    else if (STRPREFIX (lines[i], "Effective Date:    ")) {
-      if (parse_time_t (&ret->iso_volume_effective_t, &lines[i][19]) == -1)
-        goto error;
-    }
-  }
-
-  /* Any string fields which were not set above will be NULL.  However
-   * we cannot return NULL fields in structs, so we convert these to
-   * empty strings here.
-   */
-  if (ret->iso_system_id == NULL) {
-    ret->iso_system_id = strdup ("");
-    if (ret->iso_system_id == NULL) goto error;
-  }
-  if (ret->iso_volume_id == NULL) {
-    ret->iso_volume_id = strdup ("");
-    if (ret->iso_volume_id == NULL) goto error;
-  }
-  if (ret->iso_volume_set_id == NULL) {
-    ret->iso_volume_set_id = strdup ("");
-    if (ret->iso_volume_set_id == NULL) goto error;
-  }
-  if (ret->iso_publisher_id == NULL) {
-    ret->iso_publisher_id = strdup ("");
-    if (ret->iso_publisher_id == NULL) goto error;
-  }
-  if (ret->iso_data_preparer_id == NULL) {
-    ret->iso_data_preparer_id = strdup ("");
-    if (ret->iso_data_preparer_id == NULL) goto error;
-  }
-  if (ret->iso_application_id == NULL) {
-    ret->iso_application_id = strdup ("");
-    if (ret->iso_application_id == NULL) goto error;
-  }
-  if (ret->iso_copyright_file_id == NULL) {
-    ret->iso_copyright_file_id = strdup ("");
-    if (ret->iso_copyright_file_id == NULL) goto error;
-  }
-  if (ret->iso_abstract_file_id == NULL) {
-    ret->iso_abstract_file_id = strdup ("");
-    if (ret->iso_abstract_file_id == NULL) goto error;
-  }
-  if (ret->iso_bibliographic_file_id == NULL) {
-    ret->iso_bibliographic_file_id = strdup ("");
-    if (ret->iso_bibliographic_file_id == NULL) goto error;
-  }
-
-  return ret;
-
- error:
-  free (ret->iso_system_id);
-  free (ret->iso_volume_id);
-  free (ret->iso_volume_set_id);
-  free (ret->iso_publisher_id);
-  free (ret->iso_data_preparer_id);
-  free (ret->iso_application_id);
-  free (ret->iso_copyright_file_id);
-  free (ret->iso_abstract_file_id);
-  free (ret->iso_bibliographic_file_id);
-  free (ret);
-  return NULL;
-}
-
-static guestfs_int_isoinfo *
-isoinfo (const char *path)
-{
-  int r;
-  CLEANUP_FREE char *out = NULL, *err = NULL;
-  CLEANUP_FREE_STRING_LIST char **lines = NULL;
-
-  /* --debug is necessary to get additional fields, in particular
-   * the date & time fields.
-   */
-  r = command (&out, &err, "isoinfo", "--debug", "-d", "-i", path, NULL);
-  if (r == -1) {
-    reply_with_error ("%s", err);
-    return NULL;
-  }
-
-  lines = split_lines (out);
-  if (lines == NULL)
-    return NULL;
-
-  return parse_isoinfo (lines);
-}
-
-guestfs_int_isoinfo *
-do_isoinfo_device (const char *device)
-{
-  return isoinfo (device);
-}
-
-guestfs_int_isoinfo *
-do_isoinfo (const char *path)
-{
-  guestfs_int_isoinfo *ret;
-  CLEANUP_FREE char *buf = sysroot_path (path);
-  if (!buf) {
-    reply_with_perror ("malloc");
-    return NULL;
-  }
-
-  ret = isoinfo (buf);
-
-  return ret;
-}
diff --git a/daemon/isoinfo.ml b/daemon/isoinfo.ml
new file mode 100644
index 000000000..b7fe0af7e
--- /dev/null
+++ b/daemon/isoinfo.ml
@@ -0,0 +1,248 @@
+(* Parse isoinfo or xorriso output.
+ * Copyright (C) 2009-2021 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.
+ *)
+
+open Printf
+open Scanf
+open Unix
+
+open Std_utils
+open Unix_utils
+
+open Mountable
+open Utils
+
+include Structs
+
+type tool = Isoinfo | Xorriso
+let tool = ref None
+
+let get_tool () =
+  match !tool with
+  | Some t -> t
+  | None ->
+     (* Prefer isoinfo because we've been using that tool for longer. *)
+     if Sys.command "isoinfo -version" = 0 then (
+       tool := Some Isoinfo;
+       Isoinfo
+     )
+     else if Sys.command "xorriso -version" = 0 then (
+       tool := Some Xorriso;
+       Xorriso
+     )
+     else
+       failwith "isoinfo or xorriso not available"
+
+(* Default each int field in the struct to -1 and each string to "". *)
+let default_iso = {
+  iso_system_id = "";
+  iso_volume_id = "";
+  iso_volume_space_size = -1_l;
+  iso_volume_set_size = -1_l;
+  iso_volume_sequence_number = -1_l;
+  (* This is almost always true for CDs because of the media itself,
+   * and is not available from xorriso.
+   *)
+  iso_logical_block_size = 2048_l;
+  iso_volume_set_id = "";
+  iso_publisher_id = "";
+  iso_data_preparer_id = "";
+  iso_application_id = "";
+  iso_copyright_file_id = "";
+  iso_abstract_file_id = "";
+  iso_bibliographic_file_id = "";
+  iso_volume_creation_t = -1_L;
+  iso_volume_modification_t = -1_L;
+  iso_volume_expiration_t = -1_L;
+  iso_volume_effective_t = -1_L;
+}
+
+(* This is always in a fixed format:
+ * "2012 03 16 11:05:46.00"
+ * or if the field is not present, then:
+ * "0000 00 00 00:00:00.00"
+ *)
+let parse_isoinfo_date str =
+  if str = "0000 00 00 00:00:00.00" ||
+     str = "             :  :  .  " then
+    -1_L
+  else (
+    sscanf str "%04d %02d %02d %02d:%02d:%02d"
+      (fun tm_year tm_mon tm_mday tm_hour tm_min tm_sec ->
+        (* Adjust fields. *)
+        let tm_year = tm_year - 1900 in
+        let tm_mon = tm_mon - 1 in
+
+        (* Convert to time_t *)
+        let tm = { tm_sec; tm_min; tm_hour; tm_mday; tm_mon; tm_year;
+                   tm_wday = -1; tm_yday = -1; tm_isdst = false } in
+        Int64.of_float (fst (Unix.mktime tm))
+      )
+  )
+
+let do_isoinfo dev =
+  (* --debug is necessary to get additional fields, in particular
+   * the date & time fields.
+   *)
+  let lines = command "isoinfo" ["--debug"; "-d"; "-i"; dev] in
+  let lines = String.nsplit "\n" lines in
+
+  let ret = ref default_iso in
+  List.iter (
+    fun line ->
+      let n = String.length line in
+      if String.is_prefix line "System id: " then
+        ret := { !ret with iso_system_id = String.sub line 11 (n-11) }
+      else if String.is_prefix line "Volume id: " then
+        ret := { !ret with iso_volume_id = String.sub line 11 (n-11) }
+      else if String.is_prefix line "Volume set id: " then
+        ret := { !ret with iso_volume_set_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "Publisher id: " then
+        ret := { !ret with iso_publisher_id = String.sub line 14 (n-14) }
+      else if String.is_prefix line "Data preparer id: " then
+        ret := { !ret with iso_data_preparer_id = String.sub line 18 (n-18) }
+      else if String.is_prefix line "Application id: " then
+        ret := { !ret with iso_application_id = String.sub line 16 (n-16) }
+      else if String.is_prefix line "Copyright File id: " then
+        ret := { !ret with iso_copyright_file_id = String.sub line 19 (n-19) }
+      else if String.is_prefix line "Abstract File id: " then
+        ret := { !ret with iso_abstract_file_id = String.sub line 18 (n-18) }
+      else if String.is_prefix line "Bibliographic File id: " then
+        ret := { !ret with
+                 iso_bibliographic_file_id = String.sub line 23 (n-23) }
+      else if String.is_prefix line "Volume size is: " then (
+        let i = Int32.of_string (String.sub line 16 (n-16)) in
+        ret := { !ret with iso_volume_space_size = i }
+      )
+      else if String.is_prefix line "Volume set size is: " then (
+        let i = Int32.of_string (String.sub line 20 (n-20)) in
+        ret := { !ret with iso_volume_set_size = i }
+      )
+      else if String.is_prefix line "Volume set sequence number is: " then (
+        let i = Int32.of_string (String.sub line 31 (n-31)) in
+        ret := { !ret with iso_volume_sequence_number = i }
+      )
+      else if String.is_prefix line "Logical block size is: " then (
+        let i = Int32.of_string (String.sub line 23 (n-23)) in
+        ret := { !ret with iso_logical_block_size = i }
+      )
+      else if String.is_prefix line "Creation Date:     " then (
+        let t = parse_isoinfo_date (String.sub line 19 (n-19)) in
+        ret := { !ret with iso_volume_creation_t = t }
+      )
+      else if String.is_prefix line "Modification Date: " then (
+        let t = parse_isoinfo_date (String.sub line 19 (n-19)) in
+        ret := { !ret with iso_volume_modification_t = t }
+      )
+      else if String.is_prefix line "Expiration Date:   " then (
+        let t = parse_isoinfo_date (String.sub line 19 (n-19)) in
+        ret := { !ret with iso_volume_expiration_t = t }
+      )
+      else if String.is_prefix line "Effective Date:    " then (
+        let t = parse_isoinfo_date (String.sub line 19 (n-19)) in
+        ret := { !ret with iso_volume_effective_t = t }
+      )
+  ) lines;
+  !ret
+
+(* This is always in a fixed format:
+ * "2021033012313200"
+ * or if the field is not present, then:
+ * "0000000000000000"
+ * XXX Parse the time zone fields too.
+ *)
+let parse_xorriso_date str =
+  if str = "0000000000000000" then -1_L
+  else if String.length str <> 16 then -1_L
+  else (
+    let tm_year = int_of_string (String.sub str 0 4) in
+    let tm_mon = int_of_string (String.sub str 4 2) in
+    let tm_mday = int_of_string (String.sub str 6 2) in
+    let tm_hour = int_of_string (String.sub str 8 2) in
+    let tm_min = int_of_string (String.sub str 10 2) in
+    let tm_sec = int_of_string (String.sub str 12 2) in
+
+    (* Adjust fields. *)
+    let tm_year = tm_year - 1900 in
+    let tm_mon = tm_mon - 1 in
+
+    (* Convert to time_t *)
+    let tm = { tm_sec; tm_min; tm_hour; tm_mday; tm_mon; tm_year;
+               tm_wday = -1; tm_yday = -1; tm_isdst = false } in
+    Int64.of_float (fst (Unix.mktime tm))
+  )
+
+let do_xorriso dev =
+  (* stdio: prefix is to work around a stupidity of xorriso. *)
+  let lines = command "xorriso" ["-indev"; "stdio:" ^ dev; "-pvd_info"] in
+  let lines = String.nsplit "\n" lines in
+
+  let ret = ref default_iso in
+  List.iter (
+    fun line ->
+      let n = String.length line in
+      if String.is_prefix line "System Id    : " then
+        ret := { !ret with iso_system_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "Volume Id    : " then
+        ret := { !ret with iso_volume_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "Volume Set Id: " then
+        ret := { !ret with iso_volume_set_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "Publisher Id : " then
+        ret := { !ret with iso_publisher_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "App Id       : " then
+        ret := { !ret with iso_application_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "CopyrightFile: " then
+        ret := { !ret with iso_copyright_file_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "Abstract File: " then
+        ret := { !ret with iso_abstract_file_id = String.sub line 15 (n-15) }
+      else if String.is_prefix line "Biblio File  : " then
+        ret := { !ret with
+                 iso_bibliographic_file_id = String.sub line 15 (n-15) }
+      (* XXX The following fields don't appear to be available
+       * with xorriso:
+       * - iso_volume_space_size (only available on stderr)
+       * - iso_volume_sequence_number
+       * - iso_logical_block_size
+       *)
+      (* XXX xorriso provides a timezone for these fields, but
+       * we don't use it here.
+       *)
+      else if String.is_prefix line "Creation Time: " then (
+        let t = parse_xorriso_date (String.sub line 15 (n-15)) in
+        ret := { !ret with iso_volume_creation_t = t }
+      )
+      else if String.is_prefix line "Modif. Time  : " then (
+        let t = parse_xorriso_date (String.sub line 15 (n-15)) in
+        ret := { !ret with iso_volume_modification_t = t }
+      )
+      else if String.is_prefix line "Expir. Time  : " then (
+        let t = parse_xorriso_date (String.sub line 15 (n-15)) in
+        ret := { !ret with iso_volume_expiration_t = t }
+      )
+      else if String.is_prefix line "Eff. Time    : " then (
+        let t = parse_xorriso_date (String.sub line 15 (n-15)) in
+        ret := { !ret with iso_volume_effective_t = t }
+      )
+  ) lines;
+  !ret
+
+let isoinfo dev =
+  match get_tool () with
+  | Isoinfo -> do_isoinfo dev
+  | Xorriso -> do_xorriso dev
+
+let isoinfo_device = isoinfo
diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES
index 060be051b..e65c2f9d9 100644
--- a/docs/C_SOURCE_FILES
+++ b/docs/C_SOURCE_FILES
@@ -103,7 +103,6 @@ daemon/initrd.c
 daemon/inotify.c
 daemon/internal.c
 daemon/is.c
-daemon/isoinfo.c
 daemon/journal.c
 daemon/labels.c
 daemon/ldm.c
diff --git a/generator/actions_core.ml b/generator/actions_core.ml
index bb92ef2ca..40505d8b5 100644
--- a/generator/actions_core.ml
+++ b/generator/actions_core.ml
@@ -6878,6 +6878,7 @@ this will create the largest possible LV." };
   { defaults with
     name = "isoinfo_device"; added = (1, 17, 19);
     style = RStruct ("isodata", "isoinfo"), [String (Device, "device")], [];
+    impl = OCaml "Isoinfo.isoinfo_device";
     tests = [
       InitNone, Always, TestResult (
         [["isoinfo_device"; "/dev/sdd"]],
@@ -6904,6 +6905,7 @@ L<https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor>" };
   { defaults with
     name = "isoinfo"; added = (1, 17, 19);
     style = RStruct ("isodata", "isoinfo"), [String (Pathname, "isofile")], [];
+    impl = OCaml "Isoinfo.isoinfo";
     shortdesc = "get ISO information from primary volume descriptor of ISO file";
     longdesc = "\
 This is the same as C<guestfs_isoinfo_device> except that it
diff --git a/po/POTFILES b/po/POTFILES
index c5f4e6aa7..717579d56 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -82,7 +82,6 @@ daemon/initrd.c
 daemon/inotify.c
 daemon/internal.c
 daemon/is.c
-daemon/isoinfo.c
 daemon/journal.c
 daemon/labels.c
 daemon/ldm.c
-- 
2.29.0.rc2