[Libosinfo] [libosinfo PATCH 4/4] media: Check whether PPC ISOs are bootable

Fabiano Fidêncio fidencio at redhat.com
Fri Dec 14 09:21:04 UTC 2018


PPC ISOs do not have the "El Torito" extension that describes whether
the media is bootable or not. However, they have a "bootinfo.txt" file
placed under "ppc" directory in order to specify the media is bootable.

So, let's add a few more checks looking for "/ppc/bootinfo.txt" in case
the El Torito header is not found.

The whole implementation has been based on the following sources:
- The ISO 9660 (ECMA-119) specification:
  http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf
- The ISO 9660 osdev wiki page:
  https://wiki.osdev.org/ISO_9660
- IBM's developer article:
  https://www.ibm.com/developerworks/linux/library/l-detecting-bootable-ibm-power-server-iso-images/index.html

https://gitlab.com/libosinfo/libosinfo/issues/8

Signed-off-by: Fabiano Fidêncio <fidencio at redhat.com>
---
 osinfo/osinfo_media.c | 404 +++++++++++++++++++++++++++++++++++++++++-
 osinfo/osinfo_media.h |   8 +-
 2 files changed, 406 insertions(+), 6 deletions(-)

diff --git a/osinfo/osinfo_media.c b/osinfo/osinfo_media.c
index de8125c..fa6a3fa 100644
--- a/osinfo/osinfo_media.c
+++ b/osinfo/osinfo_media.c
@@ -36,9 +36,51 @@
 #define MAX_SYSTEM 32
 #define MAX_PUBLISHER 128
 #define MAX_APPLICATION 128
+#define MAX_DIRECTORYRECORD 255
 
 #define PVD_OFFSET 0x00008000
 #define BOOTABLE_TAG "EL TORITO SPECIFICATION"
+#define PPC_DIRECTORY "ppc"
+#define PPC_BOOTABLE_FILE "bootinfo.txt"
+
+enum {
+    DIRECTORY_RECORD_FLAG_EXISTENCE       = 1 << 0,
+    DIRECTORY_RECORD_FLAG_DIRECTORY       = 1 << 1,
+    DIRECTORY_RECORD_FLAG_ASSOCIATED_FILE = 1 << 2,
+    DIRECTORY_RECORD_FLAG_RECORD          = 1 << 3,
+    DIRECTORY_RECORD_FLAG_PROTECTION      = 1 << 4,
+    DIRECTORY_RECORD_FLAG_RESERVED5       = 1 << 5,
+    DIRECTORY_RECORD_FLAG_RESERVED6       = 1 << 6,
+    DIRECTORY_RECORD_FLAG_MULTIEXTENT     = 1 << 7
+};
+
+typedef struct _DirectoryRecord DirectoryRecord;
+
+struct __attribute__((packed)) _DirectoryRecord {
+    guint8 length;
+    guint8 ignored;
+    guint32 extent_location[2];
+    guint32 extent_size[2];
+    guint8 ignored2[7];
+    guint8 flags;
+    guint8 ignored3[6];
+    guint8 filename_length;
+    gchar filename[1];
+};
+
+char dummy_dr[sizeof(struct _DirectoryRecord) == 34 ? 1 : -1];
+
+typedef struct _PathTable PathTable;
+
+struct __attribute__((packed)) _PathTable {
+    guint8 name_length;
+    guint8 ignored;
+    guint32 extent_location;
+    guint16 parent;
+    gchar name[1];
+};
+
+char dummy_pt[sizeof(struct _PathTable) == 9 ? 1 : -1];
 
 typedef struct _PrimaryVolumeDescriptor PrimaryVolumeDescriptor;
 
@@ -50,11 +92,13 @@ struct _PrimaryVolumeDescriptor {
     guint32 volume_space_size[2];
     guint8 ignored3[40];
     guint16 logical_blk_size[2];
-    guint8 ignored4[186];
+    guint32 path_table_size[2];      /* Path Table size */
+    guint32 path_table_location[4];  /* Path Table location */
+    guint8 ignored5[162];
     gchar  publisher[MAX_PUBLISHER]; /* Publisher ID */
-    guint8 ignored5[128];
+    guint8 ignored6[128];
     gchar  application[MAX_APPLICATION]; /* Application ID */
-    guint8 ignored6[1346];
+    guint8 ignored7[1346];
 };
 
 /* the PrimaryVolumeDescriptor struct must exactly 2048 bytes long
@@ -79,6 +123,12 @@ struct _CreateFromLocationAsyncData {
 
     PrimaryVolumeDescriptor pvd;
     SupplementaryVolumeDescriptor svd;
+    guint8 *pt;
+    guint8 ppc_dr[MAX_DIRECTORYRECORD];
+    guint8 file_dr[MAX_DIRECTORYRECORD];
+
+    gsize ppc_dr_offset;
+    gsize max_dr_offset;
 
     gsize offset;
     gsize length;
@@ -90,6 +140,8 @@ static void create_from_location_async_data_free
    g_object_unref(data->file);
    g_object_unref(data->res);
 
+   g_free(data->pt);
+
    g_slice_free(CreateFromLocationAsyncData, data);
 }
 
@@ -747,6 +799,308 @@ create_from_location_async_data(CreateFromLocationAsyncData *data)
     return media;
 }
 
+static void on_ppc_dr_files_read(GObject *source,
+                                 GAsyncResult *res,
+                                 gpointer user_data)
+{
+    OsinfoMedia *media = NULL;
+    GInputStream *stream = G_INPUT_STREAM(source);
+    CreateFromLocationAsyncData *data;
+    DirectoryRecord *file_dr;
+    gssize ret;
+    guint8 padding;
+    GError *error = NULL;
+
+    data = (CreateFromLocationAsyncData *)user_data;
+
+    ret = g_input_stream_read_finish(stream, res, &error);
+    if (ret < 0) {
+        g_prefix_error(&error,
+                       _("Failed to read directory record for files under \"ppc\" directory: "));
+        goto EXIT;
+    }
+
+    if (ret == 0) {
+        g_set_error(&error,
+                    OSINFO_MEDIA_ERROR,
+                    OSINFO_MEDIA_ERROR_NO_PPC_DIRECTORY_FILES_RECORD,
+                    _("Directory record for files under \"ppc\" directory was truncated"));
+        goto EXIT;
+    }
+
+    data->offset += ret;
+    if (data->offset < data->length) {
+        g_input_stream_read_async(stream,
+                                  ((gchar *)&data->file_dr + data->offset),
+                                  data->length - data->offset,
+                                  g_task_get_priority(data->res),
+                                  g_task_get_cancellable(data->res),
+                                  on_ppc_dr_files_read,
+                                  data);
+        return;
+    }
+
+    /*
+     * If we reach this point, the "bootinfo.txt" file was not found.
+     */
+    if (data->ppc_dr_offset >= data->max_dr_offset) {
+        set_non_bootable_media_error(&error);
+        goto EXIT;
+    }
+
+    /*
+     * If the file is *not* a directory and its name is "bootinfo.txt",
+     * the media is bootable!
+     */
+    file_dr = (DirectoryRecord *)&data->file_dr;
+    if ((file_dr->flags & DIRECTORY_RECORD_FLAG_DIRECTORY) == 0 &&
+        strncmp(PPC_BOOTABLE_FILE, file_dr->filename, sizeof(PPC_BOOTABLE_FILE)) == 0) {
+        media = create_from_location_async_data(data);
+        goto EXIT;
+    }
+
+    padding = 0;
+    if (file_dr->filename_length % 2 == 0)
+        padding++;
+
+    data->ppc_dr_offset += file_dr->length + padding;
+
+    if (!g_seekable_seek(G_SEEKABLE(stream),
+                         data->ppc_dr_offset,
+                         G_SEEK_SET,
+                         g_task_get_cancellable(data->res),
+                         &error))
+        goto EXIT;
+
+    data->offset = 0;
+    data->length = MAX_DIRECTORYRECORD;
+    g_input_stream_read_async(stream,
+                              &data->file_dr,
+                              data->length,
+                              g_task_get_priority(data->res),
+                              g_task_get_cancellable(data->res),
+                              on_ppc_dr_files_read,
+                              data);
+    return;
+
+EXIT:
+    if (error != NULL)
+        g_task_return_error(data->res, error);
+    else
+        g_task_return_pointer(data->res, media, g_object_unref);
+
+    g_object_unref(stream);
+    create_from_location_async_data_free(data);
+}
+
+static void on_ppc_dr_read(GObject *source,
+                           GAsyncResult *res,
+                           gpointer user_data)
+{
+    GInputStream *stream = G_INPUT_STREAM(source);
+    CreateFromLocationAsyncData *data;
+    DirectoryRecord *ppc_dr;
+    gssize ret;
+    gssize ppc_dr_sector_size;
+    GError *error = NULL;
+    guint8 index = (G_BYTE_ORDER == G_LITTLE_ENDIAN) ? 0 : 1;
+
+    data = (CreateFromLocationAsyncData *)user_data;
+
+    ret = g_input_stream_read_finish(stream, res, &error);
+    if (ret < 0) {
+        g_prefix_error(&error,
+                       _("Failed to read directory record for \"ppc\" directory: "));
+        goto ON_ERROR;
+    }
+
+    if (ret == 0) {
+        g_set_error(&error,
+                    OSINFO_MEDIA_ERROR,
+                    OSINFO_MEDIA_ERROR_NO_PPC_DIRECTORY_RECORD,
+                    _("Directory record for \"ppc\" directory was truncated"));
+        goto ON_ERROR;
+    }
+
+    data->offset += ret;
+    if (data->offset < data->length) {
+        g_input_stream_read_async(stream,
+                                  ((gchar *)&data->ppc_dr + data->offset),
+                                  data->length - data->offset,
+                                  g_task_get_priority(data->res),
+                                  g_task_get_cancellable(data->res),
+                                  on_ppc_dr_read,
+                                  data);
+        return;
+    }
+
+    /*
+     * Once we have read the "ppc" directory record info, we have to determine
+     * in which sector it is in order to read the info about the files under
+     * this directory.
+     */
+    ppc_dr = (DirectoryRecord *)&data->ppc_dr;
+    ppc_dr_sector_size = ppc_dr->extent_size[index] / data->pvd.logical_blk_size[index];
+
+    /*
+     * As the directory record info extent will always be in the beginning of
+     * a sector, round it up if needed.
+     */
+    if (ppc_dr->extent_size[index] % data->pvd.logical_blk_size[index] != 0)
+        ppc_dr_sector_size++;
+
+    /*
+     * Now let's seek the stream to the extent of the ppc directory record and
+     * also set the maximum offset limit that we can use during our search for
+     * the "bootinfo.txt" file under "ppc" directory.
+     */
+    data->ppc_dr_offset = ppc_dr->extent_location[index] * data->pvd.logical_blk_size[index];
+    data->max_dr_offset = data->ppc_dr_offset + (ppc_dr_sector_size *  data->pvd.logical_blk_size[index]);
+
+    if (!g_seekable_seek(G_SEEKABLE(stream),
+                         data->ppc_dr_offset,
+                         G_SEEK_SET,
+                         g_task_get_cancellable(data->res),
+                         &error)) {
+        goto ON_ERROR;
+    }
+
+    data->offset = 0;
+    data->length = MAX_DIRECTORYRECORD;
+
+    g_input_stream_read_async(stream,
+                              &data->file_dr,
+                              data->length,
+                              g_task_get_priority(data->res),
+                              g_task_get_cancellable(data->res),
+                              on_ppc_dr_files_read,
+                              data);
+    return;
+
+ON_ERROR:
+    g_object_unref(stream);
+    g_task_return_error(data->res, error);
+    create_from_location_async_data_free(data);
+}
+
+static void on_pt_read(GObject *source,
+                       GAsyncResult *res,
+                       gpointer user_data)
+{
+    GInputStream *stream = G_INPUT_STREAM(source);
+    CreateFromLocationAsyncData *data;
+    PathTable *pt;
+    gssize ret;
+    gsize offset;
+    guint8 index = (G_BYTE_ORDER == G_LITTLE_ENDIAN) ? 0 : 1;
+    GError *error = NULL;
+
+    data = (CreateFromLocationAsyncData *)user_data;
+
+    ret = g_input_stream_read_finish(stream, res, &error);
+    if (ret < 0) {
+        g_printerr("Failed to read the path table\n");
+        g_prefix_error(&error,
+                       _("Failed to read path table: "));
+        goto ON_ERROR;
+    }
+
+    if (ret == 0) {
+        g_printerr("Path table was truncated\n");
+        g_set_error(&error,
+                    OSINFO_MEDIA_ERROR,
+                    OSINFO_MEDIA_ERROR_NO_PT,
+                    _("Path table was truncated"));
+        goto ON_ERROR;
+    }
+
+    data->offset += ret;
+    if (data->offset < data->length) {
+        g_input_stream_read_async(stream,
+                                  ((gchar *)data->pt + data->offset),
+                                  data->length - data->offset,
+                                  g_task_get_priority(data->res),
+                                  g_task_get_cancellable(data->res),
+                                  on_pt_read,
+                                  data);
+        return;
+    }
+
+    /*
+     * Once the path table is loaded, let's iterate over it searching for the
+     * "ppc" directory.
+     */
+    offset = 0;
+    do {
+        pt = (PathTable *)&data->pt[offset];
+        if (pt->parent == 1) {
+            if (strncmp(PPC_DIRECTORY, pt->name, sizeof(PPC_DIRECTORY)) == 0)
+                break;
+        }
+
+        /*
+         * In order to understan better how we get to the next entry, please,
+         * take a look at the PathTable struct.
+         *
+         * It's basically set as a 9 bytes struct, with the last element being
+         * the name of of the entry (a char name[0] that will point to the
+         * name in the memory).
+         *
+         * In order to calculate the next entry we do:
+         * sizeof(PathTable) - 1, as we don't know the length of the entry name
+         * + pt->name_length, in order to add the length of the entry name,
+         * + (pt->name_length % 2), which is just a pading that has to be added
+         *                          in case pt->name_length is odd ... as,
+         *                          according to the specs, each path table
+         *                          entry will start on an even byte number.
+         */
+        offset += sizeof(PathTable) - 1 + pt->name_length + (pt->name_length % 2);
+    } while (offset < data->length);
+
+    /*
+     * In case the "ppc" folder is not found in the root directory of the ISO,
+     * the ISO is not bootable.
+     */
+    if (offset >= data->length) {
+        set_non_bootable_media_error(&error);
+        goto ON_ERROR;
+    }
+
+    /*
+     * In case the "ppc" directory is found, let's just seek our stream to its
+     * location.
+     */
+    if (!g_seekable_seek(G_SEEKABLE(stream),
+                         data->pvd.logical_blk_size[index] * pt->extent_location,
+                         G_SEEK_SET,
+                         g_task_get_cancellable(data->res),
+                         &error)) {
+        goto ON_ERROR;
+    }
+
+    /*
+     * And, finally, read it.
+     *
+     * As the length of the directory record is represented by an uint8_t, we
+     * know its maximum size is up to 255 bytes.
+     */
+    data->offset = 0;
+    data->length = MAX_DIRECTORYRECORD;
+    g_input_stream_read_async(stream,
+                              &data->ppc_dr,
+                              data->length,
+                              g_task_get_priority(data->res),
+                              g_task_get_cancellable(data->res),
+                              on_ppc_dr_read,
+                              data);
+    return;
+
+ON_ERROR:
+    g_object_unref(stream);
+    g_task_return_error(data->res, error);
+    create_from_location_async_data_free(data);
+}
+
 static void on_svd_read(GObject *source,
                          GAsyncResult *res,
                          gpointer user_data)
@@ -792,9 +1146,49 @@ static void on_svd_read(GObject *source,
     g_strchomp(data->svd.system);
 
     if (strncmp(BOOTABLE_TAG, data->svd.system, sizeof(BOOTABLE_TAG)) != 0) {
-        set_non_bootable_media_error(&error);
+        guint8 index = (G_BYTE_ORDER == G_LITTLE_ENDIAN) ? 0 : 1;
+        /*
+         * In case we reached this point, there are basically 2 alternatives:
+         * - the media is a PPC media and we should check for the existence of
+         *   "/ppc/bootiso.txt" file
+         * - the media is not bootable.
+         *
+         * Let's check for the existence of the "/ppc/bootiso.txt" file and,
+         * only after that, return whether the media is bootable or not.
+         */
+
+        /*
+         * In order to check for the existence of "/ppc/bootiso.txt" file,
+         * let's load into the memory the media's path table.
+         *
+         * The first step to do so, is seek the stream to the beginning of
+         * the path table.
+         */
+
+        if (!g_seekable_seek(G_SEEKABLE(stream),
+                             data->pvd.logical_blk_size[index] * data->pvd.path_table_location[index * 2],
+                             G_SEEK_SET,
+                             g_task_get_cancellable(data->res),
+                             &error)) {
+            goto EXIT;
+        }
 
-        goto EXIT;
+        data->offset = 0;
+        data->length = data->pvd.path_table_size[index];
+
+        data->pt = g_malloc0(data->length);
+
+        /*
+         * And then load its (path table) content to the memory.
+         */
+        g_input_stream_read_async(stream,
+                                  data->pt,
+                                  data->length,
+                                  g_task_get_priority(data->res),
+                                  g_task_get_cancellable(data->res),
+                                  on_pt_read,
+                                  data);
+        return;
     }
 
     media = create_from_location_async_data(data);
diff --git a/osinfo/osinfo_media.h b/osinfo/osinfo_media.h
index 6df7b60..7fc6192 100644
--- a/osinfo/osinfo_media.h
+++ b/osinfo/osinfo_media.h
@@ -43,6 +43,9 @@ osinfo_media_error_quark (void) G_GNUC_CONST;
  * @OSINFO_MEDIA_ERROR_NOT_BOOTABLE: Install media not bootable.
  * @OSINFO_MEDIA_ERROR_NO_PVD: No Primary volume descriptor.
  * @OSINFO_MEDIA_ERROR_NO_SVD: No supplementary volume descriptor.
+ * @OSINFO_MEDIA_ERROR_NO_PT: No Path Table.
+ * @OSINFO_MEDIA_ERROR_NO_PPC_DIRECTORY_RECORD: No "ppc" directory record.
+ * @OSINFO_MEDIA_ERROR_NO_PPC_DIRECTORY_FILES_RECORD: No directory record for files under the "ppc" directory.
  *
  * #GError codes used for errors in the #OSINFO_MEDIA_ERROR domain, during
  * reading of data from install media location.
@@ -52,7 +55,10 @@ typedef enum {
     OSINFO_MEDIA_ERROR_NO_PVD,
     OSINFO_MEDIA_ERROR_NO_SVD,
     OSINFO_MEDIA_ERROR_INSUFFICIENT_METADATA,
-    OSINFO_MEDIA_ERROR_NOT_BOOTABLE
+    OSINFO_MEDIA_ERROR_NOT_BOOTABLE,
+    OSINFO_MEDIA_ERROR_NO_PT,
+    OSINFO_MEDIA_ERROR_NO_PPC_DIRECTORY_RECORD,
+    OSINFO_MEDIA_ERROR_NO_PPC_DIRECTORY_FILES_RECORD
 } OsinfoMediaError;
 
 /*
-- 
2.19.1




More information about the Libosinfo mailing list