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@xxxxxxxxxx> > --- > 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 :| _______________________________________________ Libosinfo mailing list Libosinfo@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libosinfo