/* * Copyright (c) 2007 Vreixo Formoso * * This file is part of the libisofs project; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. See COPYING file for details. */ #include "eltorito.h" #include "stream.h" #include "fsource.h" #include "filesrc.h" #include "image.h" #include "messages.h" #include "writer.h" #include #include /** * This table should be written with accuracy values at offset * 8 of boot image, when used ISOLINUX boot loader */ struct boot_info_table { uint8_t bi_pvd BP(1, 4); /* LBA of primary volume descriptor */ uint8_t bi_file BP(5, 8); /* LBA of boot file */ uint8_t bi_length BP(9, 12); /* Length of boot file */ uint8_t bi_csum BP(13, 16); /* Checksum of boot file */ uint8_t bi_reserved BP(17, 56); /* Reserved */ }; /** * Structure for each one of the four entries in a partition table on a * hard disk image. */ struct partition_desc { uint8_t boot_ind; uint8_t begin_chs[3]; uint8_t type; uint8_t end_chs[3]; uint8_t start[4]; uint8_t size[4]; }; /** * Structures for a Master Boot Record of a hard disk image. */ struct hard_disc_mbr { uint8_t code_area[440]; uint8_t opt_disk_sg[4]; uint8_t pad[2]; struct partition_desc partition[4]; uint8_t sign1; uint8_t sign2; }; /** * Sets the load segment for the initial boot image. This is only for * no emulation boot images, and is a NOP for other image types. */ void el_torito_set_load_seg(ElToritoBootImage *bootimg, short segment) { if (bootimg->type != ELTORITO_NO_EMUL) return; bootimg->load_seg = segment; } /** * Sets the number of sectors (512b) to be load at load segment during * the initial boot procedure. This is only for no emulation boot images, * and is a NOP for other image types. */ void el_torito_set_load_size(ElToritoBootImage *bootimg, short sectors) { if (bootimg->type != ELTORITO_NO_EMUL) return; bootimg->load_size = sectors; } /** * Marks the specified boot image as not bootable */ void el_torito_set_no_bootable(ElToritoBootImage *bootimg) { bootimg->bootable = 0; } /** * Specifies that this image needs to be patched. This involves the writting * of a 56 bytes boot information table at offset 8 of the boot image file. * The original boot image file won't be modified. * This is needed for isolinux boot images. */ void el_torito_patch_isolinux_image(ElToritoBootImage *bootimg) { bootimg->isolinux = 1; } static int iso_tree_add_boot_node(IsoDir *parent, const char *name, IsoBoot **boot) { IsoBoot *node; IsoNode **pos; time_t now; if (parent == NULL || name == NULL || boot == NULL) { return ISO_NULL_POINTER; } if (boot) { *boot = NULL; } /* check if the name is valid */ if (!iso_node_is_valid_name(name)) { return ISO_WRONG_ARG_VALUE; } /* find place where to insert */ pos = &(parent->children); while (*pos != NULL && strcmp((*pos)->name, name) < 0) { pos = &((*pos)->next); } if (*pos != NULL && !strcmp((*pos)->name, name)) { /* a node with same name already exists */ return ISO_NODE_NAME_NOT_UNIQUE; } node = calloc(1, sizeof(IsoBoot)); if (node == NULL) { return ISO_OUT_OF_MEM; } node->node.refcount = 1; node->node.type = LIBISO_BOOT; node->node.name = strdup(name); if (node->node.name == NULL) { free(node); return ISO_OUT_OF_MEM; } /* atributes from parent */ node->node.mode = S_IFREG | (parent->node.mode & 0444); node->node.uid = parent->node.uid; node->node.gid = parent->node.gid; node->node.hidden = parent->node.hidden; /* current time */ now = time(NULL); node->node.atime = now; node->node.ctime = now; node->node.mtime = now; /* add to dir */ node->node.parent = parent; node->node.next = *pos; *pos = (IsoNode*)node; if (boot) { *boot = node; } return ++parent->nchildren; } static int create_image(IsoImage *image, const char *image_path, enum eltorito_boot_media_type type, struct el_torito_boot_image **bootimg) { int ret; struct el_torito_boot_image *boot; int boot_media_type = 0; int load_sectors = 0; /* number of sector to load */ unsigned char partition_type = 0; IsoNode *imgfile; IsoStream *stream; ret = iso_tree_path_to_node(image, image_path, &imgfile); if (ret < 0) { return ret; } if (ret == 0) { return ISO_NODE_DOESNT_EXIST; } if (imgfile->type != LIBISO_FILE) { return ISO_BOOT_IMAGE_NOT_VALID; } stream = ((IsoFile*)imgfile)->stream; /* we need to read the image at least two times */ if (!iso_stream_is_repeatable(stream)) { return ISO_BOOT_IMAGE_NOT_VALID; } switch (type) { case ELTORITO_FLOPPY_EMUL: switch (iso_stream_get_size(stream)) { case 1200 * 1024: boot_media_type = 1; /* 1.2 meg diskette */ break; case 1440 * 1024: boot_media_type = 2; /* 1.44 meg diskette */ break; case 2880 * 1024: boot_media_type = 3; /* 2.88 meg diskette */ break; default: iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, "Invalid image size %d Kb. Must be one of 1.2, 1.44" "or 2.88 Mb", iso_stream_get_size(stream) / 1024); return ISO_BOOT_IMAGE_NOT_VALID; break; } /* it seems that for floppy emulation we need to load * a single sector (512b) */ load_sectors = 1; break; case ELTORITO_HARD_DISC_EMUL: { size_t i; struct hard_disc_mbr mbr; int used_partition; /* read the MBR on disc and get the type of the partition */ ret = iso_stream_open(stream); if (ret < 0) { iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, ret, "Can't open image file."); return ret; } ret = iso_stream_read(stream, &mbr, sizeof(mbr)); iso_stream_close(stream); if (ret != sizeof(mbr)) { iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, "Can't read MBR from image file."); return ret < 0 ? ret : ISO_FILE_READ_ERROR; } /* check valid MBR signature */ if ( mbr.sign1 != 0x55 || mbr.sign2 != 0xAA ) { iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, "Invalid MBR. Wrong signature."); return ISO_BOOT_IMAGE_NOT_VALID; } /* ensure single partition */ used_partition = -1; for (i = 0; i < 4; ++i) { if (mbr.partition[i].type != 0) { /* it's an used partition */ if (used_partition != -1) { iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, "Invalid MBR. At least 2 partitions: %d and " "%d, are being used\n", used_partition, i); return ISO_BOOT_IMAGE_NOT_VALID; } else used_partition = i; } } partition_type = mbr.partition[used_partition].type; } boot_media_type = 4; /* only load the MBR */ load_sectors = 1; break; case ELTORITO_NO_EMUL: boot_media_type = 0; break; } boot = calloc(1, sizeof(struct el_torito_boot_image)); if (boot == NULL) { return ISO_OUT_OF_MEM; } boot->image = (IsoFile*)imgfile; iso_node_ref(imgfile); /* get our ref */ boot->bootable = 1; boot->type = boot_media_type; boot->load_size = load_sectors; boot->partition_type = partition_type; if (bootimg) { *bootimg = boot; } return ISO_SUCCESS; } int iso_image_set_boot_image(IsoImage *image, const char *image_path, enum eltorito_boot_media_type type, const char *catalog_path, ElToritoBootImage **boot) { int ret; struct el_torito_boot_catalog *catalog; ElToritoBootImage *boot_image= NULL; IsoBoot *cat_node= NULL; if (image == NULL || image_path == NULL || catalog_path == NULL) { return ISO_NULL_POINTER; } if (image->bootcat != NULL) { return ISO_IMAGE_ALREADY_BOOTABLE; } /* create the node for the catalog */ { IsoDir *parent; char *catdir = NULL, *catname = NULL; catdir = strdup(catalog_path); if (catdir == NULL) { return ISO_OUT_OF_MEM; } /* get both the dir and the name */ catname = strrchr(catdir, '/'); if (catname == NULL) { free(catdir); return ISO_WRONG_ARG_VALUE; } if (catname == catdir) { /* we are apending catalog to root node */ parent = image->root; } else { IsoNode *p; catname[0] = '\0'; ret = iso_tree_path_to_node(image, catdir, &p); if (ret <= 0) { free(catdir); return ret < 0 ? ret : ISO_NODE_DOESNT_EXIST; } if (p->type != LIBISO_DIR) { free(catdir); return ISO_WRONG_ARG_VALUE; } parent = (IsoDir*)p; } catname++; ret = iso_tree_add_boot_node(parent, catname, &cat_node); free(catdir); if (ret < 0) { return ret; } } /* create the boot image */ ret = create_image(image, image_path, type, &boot_image); if (ret < 0) { goto boot_image_cleanup; } /* creates the catalog with the given image */ catalog = malloc(sizeof(struct el_torito_boot_catalog)); if (catalog == NULL) { ret = ISO_OUT_OF_MEM; goto boot_image_cleanup; } catalog->image = boot_image; catalog->node = cat_node; iso_node_ref((IsoNode*)cat_node); image->bootcat = catalog; if (boot) { *boot = boot_image; } return ISO_SUCCESS; boot_image_cleanup:; if (cat_node) { iso_node_take((IsoNode*)cat_node); iso_node_unref((IsoNode*)cat_node); } if (boot_image) { iso_node_unref((IsoNode*)boot_image->image); free(boot_image); } return ret; } /** * Get El-Torito boot image of an ISO image, if any. * * This can be useful, for example, to check if a volume read from a previous * session or an existing image is bootable. It can also be useful to get * the image and catalog tree nodes. An application would want those, for * example, to prevent the user removing it. * * Both nodes are owned by libisofs and should not be freed. You can get your * own ref with iso_node_ref(). You can can also check if the node is already * on the tree by getting its parent (note that when reading El-Torito info * from a previous image, the nodes might not be on the tree even if you haven't * removed them). Remember that you'll need to get a new ref * (with iso_node_ref()) before inserting them again to the tree, and probably * you will also need to set the name or permissions. * * @param image * The image from which to get the boot image. * @param boot * If not NULL, it will be filled with a pointer to the boot image, if * any. That object is owned by the IsoImage and should not be freed by * the user, nor dereferenced once the last reference to the IsoImage was * disposed via iso_image_unref(). * @param imgnode * When not NULL, it will be filled with the image tree node. No extra ref * is added, you can use iso_node_ref() to get one if you need it. * @param catnode * When not NULL, it will be filled with the catnode tree node. No extra * ref is added, you can use iso_node_ref() to get one if you need it. * @return * 1 on success, 0 is the image is not bootable (i.e., it has no El-Torito * image), < 0 error. */ int iso_image_get_boot_image(IsoImage *image, ElToritoBootImage **boot, IsoFile **imgnode, IsoBoot **catnode) { if (image == NULL) { return ISO_NULL_POINTER; } if (image->bootcat == NULL) { return 0; } /* ok, image is bootable */ if (boot) { *boot = image->bootcat->image; } if (imgnode) { *imgnode = image->bootcat->image->image; } if (catnode) { *catnode = image->bootcat->node; } return ISO_SUCCESS; } /** * Removes the El-Torito bootable image. * * The IsoBoot node that acts as placeholder for the catalog is also removed * for the image tree, if there. * If the image is not bootable (don't have el-torito boot image) this function * just returns. */ void iso_image_remove_boot_image(IsoImage *image) { if (image == NULL || image->bootcat == NULL) return; /* * remove catalog node from its parent * (the reference will be disposed next) */ iso_node_take((IsoNode*)image->bootcat->node); /* free boot catalog and image, including references to nodes */ el_torito_boot_catalog_free(image->bootcat); image->bootcat = NULL; } void el_torito_boot_catalog_free(struct el_torito_boot_catalog *cat) { struct el_torito_boot_image *image; if (cat == NULL) { return; } image = cat->image; iso_node_unref((IsoNode*)image->image); free(image); iso_node_unref((IsoNode*)cat->node); free(cat); } /** * Stream that generates the contents of a El-Torito catalog. */ struct catalog_stream { Ecma119Image *target; uint8_t buffer[BLOCK_SIZE]; int offset; /* -1 if stream is not openned */ }; static void write_validation_entry(uint8_t *buf) { size_t i; int checksum; struct el_torito_validation_entry *ve = (struct el_torito_validation_entry*)buf; ve->header_id[0] = 1; ve->platform_id[0] = 0; /* 0: 80x86, 1: PowerPC, 2: Mac */ ve->key_byte1[0] = 0x55; ve->key_byte2[0] = 0xAA; /* calculate the checksum, to ensure sum of all words is 0 */ checksum = 0; for (i = 0; i < sizeof(struct el_torito_validation_entry); i += 2) { checksum -= (int16_t) ((buf[i+1] << 8) | buf[i]); } iso_lsb(ve->checksum, checksum, 2); } /** * Write one section entry. * Currently this is used only for default image (the only supported just now) */ static void write_section_entry(uint8_t *buf, Ecma119Image *t) { struct el_torito_boot_image *img; struct el_torito_section_entry *se = (struct el_torito_section_entry*)buf; img = t->catalog->image; se->boot_indicator[0] = img->bootable ? 0x88 : 0x00; se->boot_media_type[0] = img->type; iso_lsb(se->load_seg, img->load_seg, 2); se->system_type[0] = img->partition_type; iso_lsb(se->sec_count, img->load_size, 2); iso_lsb(se->block, t->bootimg->sections[0].block, 4); } static int catalog_open(IsoStream *stream) { struct catalog_stream *data; if (stream == NULL) { return ISO_NULL_POINTER; } data = stream->data; if (data->offset != -1) { return ISO_FILE_ALREADY_OPENED; } memset(data->buffer, 0, BLOCK_SIZE); /* fill the buffer with the catalog contents */ write_validation_entry(data->buffer); /* write default entry */ write_section_entry(data->buffer + 32, data->target); data->offset = 0; return ISO_SUCCESS; } static int catalog_close(IsoStream *stream) { struct catalog_stream *data; if (stream == NULL) { return ISO_NULL_POINTER; } data = stream->data; if (data->offset == -1) { return ISO_FILE_NOT_OPENED; } data->offset = -1; return ISO_SUCCESS; } static off_t catalog_get_size(IsoStream *stream) { return BLOCK_SIZE; } static int catalog_read(IsoStream *stream, void *buf, size_t count) { size_t len; struct catalog_stream *data; if (stream == NULL || buf == NULL) { return ISO_NULL_POINTER; } if (count == 0) { return ISO_WRONG_ARG_VALUE; } data = stream->data; if (data->offset == -1) { return ISO_FILE_NOT_OPENED; } len = MIN(count, BLOCK_SIZE - data->offset); memcpy(buf, data->buffer + data->offset, len); return len; } static int catalog_is_repeatable(IsoStream *stream) { return 1; } /** * fs_id will be the id reserved for El-Torito * dev_id will be 0 for catalog, 1 for boot image (if needed) * we leave ino_id for future use when we support multiple boot images */ static void catalog_get_id(IsoStream *stream, unsigned int *fs_id, dev_t *dev_id, ino_t *ino_id) { *fs_id = ISO_ELTORITO_FS_ID; *dev_id = 0; *ino_id = 0; } static void catalog_free(IsoStream *stream) { free(stream->data); } IsoStreamIface catalog_stream_class = { 0, "boot", catalog_open, catalog_close, catalog_get_size, catalog_read, catalog_is_repeatable, catalog_get_id, catalog_free }; /** * Create an IsoStream for writing El-Torito catalog for a given target. */ static int catalog_stream_new(Ecma119Image *target, IsoStream **stream) { IsoStream *str; struct catalog_stream *data; if (target == NULL || stream == NULL || target->catalog == NULL) { return ISO_NULL_POINTER; } str = malloc(sizeof(IsoStream)); if (str == NULL) { return ISO_OUT_OF_MEM; } data = malloc(sizeof(struct catalog_stream)); if (str == NULL) { free(str); return ISO_OUT_OF_MEM; } /* fill data */ data->target = target; data->offset = -1; str->refcount = 1; str->data = data; str->class = &catalog_stream_class; *stream = str; return ISO_SUCCESS; } int el_torito_catalog_file_src_create(Ecma119Image *target, IsoFileSrc **src) { int ret; IsoFileSrc *file; IsoStream *stream; if (target == NULL || src == NULL || target->catalog == NULL) { return ISO_OUT_OF_MEM; } if (target->cat != NULL) { /* catalog file src already created */ *src = target->cat; return ISO_SUCCESS; } file = malloc(sizeof(IsoFileSrc)); if (file == NULL) { return ISO_OUT_OF_MEM; } ret = catalog_stream_new(target, &stream); if (ret < 0) { free(file); return ret; } /* fill fields */ file->prev_img = 0; /* TODO allow copy of old img catalog???? */ file->nsections = 0; /* to be filled later */ file->sections = NULL; file->sort_weight = 1000; /* slightly high */ file->stream = stream; ret = iso_file_src_add(target, file, src); if (ret <= 0) { iso_stream_unref(stream); free(file); } else { target->cat = *src; } return ret; } /******************* EL-TORITO WRITER *******************************/ static int eltorito_writer_compute_data_blocks(IsoImageWriter *writer) { /* nothing to do, the files are written by the file writer */ return ISO_SUCCESS; } /** * Write the Boot Record Volume Descriptor (ECMA-119, 8.2) */ static int eltorito_writer_write_vol_desc(IsoImageWriter *writer) { Ecma119Image *t; struct el_torito_boot_catalog *cat; struct ecma119_boot_rec_vol_desc vol; if (writer == NULL) { return ISO_NULL_POINTER; } t = writer->target; cat = t->catalog; iso_msg_debug(t->image->id, "Write El-Torito boot record"); memset(&vol, 0, sizeof(struct ecma119_boot_rec_vol_desc)); vol.vol_desc_type[0] = 0; memcpy(vol.std_identifier, "CD001", 5); vol.vol_desc_version[0] = 1; memcpy(vol.boot_sys_id, "EL TORITO SPECIFICATION", 23); iso_lsb(vol.boot_catalog, t->cat->sections[0].block, 4); return iso_write(t, &vol, sizeof(struct ecma119_boot_rec_vol_desc)); } /** * Patch an isolinux boot image. * * @return * 1 on success, 0 error (but continue), < 0 error */ static int patch_boot_image(uint8_t *buf, Ecma119Image *t, size_t imgsize) { struct boot_info_table *info; uint32_t checksum; size_t offset; if (imgsize < 64) { return iso_msg_submit(t->image->id, ISO_ISOLINUX_CANT_PATCH, 0, "Isolinux image too small. We won't patch it."); } /* compute checksum, as the the sum of all 32 bit words in boot image * from offset 64 */ checksum = 0; offset = (size_t) 64; while (offset <= imgsize - 4) { checksum += iso_read_lsb(buf + offset, 4); offset += 4; } if (offset != imgsize) { /* file length not multiple of 4 */ return iso_msg_submit(t->image->id, ISO_ISOLINUX_CANT_PATCH, 0, "Unexpected isolinux image length. Patch might not work."); } /* patch boot info table */ info = (struct boot_info_table*)(buf + 8); /*memset(info, 0, sizeof(struct boot_info_table));*/ iso_lsb(info->bi_pvd, t->ms_block + 16, 4); iso_lsb(info->bi_file, t->bootimg->sections[0].block, 4); iso_lsb(info->bi_length, imgsize, 4); iso_lsb(info->bi_csum, checksum, 4); return ISO_SUCCESS; } static int eltorito_writer_write_data(IsoImageWriter *writer) { /* * We have nothing to write, but if we need to patch an isolinux image, * this is a good place to do so. */ Ecma119Image *t; int ret; if (writer == NULL) { return ISO_NULL_POINTER; } t = writer->target; if (t->catalog->image->isolinux) { /* we need to patch the image */ size_t size; uint8_t *buf; IsoStream *new = NULL; IsoStream *original = t->bootimg->stream; size = (size_t) iso_stream_get_size(original); buf = malloc(size); if (buf == NULL) { return ISO_OUT_OF_MEM; } ret = iso_stream_open(original); if (ret < 0) { return ret; } ret = iso_stream_read(original, buf, size); iso_stream_close(original); if (ret != size) { return (ret < 0) ? ret : ISO_FILE_READ_ERROR; } /* ok, patch the read buffer */ ret = patch_boot_image(buf, t, size); if (ret < 0) { return ret; } /* replace the original stream with a memory stream that reads from * the patched buffer */ ret = iso_memory_stream_new(buf, size, &new); if (ret < 0) { return ret; } t->bootimg->stream = new; iso_stream_unref(original); } return ISO_SUCCESS; } static int eltorito_writer_free_data(IsoImageWriter *writer) { /* nothing to do */ return ISO_SUCCESS; } int eltorito_writer_create(Ecma119Image *target) { int ret; IsoImageWriter *writer; IsoFile *bootimg; IsoFileSrc *src; writer = malloc(sizeof(IsoImageWriter)); if (writer == NULL) { return ISO_OUT_OF_MEM; } writer->compute_data_blocks = eltorito_writer_compute_data_blocks; writer->write_vol_desc = eltorito_writer_write_vol_desc; writer->write_data = eltorito_writer_write_data; writer->free_data = eltorito_writer_free_data; writer->data = NULL; writer->target = target; /* add this writer to image */ target->writers[target->nwriters++] = writer; /* * get catalog and image file sources. * Note that the catalog may be already added, when creating the low * level ECMA-119 tree. */ if (target->cat == NULL) { ret = el_torito_catalog_file_src_create(target, &src); if (ret < 0) { return ret; } } bootimg = target->catalog->image->image; ret = iso_file_src_create(target, bootimg, &src); if (ret < 0) { return ret; } target->bootimg = src; /* if we have selected to patch the image, it needs to be copied always */ if (target->catalog->image->isolinux) { src->prev_img = 0; } /* we need the bootable volume descriptor */ target->curblock++; return ISO_SUCCESS; }