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