[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