From efb8a766cac4ba8e413594946136bf91e176bb8c Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" 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 - -#include -#include -#include -#include -#include -#include -#include - -#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" }; { 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 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