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

Daniel P. Berrangé berrange at redhat.com
Fri Dec 14 09:59:59 UTC 2018


On Fri, Dec 14, 2018 at 10:21:04AM +0100, Fabiano Fidêncio wrote:
> 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];

G_STATIC_ASSERT(sizeof(struct _DirectoryRecord) == 34);

> +
> +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];

Again G_STATIC_ASSERT

>  
>  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];

This renumbering isn't needed - it should still be called
ignored4

>      gchar  publisher[MAX_PUBLISHER]; /* Publisher ID */
> -    guint8 ignored5[128];
> +    guint8 ignored6[128];
>      gchar  application[MAX_APPLICATION]; /* Application ID */
> -    guint8 ignored6[1346];
> +    guint8 ignored7[1346];

This isn't then needed

> @@ -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)

Lets call it  on_iso_directory_record_entry_read

> +{
> +    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;
> +    }

Please just use 'goto cleanup' as that's the normal naming
convention for this.

> +
> +    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)

Lets call it  on_iso_directory_record_read

> +{
> +    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;

s/ON_ERROR/error/ to follow normal naming

> +    }
> +
> +    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)

Can you name this   on_iso_path_table_read() to make it clearer

> +{
> +    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;

s/ON_ERROR/error/ to follow normal naming

> +    }
> +
> +    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);
> +}


It occurs to me that this code is almost capable of finding arbitrary
files in the ISO image. So I think it would be nice to structure it
in a way which is generically usable.

ie, make CreateFromLocationAsyncData  contain the name of the directory
and file we're looking for, instead of harcoding ppc bootinfo.

> +
>  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);

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|




More information about the Libosinfo mailing list