From fc48c2393265c7f04b4a67eaf9b2271d9a1088a6 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Mon, 27 Aug 2007 22:51:48 +0000 Subject: [PATCH] Done major changes to libisofs, including multisession and reading support --- libisofs/trunk/Makefile.am | 13 +- libisofs/trunk/TODO | 25 ++- libisofs/trunk/libisofs/ecma119.c | 72 ++++++-- libisofs/trunk/libisofs/ecma119_read.c | 213 ++++++++++++++++------ libisofs/trunk/libisofs/ecma119_read.h | 24 ++- libisofs/trunk/libisofs/ecma119_read_rr.c | 25 +-- libisofs/trunk/libisofs/ecma119_tree.h | 2 +- libisofs/trunk/libisofs/eltorito.c | 8 +- libisofs/trunk/libisofs/eltorito.h | 6 + libisofs/trunk/libisofs/joliet.c | 8 +- libisofs/trunk/libisofs/joliet.h | 3 + libisofs/trunk/libisofs/libisofs.h | 169 +++++++++++++++-- libisofs/trunk/libisofs/tree.c | 10 +- libisofs/trunk/libisofs/util.c | 53 +++++- libisofs/trunk/libisofs/util.h | 8 +- libisofs/trunk/test/iso.c | 9 +- libisofs/trunk/test/iso_add.c | 12 ++ libisofs/trunk/test/iso_ms.c | 12 ++ libisofs/trunk/test/iso_read.c | 18 ++ 19 files changed, 573 insertions(+), 117 deletions(-) diff --git a/libisofs/trunk/Makefile.am b/libisofs/trunk/Makefile.am index c5465893..d3fab191 100644 --- a/libisofs/trunk/Makefile.am +++ b/libisofs/trunk/Makefile.am @@ -38,7 +38,11 @@ libisofs_libisofs_la_SOURCES = \ libisofs/ecma119_read.h \ libisofs/ecma119_read.c \ libisofs/ecma119_read_rr.h \ - libisofs/ecma119_read_rr.c + libisofs/ecma119_read_rr.c \ + libisofs/libdax_msgs.h \ + libisofs/libdax_msgs.c \ + libisofs/messages.h \ + libisofs/messages.c libinclude_HEADERS = \ libisofs/libisofs.h @@ -50,7 +54,8 @@ noinst_PROGRAMS = \ test/iso \ test/isoread \ test/isoms \ - test/isoadd + test/isoadd \ + test/isogrow test_iso_CPPFLAGS = -Ilibisofs test_iso_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) @@ -68,6 +73,10 @@ test_isoadd_CPPFLAGS = -Ilibisofs test_isoadd_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) test_isoadd_SOURCES = test/iso_add.c +test_isogrow_CPPFLAGS = -Ilibisofs +test_isogrow_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +test_isogrow_SOURCES = test/iso_grow.c + ## Build unit test check_PROGRAMS = \ diff --git a/libisofs/trunk/TODO b/libisofs/trunk/TODO index e2d00f26..36b1e44a 100644 --- a/libisofs/trunk/TODO +++ b/libisofs/trunk/TODO @@ -7,10 +7,12 @@ FEATURES CD reading [ok] plain iso [ok] Rock Ridge - Joliet + [ok] Joliet Merge RR and Joliet trees - User options to customize reading - Multisession + [ok] User options to customize reading + Read El-Torito info + [ok] Multisession + [ok] DVD+RW image growing UDF [ok] ISO relaxed contraints ISO 9660:1998 @@ -29,11 +31,20 @@ IMPLEMENTATION ============== a way to return NULL sources meaning a failure!! - Error message queue + [ok] Error message queue Better charset support - default input charset to locale one, no always UTF-8 + [ok] default input charset to locale one, no always UTF-8 add charset management on image reading - use iso-8859-1 instead of UTF-8 dor RR? - Improve date handling + use iso-8859-1 instead of UTF-8 on RR? + [ok] Improve date handling + for DVD+RW, the VD to be written at the beginning of disc must be + returned as 32KB block + +BUGS +==== + + Joliet names need ";1" at the end + RR Continuation Areas can't be in Directory Record block + [ok] Fix mangle names when iso relaxed constraints \ No newline at end of file diff --git a/libisofs/trunk/libisofs/ecma119.c b/libisofs/trunk/libisofs/ecma119.c index b20c446f..6ee38861 100644 --- a/libisofs/trunk/libisofs/ecma119.c +++ b/libisofs/trunk/libisofs/ecma119.c @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include "ecma119.h" #include "ecma119_tree.h" @@ -20,6 +23,7 @@ #include "libisofs.h" #include "libburn/libburn.h" #include "eltorito.h" +#include "messages.h" /* burn-source compatible stuff */ static int @@ -287,7 +291,7 @@ calc_file_pos(struct ecma119_write_target *t, */ static struct ecma119_write_target* ecma119_target_new(struct iso_volset *volset, - const struct ecma119_source_opts *opts) + struct ecma119_source_opts *opts) { struct ecma119_write_target *t = calloc(1, sizeof(struct ecma119_write_target)); @@ -310,8 +314,13 @@ ecma119_target_new(struct iso_volset *volset, t->gid = opts->gid; t->uid = opts->uid; - //TODO get defailt values for current locale, no UTF-8 - t->input_charset = opts->input_charset ? opts->input_charset : "UTF-8"; + if (opts->input_charset) { + t->input_charset = opts->input_charset; + } else { + /* default to locale charset */ + setlocale(LC_CTYPE, ""); + t->input_charset = nl_langinfo(CODESET); + } t->ouput_charset = opts->ouput_charset ? opts->ouput_charset : "UTF-8"; t->sort_files = opts->sort_files; @@ -419,11 +428,43 @@ ecma119_target_new(struct iso_volset *volset, rrip_finalize(t, t->root); } - t->total_size = t->curblock * t->block_size; + t->total_size = (t->curblock - t->ms_block) * t->block_size; + + if (opts->dvd_plus_rw) { + + /* + * Get a copy of the volume descriptors to be written in a DVD+RW + * disc + */ + uint8_t *buf; + opts->vol_desc_count = 2 + (t->eltorito ? 1 : 0) + + (t->joliet ? 1 : 0); + opts->vol_desc = calloc(opts->vol_desc_count, t->block_size); + buf = opts->vol_desc; + + /* + * In the PVM to be written in the 16th sector of the disc, we + * need to specify the full size. + */ + t->vol_space_size = t->curblock; + write_pri_vol_desc(t, buf); + buf += t->block_size; + if (t->joliet) { + joliet_write_sup_vol_desc(t, buf); + buf += t->block_size; + } + if (t->eltorito) { + el_torito_write_boot_vol_desc(t, buf); + buf += t->block_size; + } + write_vol_desc_terminator(t, buf); + } - //TODO how needs to be here for ms? - // a change here requires changes in bs_read!! - t->vol_space_size = t->curblock; + /* + * The volume space size is just the size of the last session, in + * case of ms images. + */ + t->vol_space_size = t->curblock - t->ms_block; /* prepare for writing */ t->curblock = 0; @@ -456,7 +497,11 @@ next_state(struct ecma119_write_target *t) ||(!t->eltorito && is_eltorito_state(t->state)) ) t->state++; - printf ("now in state %d, curblock=%d\n", (int)t->state, (int)t->curblock); + { + char msg[42]; + sprintf(msg, "Now in state %d, curblock=%d.", t->state, t->curblock); + iso_msg_debug(msg); + } } static void @@ -526,11 +571,16 @@ wr_files(struct ecma119_write_target *t, uint8_t *buf) const char *path = f->path; if (!f_st->fd) { - printf("Writing file %s\n", path); + + char msg[PATH_MAX + 14]; + sprintf(msg, "Writing file %s", path); + iso_msg_debug(msg); + f_st->data_len = f->size; f_st->fd = fopen(path, "r"); - if (!f_st->fd) + if (!f_st->fd) { err(1, "couldn't open %s for reading", path); + } assert(t->curblock + t->ms_block == f->block); } @@ -824,7 +874,7 @@ bs_read(struct burn_source *bs, unsigned char *buf, int size) warnx("you must read data in block-sized chunks (%d bytes)", (int)t->block_size); return 0; - } else if (t->curblock + t->ms_block >= t->vol_space_size) { + } else if (t->curblock >= t->vol_space_size + t->ms_block) { return 0; } if (t->state_data_valid) diff --git a/libisofs/trunk/libisofs/ecma119_read.c b/libisofs/trunk/libisofs/ecma119_read.c index 12da06c8..9dc6375a 100644 --- a/libisofs/trunk/libisofs/ecma119_read.c +++ b/libisofs/trunk/libisofs/ecma119_read.c @@ -27,6 +27,7 @@ #include "util.h" #include "volume.h" #include "tree.h" +#include "messages.h" #define BLOCK_SIZE 2048 @@ -153,20 +154,22 @@ iso_read_single_directory_record(struct iso_read_info *info, */ relocated_dir = iso_read_bb(sue->data.CL.child_loc, 4, NULL); } else if (SUSP_SIG(sue, 'S', 'F')) { - printf("[ERROR] Sparse files not supported.\n"); + iso_msg_sorry(LIBISO_RR_UNSUPPORTED, "Sparse files not supported."); info->error = LIBISOFS_UNSUPPORTED_IMAGE; break; } else if (SUSP_SIG(sue, 'R', 'R')) { /* TODO I've seen this RR on mkisofs images. what's this? */ continue; } else { - printf("[DEBUG] Unhandled SUSP entry %c%c\n", sue->sig[0], sue->sig[1]); + char msg[28]; + sprintf(msg, "Unhandled SUSP entry %c%c.", sue->sig[0], sue->sig[1]); + iso_msg_hint(LIBISO_SUSP_UNHANLED, msg); } } if ( !info->error && !relocated_dir && atts.st_mode == (mode_t) 0 ) { - printf("[ERROR] Mandatory Rock Ridge PX entry is not present " - "or it contains invalid values.\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "Mandatory Rock Ridge PX entry is " + "not present or it contains invalid values."); info->error = LIBISOFS_WRONG_RR; } @@ -179,22 +182,30 @@ iso_read_single_directory_record(struct iso_read_info *info, } else { /* RR extensions are not read / used */ - //TODO allow to specify suitable values - atts.st_mode = 0555; - atts.st_gid = 0; - atts.st_uid = 0; + atts.st_mode = info->mode; + atts.st_gid = info->gid; + atts.st_uid = info->uid; if (record->flags[0] & 0x02) atts.st_mode |= S_IFDIR; else atts.st_mode |= S_IFREG; + atts.st_ino = ++info->ino; } /* * if we haven't RR extensions, or no NM entry is present, - * we use the plain ISO name + * we use the name in directory record */ - if (!name) - name = strcopy((char*)record->file_id, record->len_fi[0]); + if (!name) { + size_t len; + name = info->get_name((char*)record->file_id, record->len_fi[0]); + + /* remove trailing version number */ + len = strlen(name); + if (len > 2 && name[len-2] == ';' && name[len-1] == '1') { + name[len-2] = '\0'; + } + } /* * if we haven't RR extensions, or a needed TF time stamp is not present, @@ -255,7 +266,7 @@ iso_read_single_directory_record(struct iso_read_info *info, } break; default: - printf("[ERROR] File type not supported.\n"); + iso_msg_sorry(LIBISO_RR_UNSUPPORTED, "File type not supported."); return -1; } @@ -354,7 +365,7 @@ iso_read_dir(struct iso_read_info *info, struct iso_tree_node_dir *dir, /* check for unsupported multiextend */ if (record->flags[0] & 0x80) { - printf("[ERROR] Unsupported image.\n" + iso_msg_fatal(LIBISO_IMG_UNSUPPORTED, "Unsupported image.\n" "This image makes use of Multi-Extend features, that " "are not supported at this time.\n" "If you need support for that, please request us this feature.\n" @@ -364,7 +375,7 @@ iso_read_dir(struct iso_read_info *info, struct iso_tree_node_dir *dir, } /* check for unsupported interleaved mode */ if ( record->file_unit_size[0] || record->interleave_gap_size[0] ) { - printf("[ERROR] Unsupported image.\n" + iso_msg_fatal(LIBISO_IMG_UNSUPPORTED, "Unsupported image.\n" "This image has at least one file recorded in " "interleaved mode.\n" "We don't support this mode, as we think it's not used.\n" @@ -410,7 +421,7 @@ read_root_susp_entries(struct iso_read_info *info, record = (struct ecma119_dir_record *)buffer; /* - * FIXME + * TODO * SUSP specification claims that for CD-ROM XA the SP entry * is not at position BP 1, but at BP 15. Is that used? * In that case, we need to set info->len_skp to 15!! @@ -424,7 +435,7 @@ read_root_susp_entries(struct iso_read_info *info, susp_iter_free(iter); return -1; } else if (!sue || !SUSP_SIG(sue, 'S', 'P') ) { - printf("[DEBUG] SUSP/RR is not being used.\n"); + iso_msg_debug("SUSP/RR is not being used."); susp_iter_free(iter); return 0; } @@ -433,13 +444,13 @@ read_root_susp_entries(struct iso_read_info *info, if ( sue->version[0] != 1 || sue->data.SP.be[0] != 0xBE || sue->data.SP.ef[0] != 0xEF) { - printf("[WARN] SUSP SP system use entry seems to be wrong.\n" - "Ignoring Rock Ridge Extensions.\n"); + iso_msg_sorry(LIBISO_SUSP_WRONG, "SUSP SP system use entry seems to " + "be wrong. Ignoring Rock Ridge Extensions."); susp_iter_free(iter); - return 0; + return 0; } - printf("[DEBUG] SUSP is being used.\n"); + iso_msg_debug("SUSP/RR is being used."); /* * The LEN_SKP field, defined in IEEE 1281, SUSP. 5.3, specifies the @@ -469,10 +480,10 @@ read_root_susp_entries(struct iso_read_info *info, if (SUSP_SIG(sue, 'E', 'R')) { if (info->rr) { - printf("[WARN] More than one ER has found. " - "This is not supported.\n" - "It will be ignored, but can cause problems. " - "Please notify us about this.\n"); + iso_msg_warn(LIBISO_SUSP_MULTIPLE_ER, + "More than one ER has found. This is not supported.\n" + "It will be ignored, but can cause problems. " + "Please notify us about this.\n"); } /* * it seems that Rock Ridge can be identified with any @@ -481,27 +492,23 @@ read_root_susp_entries(struct iso_read_info *info, if ( sue->data.ER.len_id[0] == 10 && !strncmp((char*)sue->data.ER.ext_id, "RRIP_1991A", 10) ) { - printf("[DEBUG] suitable Rock Ridge ER found. Version 1.10.\n"); + iso_msg_debug("Suitable Rock Ridge ER found. Version 1.10."); info->rr = RR_EXT_110; } else if ( ( sue->data.ER.len_id[0] == 10 && !strncmp((char*)sue->data.ER.ext_id, "IEEE_P1282", 10) ) || ( sue->data.ER.len_id[0] == 9 && !strncmp((char*)sue->data.ER.ext_id, "IEEE_1282", 9) ) ) { - - printf("[DEBUG] suitable Rock Ridge ER found. Version 1.12.\n"); + + iso_msg_debug("Suitable Rock Ridge ER found. Version 1.12."); info->rr = RR_EXT_112; //TODO check also version? } else { - printf("[WARN] Not Rock Ridge ER (%s) found.\n" - "That will be ignored, but can cause problems in " - "image reading. Please notify us about this.\n", - sue->data.ER.ext_id); - } - - } else { - //TODO look also for other RR entries??? - //printf("[DEBUG] Unhandled SUSP entry %c%c\n", sue->sig[0], sue->sig[1]); + iso_msg_warn(LIBISO_SUSP_MULTIPLE_ER, + "Not Rock Ridge ER found.\n" + "That will be ignored, but can cause problems in " + "image reading. Please notify us about this"); + } } } @@ -536,8 +543,8 @@ read_pvm(struct iso_read_info *info, uint32_t block) || pvm->vol_desc_version[0] != 1 || pvm->file_structure_version[0] != 1 ) { - printf("Wrong file.\n" - "Maybe this is a damaged image, or it's not an ISO-9660 image.\n"); + iso_msg_fatal(LIBISO_WRONG_IMG, "Wrong PVM. Maybe this is a damaged " + "image, or it's not an ISO-9660 image.\n"); info->error = LIBISOFS_WRONG_PVM; return NULL; } @@ -556,11 +563,13 @@ read_pvm(struct iso_read_info *info, uint32_t block) volset_id = strcopy((char*)pvm->vol_set_id, 128); + *(info->size) = iso_read_bb(pvm->vol_space_size, 4, NULL); + volset = iso_volset_new(volume, volset_id); free(volset_id); /* - * FIXME + * TODO * I don't like the way the differences volset - volume are hanled now. * While theorically right (a volset can contain several volumes), in * practice it seems that this never happen. Current implementation, with @@ -584,20 +593,10 @@ read_pvm(struct iso_read_info *info, uint32_t block) return NULL; } - /* we want to read RR? */ + /* are RR ext present */ info->hasRR = info->rr ? 1 : 0; - if (info->norock) - info->rr = RR_EXT_NO; - - /* Now, read the tree */ - if ( iso_read_dir(info, volume->root, - iso_read_bb(rootdr->block, 4, NULL)) ) { - - /* error, cleanup and return */ - iso_volset_free(volset); - return NULL; - } + info->iso_root_block = iso_read_bb(rootdr->block, 4, NULL); /* * PVM has things that can be interested, but don't have a member in @@ -613,6 +612,8 @@ iso_volset_read(struct data_source *src, struct ecma119_read_opts *opts) { struct iso_read_info info; struct iso_volset *volset; + uint32_t block, root_dir_block; + unsigned char buffer[BLOCK_SIZE]; assert(src && opts); @@ -623,6 +624,11 @@ iso_volset_read(struct data_source *src, struct ecma119_read_opts *opts) info.len_skp = 0; info.ino = 0; info.norock = opts->norock; + info.uid = opts->uid; + info.gid = opts->gid; + info.mode = opts->mode & ~S_IFMT; + info.size = &opts->size; + root_dir_block = 0; /* read primary volume description */ volset = read_pvm(&info, opts->block + 16); @@ -632,15 +638,108 @@ iso_volset_read(struct data_source *src, struct ecma119_read_opts *opts) return NULL; } - opts->hasRR = info.hasRR; + block = opts->block + 17; + do { + if ( info.src->read_block(info.src, block, buffer) < 0 ) { + info.error = LIBISOFS_READ_FAILURE; + iso_volset_free(volset); + return NULL; + } + switch (buffer[0]) { + case 0: + /* boot record */ + //TODO handle el-torito + break; + case 2: + /* suplementary volume descritor */ + { + struct ecma119_sup_vol_desc *sup; + struct ecma119_dir_record *root; + + sup = (struct ecma119_sup_vol_desc*)buffer; + if (sup->esc_sequences[0] == 0x25 && + sup->esc_sequences[1] == 0x2F && + (sup->esc_sequences[2] == 0x40 || + sup->esc_sequences[2] == 0x43 || + sup->esc_sequences[2] == 0x45) ) { + + /* it's a Joliet Sup. Vol. Desc. */ + info.hasJoliet = 1; + root = (struct ecma119_dir_record*)sup->root_dir_record; + root_dir_block = iso_read_bb(root->block, 4, NULL); + //TODO maybe we can set the volume attribs from this + //descriptor + } else { + iso_msg_hint(LIBISO_UNSUPPORTED_VD, + "Not supported Sup. Vol. Desc found."); + } + } + break; + + case 255: + /* + * volume set terminator + * ignore, as it's checked in loop end condition + */ + break; + default: + { + char msg[32]; + sprintf(msg, "Ignoring Volume descriptor %d.", buffer[0]); + iso_msg_hint(LIBISO_UNSUPPORTED_VD, msg); + } + break; + } + block++; + } while (buffer[0] != 255); - // TODO read other volume descriptors - // - supplementary: for joliet - // - boot: el-torito - // Read all volume descriptor till Volume Descriptor Set Terminator + + opts->hasRR = info.hasRR; + opts->hasJoliet = info.hasJoliet; + + /* user doesn't want to read RR extensions */ + if (info.norock) + info.rr = RR_EXT_NO; + + /* select what tree to read */ + if (info.rr) { + /* RR extensions are available */ + if (opts->preferjoliet && info.hasJoliet) { + /* if user prefers joliet, that is used */ + iso_msg_debug("Reading Joliet extensions."); + info.get_name = ucs2str; + info.rr = RR_EXT_NO; + /* root_dir_block already contains root for joliet */ + } else { + /* RR will be used */ + iso_msg_debug("Reading Rock Ridge extensions."); + root_dir_block = info.iso_root_block; + info.get_name = strcopy; + } + } else { + /* RR extensions are not available */ + if (info.hasJoliet && !opts->nojoliet) { + /* joliet will be used */ + iso_msg_debug("Reading Joliet extensions."); + info.get_name = ucs2str; + /* root_dir_block already contains root for joliet */ + } else { + /* default to plain iso */ + iso_msg_debug("Reading plain ISO-9660 tree."); + root_dir_block = info.iso_root_block; + info.get_name = strcopy; + } + } + + /* Read the ISO/RR or Joliet tree */ + if ( iso_read_dir(&info, volset->volume[0]->root, root_dir_block) ) { + + /* error, cleanup and return */ + iso_volset_free(volset); + return NULL; + } // TODO merge tree info - // TODO free here? data_source_free(src); return volset; } diff --git a/libisofs/trunk/libisofs/ecma119_read.h b/libisofs/trunk/libisofs/ecma119_read.h index 2604fdcc..ead19e02 100644 --- a/libisofs/trunk/libisofs/ecma119_read.h +++ b/libisofs/trunk/libisofs/ecma119_read.h @@ -35,10 +35,27 @@ struct iso_read_info { struct data_source *src; enum read_error error; + uid_t uid; /**< Default uid when no RR */ + gid_t gid; /**< Default uid when no RR */ + mode_t mode; /**< Default mode when no RR (only permissions) */ + + uint32_t iso_root_block; /**< Will be filled with the block lba of the + * extend for the root directory, as read from + * the PVM + */ + enum read_rr_ext rr; /*< If we need to read RR extensions. i.e., if the image * contains RR extensions, and the user wants to read them. */ - ino_t ino; /*< RR version 1.10 does not have file serial numbers, we - * need to generate it */ + + char *(*get_name)(const char *, size_t); + /**< + * The function used to read the name from a directoy record. For + * ISO, the name is in US-ASCII. For Joliet, in UCS-2BE. Thus, we + * need different functions for both. + */ + + ino_t ino; /*< Joliet and RR version 1.10 does not have file serial numbers, + * we need to generate it. */ uint8_t len_skp; /*< bytes skipped within the System Use field of a directory record, before the beginning of the SUSP system user entries. See IEEE 1281, SUSP. 5.3. */ @@ -46,6 +63,9 @@ struct iso_read_info { unsigned int norock:1; /*< Do not read Rock Ridge extensions */ unsigned int hasRR:1; /*< It will be set to 1 if RR extensions are present, to 0 if not. */ + unsigned int hasJoliet:1; /*< It will be set to 1 if Joliet ext are present, + to 0 if not. */ + uint32_t *size; }; diff --git a/libisofs/trunk/libisofs/ecma119_read_rr.c b/libisofs/trunk/libisofs/ecma119_read_rr.c index 44af3f9b..ef64f1c2 100644 --- a/libisofs/trunk/libisofs/ecma119_read_rr.c +++ b/libisofs/trunk/libisofs/ecma119_read_rr.c @@ -15,6 +15,7 @@ #include "ecma119_read.h" #include "ecma119_read_rr.h" #include "util.h" +#include "messages.h" #define BLOCK_SIZE 2048 @@ -30,11 +31,11 @@ read_rr_PX(struct iso_read_info *info, struct susp_sys_user_entry *px, assert( px->sig[0] == 'P' && px->sig[1] == 'X'); if ( info->rr == RR_EXT_112 && px->len_sue[0] != 44 ) { - printf("[ERROR] Invalid PX entry for RR version 1.12\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "Invalid PX entry for RR version 1.12"); info->error = LIBISOFS_WRONG_RR; return -1; } else if ( info->rr == RR_EXT_110 && px->len_sue[0] != 36 ) { - printf("[ERROR] Invalid PX entry for RR version 1.10\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "Invalid PX entry for RR version 1.10"); info->error = LIBISOFS_WRONG_RR; return -1; } @@ -85,7 +86,7 @@ read_rr_TF(struct iso_read_info *info, struct susp_sys_user_entry *tf, /* 2. modify time */ if (tf->data.TF.flags[0] & (1 << 1)) { if (tf->len_sue[0] < 5 + (nts+1) * s) { - printf("[ERROR] RR TF entry too short.\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "RR TF entry too short."); info->error = LIBISOFS_WRONG_RR; return -1; } @@ -101,7 +102,7 @@ read_rr_TF(struct iso_read_info *info, struct susp_sys_user_entry *tf, /* 3. access time */ if (tf->data.TF.flags[0] & (1 << 2)) { if (tf->len_sue[0] < 5 + (nts+1) * s) { - printf("[ERROR] RR TF entry too short.\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "RR TF entry too short."); info->error = LIBISOFS_WRONG_RR; return -1; } @@ -117,7 +118,7 @@ read_rr_TF(struct iso_read_info *info, struct susp_sys_user_entry *tf, /* 4. attributes time */ if (tf->data.TF.flags[0] & (1 << 3)) { if (tf->len_sue[0] < 5 + (nts+1) * s) { - printf("[ERROR] RR TF entry too short.\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "RR TF entry too short."); info->error = LIBISOFS_WRONG_RR; return -1; } @@ -178,7 +179,9 @@ read_rr_SL(struct susp_sys_user_entry *sl, char *dest) len = 1; comp = "/"; } else if (flags & ~0x01) { - printf("[ERROR] SL component flag %x not supported.\n", flags); + char msg[38]; + sprintf(msg, "SL component flag %x not supported.", flags); + iso_msg_sorry(LIBISO_RR_ERROR, msg); return NULL; } else { len = sl->data.SL.comps[pos + 1]; @@ -275,7 +278,7 @@ susp_iter_next(struct susp_iterator* iter) if (entry->len_sue[0] == 0) { /* a wrong image with this lead us to a infinity loop */ - printf("[ERROR] Damaged RR/SUSP information.\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "Damaged RR/SUSP information."); iter->info->error = LIBISOFS_WRONG_RR; return NULL; } @@ -285,10 +288,10 @@ susp_iter_next(struct susp_iterator* iter) if ( SUSP_SIG(entry, 'C', 'E') ) { /* Continuation entry */ if (iter->ce_len) { - printf("[WARN] More than one CE System user entry has found " - "in a single System Use field or continuation area.\n" - "This breaks SUSP standard and it's not supported.\n" - "Ignoring last CE. Maybe the image is damaged.\n"); + iso_msg_sorry(LIBISO_RR_ERROR, "More than one CE System user entry " + "has found in a single System Use field or continuation area. " + "This breaks SUSP standard and it's not supported.\n" + "Ignoring last CE. Maybe the image is damaged.\n"); } else { iter->ce_block = iso_read_bb(entry->data.CE.block, 4, NULL); iter->ce_off = iso_read_bb(entry->data.CE.offset, 4, NULL); diff --git a/libisofs/trunk/libisofs/ecma119_tree.h b/libisofs/trunk/libisofs/ecma119_tree.h index e215acd9..77ad0424 100644 --- a/libisofs/trunk/libisofs/ecma119_tree.h +++ b/libisofs/trunk/libisofs/ecma119_tree.h @@ -53,7 +53,7 @@ struct ecma119_tree_node { char *iso_name; /**< in ASCII, conforming to the * current ISO level. */ - char *full_name; /**< full name, in current locale (TODO put this in UTF-8?) */ + char *full_name; /**< full name, in current locale */ size_t dirent_len; /**< Length of the directory record, * not including SU. */ diff --git a/libisofs/trunk/libisofs/eltorito.c b/libisofs/trunk/libisofs/eltorito.c index de46c616..8e461f75 100644 --- a/libisofs/trunk/libisofs/eltorito.c +++ b/libisofs/trunk/libisofs/eltorito.c @@ -149,7 +149,7 @@ create_image(struct iso_tree_node *image, if (mbr.partition[i].type != 0) { /* it's an used partition */ if (used_partition != -1) { - fprintf(stderr, "Invalid MBR. At least 2 paritions: %d and " + fprintf(stderr, "Invalid MBR. At least 2 partitions: %d and " "%d, are being used\n", used_partition, i); return NULL; } else @@ -294,8 +294,8 @@ void el_torito_get_image_files(struct ecma119_write_target *t) /** * Write the Boot Record Volume Descriptor */ -static void -write_boot_vol_desc(struct ecma119_write_target *t, uint8_t *buf) +void +el_torito_write_boot_vol_desc(struct ecma119_write_target *t, uint8_t *buf) { struct el_torito_boot_catalog *cat = t->catalog; struct ecma119_boot_rec_vol_desc *vol = @@ -438,7 +438,7 @@ el_torito_wr_boot_vol_desc(struct ecma119_write_target *t, uint8_t *buf) { assert(t->catalog); ecma119_start_chunking(t, - write_boot_vol_desc, + el_torito_write_boot_vol_desc, 2048, buf); } diff --git a/libisofs/trunk/libisofs/eltorito.h b/libisofs/trunk/libisofs/eltorito.h index 2e27fac8..0b9c91b5 100644 --- a/libisofs/trunk/libisofs/eltorito.h +++ b/libisofs/trunk/libisofs/eltorito.h @@ -51,6 +51,12 @@ void el_torito_get_image_files(struct ecma119_write_target *t); */ void el_torito_patch_image_files(struct ecma119_write_target *t); +/** + * Write the Boot Record Volume Descriptor + */ +void +el_torito_write_boot_vol_desc(struct ecma119_write_target *t, uint8_t *buf); + void el_torito_wr_boot_vol_desc(struct ecma119_write_target *t, uint8_t *buf); diff --git a/libisofs/trunk/libisofs/joliet.c b/libisofs/trunk/libisofs/joliet.c index 0266f8d2..ad8583b5 100644 --- a/libisofs/trunk/libisofs/joliet.c +++ b/libisofs/trunk/libisofs/joliet.c @@ -302,8 +302,8 @@ write_m_path_table(struct ecma119_write_target *t, uint8_t *buf) write_path_table (t, 0, buf); } -static void -write_sup_vol_desc(struct ecma119_write_target *t, uint8_t *buf) +void +joliet_write_sup_vol_desc(struct ecma119_write_target *t, uint8_t *buf) { struct ecma119_sup_vol_desc *vol = (struct ecma119_sup_vol_desc*)buf; struct iso_volume *volume = t->volset->volume[t->volnum]; @@ -414,7 +414,7 @@ write_dirs(struct ecma119_write_target *t, uint8_t *buf) size_t i; struct joliet_tree_node *dir; - assert (t->curblock == t->dirlist_joliet[0]->info.dir.block); + assert (t->curblock + t->ms_block == t->dirlist_joliet[0]->info.dir.block); for (i = 0; i < t->dirlist_len_joliet; i++) { dir = t->dirlist_joliet[i]; write_one_dir(t, dir, buf); @@ -427,7 +427,7 @@ joliet_wr_sup_vol_desc(struct ecma119_write_target *t, uint8_t *buf) { ecma119_start_chunking(t, - write_sup_vol_desc, + joliet_write_sup_vol_desc, 2048, buf); } diff --git a/libisofs/trunk/libisofs/joliet.h b/libisofs/trunk/libisofs/joliet.h index 07f9a3e6..1b4adc41 100644 --- a/libisofs/trunk/libisofs/joliet.h +++ b/libisofs/trunk/libisofs/joliet.h @@ -74,6 +74,9 @@ joliet_prepare_path_tables(struct ecma119_write_target *t); void joliet_tree_free(struct joliet_tree_node *root); +void +joliet_write_sup_vol_desc(struct ecma119_write_target *t, uint8_t *buf); + void joliet_wr_sup_vol_desc(struct ecma119_write_target *t, uint8_t *buf); diff --git a/libisofs/trunk/libisofs/libisofs.h b/libisofs/trunk/libisofs/libisofs.h index 6e043ef0..b4193257 100644 --- a/libisofs/trunk/libisofs/libisofs.h +++ b/libisofs/trunk/libisofs/libisofs.h @@ -51,7 +51,8 @@ struct iso_tree_node; * iso_tree_node_get_type to get the current type of the node, and then * cast to the appropriate subtype. For example: * - * struct iso_tree_node *node = iso... TODO + * ... + * struct iso_tree_node *node = iso_tree_iter_next(iter); * if ( iso_tree_node_get_type(node) == LIBISO_NODE_DIR ) { * struct iso_tree_node_dir *dir = (struct iso_tree_node_dir *)node; * ... @@ -161,12 +162,47 @@ enum eltorito_boot_media_type { ELTORITO_NO_EMUL }; +/** + * ISO-9660 (ECMA-119) has important restrictions in both file/dir names + * and deep of the directory hierarchy. These are intented for compatibility + * with old systems, and most modern operative system can safety deal with + * ISO filesystems with relaxed constraints. + * You can use some of these flags to generate that kind of filesystems with + * libisofs. Of course, all these options will lead to an image not conforming + * with ISO-9660 specification, so use them with caution. + * Moreover, note that there are much better options to have an ISO-9660 image + * compliant with modern systems, such as the Rock Ridge and Joliet extensions, + * that add support for longer filenames, deeper directory hierarchy and even + * file permissions (in case of RR), while keeping a standard ISO structure + * suitable for old systems. + * Thus, in most cases you don't want to use the relaxed constraints. + */ enum ecma119_relaxed_constraints_flag { ECMA119_OMIT_VERSION_NUMBERS = (1<<0), - /* 37 char filenames involves no version number */ + /**< + * ISO-9660 requires a version number at the end of each file name. + * That number is just ignored on most systems, so you can omit them + * if you want. + */ ECMA119_37_CHAR_FILENAMES = (1<<1) | (1<<0), + /**< + * Allow ISO-9660 filenames to be up to 37 characters long. The extra + * space is taken from the version number, so this option involves + * no version number + */ ECMA119_NO_DIR_REALOCATION = (1<<2), + /**< + * In ISO-9660 images the depth of the directory hierarchy can't be + * greater than 8 levels. In addition, a path to a file on disc can't + * be more than 255 characteres. Use the ECMA119_NO_DIR_REALOCATION + * to disable this restriction. + */ ECMA119_RELAXED_FILENAMES = (1<<3) + /**< + * Allow filenames with any character. Note that with this flag, the + * filename provide by the user will be used without any modification + * other that a truncate to max. length. + */ }; /** @@ -241,27 +277,77 @@ struct ecma119_source_opts { * image, used to read file contents. * Otherwise it can be NULL. */ + unsigned int dvd_plus_rw:1; + /**< + * When 1, vol_desc and vol_desc_count will be filled propertly + * with information useful for "growing" a DVD+RW. + */ + uint8_t *vol_desc; + /**< + * If dvd_plus_rw is set to one, this will be filled with a + * pointer to a memory region containing a copy of the + * volume descriptors of the image, including the volume + * descriptor set terminator. + * A suitable program can write the contents of this memory + * region from sector 16 of a DVD+RW to "grow" its image. + * The size of this region will be vol_desc_count * 2048 and + * should be freed by user when no more needed. + */ + int vol_desc_count; + /**< + * If dvd_plus_rw is set to one, this will be filled with the + * number of volume descriptors written to vol_desc. + */ }; /** - * FIXME documentar isto!!! + * Options for image reading. + * There are four kind of options: + * - Related to multisession support. + * In most cases, an image begins at LBA 0 of the data source. However, + * in multisession discs, the later image begins in the last session on + * disc. The block option can be used to specify the start of that last + * session. + * - Related to the tree that will be read. + * As default, when Rock Ridge extensions are present in the image, that + * will be used to get the tree. If RR extensions are not present, libisofs + * will use the Joliet extensions if available. Finally, the plain ISO-9660 + * tree is used if neither RR nor Joliet extensions are available. With + * norock, nojoliet, and preferjoliet options, you can change this + * default behavior. + * - Related to default POSIX attributes. + * When Rock Ridege extensions are not used, libisofs can't figure out what + * are the the permissions, uid or gid for the files. You should supply + * default values for that. + * - Return information for image. + * Both size, hasRR and hasJoliet will be filled by libisofs with suitable values. + * Also, error is set to non-0 if some error happens (error codes are + * private now) */ struct ecma119_read_opts { - int tree_to_read; - int block; /** Block where the image begins, usually 0, can be - * different on a multisession disc. - */ - //TODO.... + uint32_t block; /** Block where the image begins, usually 0, can be + * different on a multisession disc. + */ + unsigned int norock:1; /*< Do not read Rock Ridge extensions */ - //nojoliet - //check -> convert names to lower case - //uid, gid (when no RR) - //file and dir mode (when no RR) + unsigned int nojoliet:1; /*< Do not read Joliet extensions */ + unsigned int preferjoliet:1; + /*< When both Joliet and RR extensions are present, the RR + * tree is used. If you prefer using Joliet, set this to 1. */ + + uid_t uid; /**< Default uid when no RR */ + gid_t gid; /**< Default uid when no RR */ + mode_t mode; /**< Default mode when no RR (only permissions) */ + //TODO differ file and dir mode + //option to convert names to lower case? /* modified by the function */ unsigned int hasRR:1; /*< It will be set to 1 if RR extensions are present, to 0 if not. */ - //hasJoliet + unsigned int hasJoliet:1; /*< It will be set to 1 if Joliet extensions are + present, to 0 if not. */ + uint32_t size; /**< Will be filled with the size (in 2048 byte block) of + * the image, as reported in the PVM. */ int error; }; @@ -327,6 +413,17 @@ struct iso_tree_radd_dir_behavior { //char** errors; }; +/** + * Initialize libisofs. You must call this before any usage of the library. + * @return 1 on success, 0 on error + */ +int iso_init(); + +/** + * Finalize libisofs. + */ +void iso_finish(); + /** * Create a new volume. * The parameters can be set to NULL if you wish to set them later. @@ -804,6 +901,14 @@ struct iso_tree_iter *iso_tree_node_children(struct iso_tree_node_dir *dir); */ struct iso_tree_node *iso_tree_iter_next(struct iso_tree_iter *iter); +/** + * Check if there're more children. + * @return + * 1 if next call to iso_tree_iter_next() will return != NULL, + * 0 otherwise + */ +int iso_tree_iter_has_next(struct iso_tree_iter *iter); + /** Free an iteration */ void iso_tree_iter_free(struct iso_tree_iter *iter); @@ -933,4 +1038,42 @@ void data_source_free(struct data_source*); struct iso_volset *iso_volset_read(struct data_source *src, struct ecma119_read_opts *opts); +/** + * Control queueing and stderr printing of messages from libisofs. + * Severity may be one of "NEVER", "FATAL", "SORRY", "WARNING", "HINT", + * "NOTE", "UPDATE", "DEBUG", "ALL". + * + * @param queue_severity Gives the minimum limit for messages to be queued. + * Default: "NEVER". If you queue messages then you + * must consume them by iso_msgs_obtain(). + * @param print_severity Does the same for messages to be printed directly + * to stderr. + * @param print_id A text prefix to be printed before the message. + * @return >0 for success, <=0 for error + */ +int iso_msgs_set_severities(char *queue_severity, + char *print_severity, char *print_id); + +#define ISO_MSGS_MESSAGE_LEN 4096 + +/** + * Obtain the oldest pending libisofs message from the queue which has at + * least the given minimum_severity. This message and any older message of + * lower severity will get discarded from the queue and is then lost forever. + * + * Severity may be one of "NEVER", "FATAL", "SORRY", "WARNING", "HINT", + * "NOTE", "UPDATE", "DEBUG", "ALL". To call with minimum_severity "NEVER" + * will discard the whole queue. + * + * @param error_code Will become a unique error code as listed in messages.h + * @param msg_text Must provide at least ISO_MSGS_MESSAGE_LEN bytes. + * @param os_errno Will become the eventual errno related to the message + * @param severity Will become the severity related to the message and + * should provide at least 80 bytes. + * @return 1 if a matching item was found, 0 if not, <0 for severe errors + */ +int iso_msgs_obtain(char *minimum_severity, + int *error_code, char msg_text[], int *os_errno, + char severity[]); + #endif /* LIBISO_LIBISOFS_H */ diff --git a/libisofs/trunk/libisofs/tree.c b/libisofs/trunk/libisofs/tree.c index c7962ee6..044961e1 100644 --- a/libisofs/trunk/libisofs/tree.c +++ b/libisofs/trunk/libisofs/tree.c @@ -36,7 +36,8 @@ void iso_tree_add_child(struct iso_tree_node_dir *parent, struct iso_tree_node *child) { - assert( parent && child); + assert(parent && child); + assert(!child->parent); parent->nchildren++; parent->children = @@ -338,6 +339,13 @@ iso_tree_iter_next(struct iso_tree_iter *iter) return NULL; } +int +iso_tree_iter_has_next(struct iso_tree_iter *iter) +{ + assert(iter); + return iter->index + 1 < iter->dir->nchildren; +} + void iso_tree_iter_free(struct iso_tree_iter *iter) { diff --git a/libisofs/trunk/libisofs/util.c b/libisofs/trunk/libisofs/util.c index 18f33c26..27aed11b 100644 --- a/libisofs/trunk/libisofs/util.c +++ b/libisofs/trunk/libisofs/util.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -571,7 +572,7 @@ iso_r_fileid(const char *src_arg, const char *icharset, int flag) /* no relaxed filenames */ dot = strrchr(src, '.'); - max = size == 37 ? 36 : 30; + max = (size == 37 ? 36 : 30); /* Since the maximum length can be divided freely over the name and extension, we need to calculate their new lengths (lnname and lnext). If the original filename is too long, we start by trimming @@ -851,5 +852,55 @@ char *strcopy(const char *buf, size_t len) str = malloc( (len+1) * sizeof(char) ); strncpy(str, buf, len); str[len] = '\0'; + + /* remove trailing spaces */ + for (len = len-1; str[len] == ' ' && len > 0; --len) + str[len] = '\0'; + + return str; +} + +char *ucs2str(const char *buf, size_t len) +{ + size_t outbytes, inbytes; + char *str; + + inbytes = len; + + outbytes = (inbytes+1) * MB_LEN_MAX; + { + /* ensure enought space */ + char out[outbytes]; + char *src; + iconv_t conv; + size_t n; + + /* convert to local charset */ + setlocale(LC_CTYPE, ""); + conv = iconv_open(nl_langinfo(CODESET), "UCS-2BE"); + if (conv == (iconv_t)(-1)) { + printf("Can't convert from %s to %s\n", "UCS-2BE", nl_langinfo(CODESET)); + return NULL; + } + src = (char *)buf; + str = (char *)out; + + n = iconv(conv, &src, &inbytes, &str, &outbytes); + if (n == -1) { + /* error just return input stream */ + perror("Convert error."); + printf("Maybe string is not encoded in UCS-2BE.\n"); + + iconv_close(conv); + return NULL; + } + iconv_close(conv); + *str = '\0'; + + /* remove trailing spaces */ + for (len = strlen(out) - 1; out[len] == ' ' && len > 0; --len) + out[len] = '\0'; + str = strdup(out); + } return str; } diff --git a/libisofs/trunk/libisofs/util.h b/libisofs/trunk/libisofs/util.h index 2e9b27cd..11da94fb 100644 --- a/libisofs/trunk/libisofs/util.h +++ b/libisofs/trunk/libisofs/util.h @@ -141,9 +141,13 @@ int ucscmp(const uint16_t *s1, const uint16_t *s2); /** * Copy up to \p len chars from \p buf and return this newly allocated * string. The new string is null-terminated. - * TODO it would be great to return NULL is the original string was all - * white spaces. */ char *strcopy(const char *buf, size_t len); +/** + * Convert a Joliet string with a length of \p len bytes to a new string + * in local charset. + */ +char *ucs2str(const char *buf, size_t len); + #endif /* LIBISO_UTIL_H */ diff --git a/libisofs/trunk/test/iso.c b/libisofs/trunk/test/iso.c index 9d792fe9..08c05890 100644 --- a/libisofs/trunk/test/iso.c +++ b/libisofs/trunk/test/iso.c @@ -92,6 +92,12 @@ int main(int argc, char **argv) usage(); return 1; } + + if (!iso_init()) { + err(1, "Can't init libisofs"); + } + iso_msgs_set_severities("NEVER", "ALL", ""); + fd = fopen(argv[optind+1], "w"); if (!fd) { err(1, "error opening output file"); @@ -133,7 +139,7 @@ int main(int argc, char **argv) opts.level = level; opts.flags = flags; opts.relaxed_constraints = 0;//constraints; - opts.input_charset = "UTF-8"; + opts.input_charset = NULL;//"UTF-8"; opts.ouput_charset = "UTF-8"; src = iso_source_new_ecma119(volset, &opts); @@ -143,5 +149,6 @@ int main(int argc, char **argv) } fclose(fd); + iso_finish(); return 0; } diff --git a/libisofs/trunk/test/iso_add.c b/libisofs/trunk/test/iso_add.c index b5facf5b..48b41eb5 100644 --- a/libisofs/trunk/test/iso_add.c +++ b/libisofs/trunk/test/iso_add.c @@ -98,6 +98,11 @@ int main(int argc, char **argv) return 1; } + if (!iso_init()) { + err(1, "Can't init libisofs"); + } + iso_msgs_set_severities("NEVER", "ALL", ""); + rsrc = data_source_from_file(argv[optind]); if (rsrc == NULL) { printf ("Can't open device\n"); @@ -111,6 +116,11 @@ int main(int argc, char **argv) ropts.block = 0; ropts.norock = 0; + ropts.nojoliet = 0; + ropts.preferjoliet = 0; + ropts.mode = 0555; + ropts.uid = 0; + ropts.gid = 0; volset = iso_volset_read(rsrc, &ropts); if (volset == NULL) { @@ -141,5 +151,7 @@ int main(int argc, char **argv) } fclose(fd); + iso_finish(); + return 0; } diff --git a/libisofs/trunk/test/iso_ms.c b/libisofs/trunk/test/iso_ms.c index 70f37829..716d2d1e 100644 --- a/libisofs/trunk/test/iso_ms.c +++ b/libisofs/trunk/test/iso_ms.c @@ -101,6 +101,11 @@ int main(int argc, char **argv) return 1; } + if (!iso_init()) { + err(1, "Can't init libisofs"); + } + iso_msgs_set_severities("NEVER", "ALL", ""); + rsrc = data_source_from_file(argv[optind+2]); if (rsrc == NULL) { printf ("Can't open device\n"); @@ -114,6 +119,11 @@ int main(int argc, char **argv) ropts.block = atoi(argv[optind]); ropts.norock = 0; + ropts.nojoliet = 0; + ropts.preferjoliet = 0; + ropts.mode = 0555; + ropts.uid = 0; + ropts.gid = 0; volset = iso_volset_read(rsrc, &ropts); if (volset == NULL) { @@ -143,5 +153,7 @@ int main(int argc, char **argv) } fclose(fd); + iso_finish(); + return 0; } diff --git a/libisofs/trunk/test/iso_read.c b/libisofs/trunk/test/iso_read.c index f7381989..9dbb05b5 100644 --- a/libisofs/trunk/test/iso_read.c +++ b/libisofs/trunk/test/iso_read.c @@ -81,6 +81,11 @@ int main(int argc, char **argv) return 1; } + if (!iso_init()) { + err(1, "Can't init libisofs"); + } + iso_msgs_set_severities("NEVER", "ALL", ""); + src = data_source_from_file(argv[1]); if (src == NULL) { printf ("Can't open image\n"); @@ -89,6 +94,12 @@ int main(int argc, char **argv) opts.block = 0; opts.norock = 0; + opts.nojoliet = 0; + opts.preferjoliet = 1; + opts.mode = 0555; + opts.uid = 0; + opts.gid = 0; + volset = iso_volset_read(src, &opts); if (volset == NULL) { @@ -110,6 +121,11 @@ int main(int argc, char **argv) printf("Abstract: %s\n", iso_volume_get_abstract_file_id(volume)); printf("Biblio: %s\n", iso_volume_get_biblio_file_id(volume)); + if (opts.hasRR) + printf("Rock Ridge Extensions are available.\n"); + if (opts.hasJoliet) + printf("Joliet Extensions are available.\n"); + printf("\nDIRECTORY TREE\n"); printf("==============\n"); @@ -120,5 +136,7 @@ int main(int argc, char **argv) data_source_free(src); iso_volset_free(volset); + iso_finish(); + return 0; }