[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