libisofs/libisofs/eltorito.c
Vreixo Formoso 89b0e9da68 Patch isolinux image before image writing.
Currently isolinux images are patching on-the-fly during image writing, and
that can be a problem on multisession images, as we may be reading the
old image after begining the burning of the new session. That is not supported
in several media and lead to burning failure. Fixed by caching the patched
image on memory.
2008-09-29 22:33:26 +02:00

940 lines
24 KiB
C

/*
* 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 <stdlib.h>
#include <string.h>
/**
* 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 != 0)
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 != 0)
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;
}
/* TODO getter for boot image properties should be exposed
* useful when reading discs */
int el_torito_get_boot_media_type(const ElToritoBootImage *bootimg)
{
if (bootimg) {
switch (bootimg->type) {
case 1:
case 2:
case 3:
return ELTORITO_FLOPPY_EMUL;
break;
case 4:
return ELTORITO_HARD_DISC_EMUL;
break;
case 0:
return ELTORITO_NO_EMUL;
break;
default:
/* should never happen */
return ISO_ASSERT_FAILURE;
break;
}
} else {
return ISO_WRONG_ARG_VALUE;
}
}
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 = 1;
file->sections = calloc(1, sizeof(struct iso_file_section));
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 *******************************/
/**
* 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
* empty space in isofs is padded with zero;
* assume same for last dword
*/
checksum += iso_read_lsb(buf + offset, imgsize - offset);
}
/* 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_compute_data_blocks(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;
}
/**
* 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));
}
static
int eltorito_writer_write_data(IsoImageWriter *writer)
{
/* nothing to do, the files are written by the file writer */
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;
}