You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6634 lines
213 KiB
6634 lines
213 KiB
/* |
|
* Copyright (c) 2007 Vreixo Formoso |
|
* Copyright (c) 2009 - 2018 Thomas Schmitt |
|
* |
|
* 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 |
|
* or later as published by the Free Software Foundation. |
|
* See COPYING file for details. |
|
*/ |
|
|
|
/* |
|
* Filesystem/FileSource implementation to access an ISO image, using an |
|
* IsoDataSource to read image data. |
|
*/ |
|
|
|
#ifdef HAVE_CONFIG_H |
|
#include "../config.h" |
|
#endif |
|
|
|
#include "libisofs.h" |
|
#include "ecma119.h" |
|
#include "messages.h" |
|
#include "rockridge.h" |
|
#include "image.h" |
|
#include "tree.h" |
|
#include "eltorito.h" |
|
#include "node.h" |
|
#include "aaip_0_2.h" |
|
#include "system_area.h" |
|
|
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <locale.h> |
|
#include <langinfo.h> |
|
#include <limits.h> |
|
#include <stdio.h> |
|
#include <ctype.h> |
|
|
|
|
|
/* Enable this and write the correct absolute path into the include statement |
|
below in order to test the pending contribution to syslinux: |
|
http://www.syslinux.org/archives/2013-March/019755.html |
|
|
|
# def ine Libisofs_syslinux_tesT 1 |
|
|
|
*/ |
|
#ifdef Libisofs_syslinux_tesT |
|
#define Isolinux_rockridge_in_libisofS 1 |
|
#include "/reiser/syslinux/core/fs/iso9660/susp_rr.c" |
|
/* |
|
# inc lude "/home/thomas/projekte/cdrskin_dir/libisoburn-develop/test/susp_rr.c" |
|
*/ |
|
#endif /* Libisofs_syslinux_tesT */ |
|
|
|
|
|
/** |
|
* 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. |
|
*/ |
|
struct iso_read_opts |
|
{ |
|
/** |
|
* Block where the image begins, usually 0, can be different on a |
|
* multisession disc. |
|
*/ |
|
uint32_t block; |
|
|
|
unsigned int norock : 1; /*< Do not read Rock Ridge extensions */ |
|
unsigned int nojoliet : 1; /*< Do not read Joliet extensions */ |
|
unsigned int noiso1999 : 1; /*< Do not read ISO 9660:1999 enhanced tree */ |
|
unsigned int noaaip : 1; /* Do not read AAIP extension for xattr and ACL */ |
|
unsigned int nomd5 : 2; /* Do not read MD5 array */ |
|
|
|
/** |
|
* Hand out new inode numbers and overwrite eventually read PX inode |
|
* numbers. This will split apart any hardlinks. |
|
*/ |
|
unsigned int make_new_ino : 1 ; |
|
|
|
/** |
|
* When both Joliet and RR extensions are present, the RR tree is used. |
|
* If you prefer using Joliet, set this to 1. |
|
*/ |
|
unsigned int preferjoliet : 1; |
|
|
|
/** |
|
* If neither Rock Ridge nor Joliet is used, the ECMA-119 names are mapped |
|
* according to one of these rules |
|
* 0 = unmapped: show name as recorded in ECMA-119 directory record |
|
* (not suitable for writing them to a new ISO filesystem) |
|
* 1 = stripped: like unmapped, but strip off trailing ";1" or ".;1" |
|
* 2 = uppercase: like stripped, but {a-z} mapped to {A-Z} |
|
* 3 = lowercase: like stripped, but {A-Z} mapped to {a-z} |
|
*/ |
|
unsigned int ecma119_map : 2; |
|
|
|
uid_t uid; /**< Default uid when no RR */ |
|
gid_t gid; /**< Default uid when no RR */ |
|
mode_t dir_mode; /**< Default mode when no RR (only permissions) */ |
|
mode_t file_mode; |
|
/* TODO #00024 : option to convert names to lower case for iso reading */ |
|
|
|
/** |
|
* Input charset for RR file names. NULL to use default locale charset. |
|
*/ |
|
char *input_charset; |
|
|
|
/** |
|
* Enable or disable methods to automatically choose an input charset. |
|
* This eventually overrides input_charset. |
|
* |
|
* bit0= allow to set the input character set automatically from |
|
* attribute "isofs.cs" of root directory |
|
*/ |
|
int auto_input_charset; |
|
|
|
|
|
/** |
|
* Enable or disable loading of the first 32768 bytes of the session and |
|
* submission by iso_write_opts_set_system_area(data, 0). |
|
*/ |
|
int load_system_area; |
|
|
|
/** |
|
* Keep data source of imported ISO filesystem in IsoImage.import_src |
|
*/ |
|
int keep_import_src; |
|
|
|
/** |
|
* What to do in case of name longer than truncate_length: |
|
* 0= throw FAILURE |
|
* 1= truncate to truncate_length with MD5 of whole name at end |
|
*/ |
|
int truncate_mode; |
|
int truncate_length; |
|
|
|
}; |
|
|
|
/** |
|
* Return information for image. |
|
* Both size, hasRR and hasJoliet will be filled by libisofs with suitable |
|
* values. |
|
*/ |
|
struct iso_read_image_features |
|
{ |
|
/** |
|
* Will be filled with the size (in 2048 byte block) of the image, as |
|
* reported in the PVM. |
|
*/ |
|
uint32_t size; |
|
|
|
/** It will be set to 1 if RR extensions are present, to 0 if not. */ |
|
unsigned int hasRR :1; |
|
|
|
/** It will be set to 1 if Joliet extensions are present, to 0 if not. */ |
|
unsigned int hasJoliet :1; |
|
|
|
/** |
|
* It will be set to 1 if the image is an ISO 9660:1999, i.e. it has |
|
* a version 2 Enhanced Volume Descriptor. |
|
*/ |
|
unsigned int hasIso1999 :1; |
|
|
|
/** It will be set to 1 if El-Torito boot record is present, to 0 if not.*/ |
|
unsigned int hasElTorito :1; |
|
}; |
|
|
|
static int ifs_fs_open(IsoImageFilesystem *fs); |
|
static int ifs_fs_close(IsoImageFilesystem *fs); |
|
static int iso_file_source_new_ifs(IsoImageFilesystem *fs, |
|
IsoFileSource *parent, struct ecma119_dir_record *record, |
|
IsoFileSource **src, int flag); |
|
|
|
/** unique identifier for each image */ |
|
unsigned int fs_dev_id = 0; |
|
|
|
/** |
|
* Should the RR extensions be read? |
|
*/ |
|
enum read_rr_ext { |
|
RR_EXT_NO = 0, /*< Do not use RR extensions */ |
|
RR_EXT_110 = 1, /*< RR extensions conforming version 1.10 */ |
|
RR_EXT_112 = 2 /*< RR extensions conforming version 1.12 */ |
|
}; |
|
|
|
|
|
/** |
|
* Private data for the image IsoFilesystem |
|
*/ |
|
typedef struct |
|
{ |
|
/** DataSource from where data will be read */ |
|
IsoDataSource *src; |
|
|
|
/** unique id for the each image (filesystem instance) */ |
|
unsigned int id; |
|
|
|
/** |
|
* Counter of the times the filesystem has been openned still pending of |
|
* close. It is used to keep track of when we need to actually open or |
|
* close the IsoDataSource. |
|
*/ |
|
unsigned int open_count; |
|
|
|
uid_t uid; /**< Default uid when no RR */ |
|
gid_t gid; /**< Default uid when no RR */ |
|
mode_t dir_mode; /**< Default mode when no RR (only permissions) */ |
|
mode_t file_mode; |
|
|
|
int msgid; |
|
|
|
char *input_charset; /**< Input charset for RR names */ |
|
char *local_charset; /**< For RR names, will be set to the locale one */ |
|
|
|
/** |
|
* Enable or disable methods to automatically choose an input charset. |
|
* This eventually overrides input_charset. |
|
* |
|
* bit0= allow to set the input character set automatically from |
|
* attribute "isofs.cs" of root directory |
|
*/ |
|
int auto_input_charset; |
|
|
|
/** |
|
* Will be filled with the block lba of the extend for the root directory |
|
* of the hierarchy that will be read, either from the PVD (ISO, RR) or |
|
* from the SVD (Joliet) |
|
*/ |
|
uint32_t iso_root_block; |
|
|
|
/** |
|
* Will be filled with the block lba of the extend for the root directory, |
|
* as read from the PVM |
|
*/ |
|
uint32_t pvd_root_block; |
|
|
|
/** |
|
* Will be filled with the block lba of the extend for the root directory, |
|
* as read from the SVD |
|
*/ |
|
uint32_t svd_root_block; |
|
|
|
/** |
|
* Will be filled with the block lba of the extend for the root directory, |
|
* as read from the enhanced volume descriptor (ISO 9660:1999) |
|
*/ |
|
uint32_t evd_root_block; |
|
|
|
/** |
|
* If we need to read RR extensions. i.e., if the image contains RR |
|
* extensions, and the user wants to read them. |
|
*/ |
|
enum read_rr_ext rr; |
|
|
|
/** |
|
* 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. |
|
*/ |
|
uint8_t len_skp; |
|
|
|
/* Volume attributes */ |
|
char *volset_id; |
|
char *volume_id; /**< Volume identifier. */ |
|
char *publisher_id; /**< Volume publisher. */ |
|
char *data_preparer_id; /**< Volume data preparer. */ |
|
char *system_id; /**< Volume system identifier. */ |
|
char *application_id; /**< Volume application id */ |
|
char *copyright_file_id; |
|
char *abstract_file_id; |
|
char *biblio_file_id; |
|
char *creation_time; |
|
char *modification_time; |
|
char *expiration_time; |
|
char *effective_time; |
|
|
|
/* extension information */ |
|
|
|
/** |
|
* RR version being used in image. |
|
* 0 no RR extension, 1 RRIP 1.10, 2 RRIP 1.12 |
|
*/ |
|
enum read_rr_ext rr_version; |
|
|
|
/** If Joliet extensions are available on image */ |
|
unsigned int joliet : 1; |
|
|
|
/** If ISO 9660:1999 is available on image */ |
|
unsigned int iso1999 : 1; |
|
|
|
/** |
|
* See struct iso_read_opts. |
|
*/ |
|
int truncate_mode; |
|
int truncate_length; |
|
unsigned int ecma119_map : 2; |
|
|
|
/** Whether AAIP info shall be loaded if it is present. |
|
* 1 = yes , 0 = no |
|
*/ |
|
int aaip_load; |
|
|
|
/** Whether the MD5 array shall be read if available. |
|
* 2 = yes, but do not check tags , 1 = yes , 0 = no |
|
*/ |
|
int md5_load; |
|
|
|
/** Whether AAIP is present. Version major.minor = major * 100 + minor |
|
* Value -1 means that no AAIP ER was detected yet. |
|
*/ |
|
int aaip_version; |
|
|
|
/** |
|
* Start block of loaded session. |
|
*/ |
|
uint32_t session_lba; |
|
|
|
/** |
|
* Number of blocks of the volume, as reported in the PVM. |
|
*/ |
|
uint32_t nblocks; |
|
|
|
/* el-torito information */ |
|
unsigned int eltorito : 1; /* is el-torito available */ |
|
int num_bootimgs; |
|
unsigned char platform_ids[Libisofs_max_boot_imageS]; |
|
unsigned char id_strings[Libisofs_max_boot_imageS][28]; |
|
unsigned char selection_crits[Libisofs_max_boot_imageS][20]; |
|
unsigned char boot_flags[Libisofs_max_boot_imageS]; /* bit0= bootable */ |
|
unsigned char media_types[Libisofs_max_boot_imageS]; |
|
unsigned char partition_types[Libisofs_max_boot_imageS]; |
|
short load_segs[Libisofs_max_boot_imageS]; |
|
short load_sizes[Libisofs_max_boot_imageS]; |
|
/** Block addresses of for El-Torito boot images. |
|
Needed to recognize them when the get read from the directory tree. |
|
*/ |
|
uint32_t bootblocks[Libisofs_max_boot_imageS]; |
|
|
|
uint32_t catblock; /**< Block for El-Torito catalog */ |
|
off_t catsize; /* Size of boot catalog in bytes */ |
|
char *catcontent; |
|
|
|
/* Whether inode numbers from PX entries shall be discarded */ |
|
unsigned int make_new_ino : 1 ; |
|
|
|
/* Inode number generator counter. 32 bit because for Rock Ridge PX. */ |
|
uint32_t inode_counter; |
|
|
|
/* PX inode number status |
|
bit0= there were nodes with PX inode numbers |
|
bit1= there were nodes with PX but without inode numbers |
|
bit2= there were nodes without PX |
|
bit3= there were nodes with faulty PX |
|
*/ |
|
int px_ino_status; |
|
|
|
/* Which Rock Ridge error messages already have occurred |
|
bit0= Invalid PX entry |
|
bit1= Invalid TF entry |
|
bit2= New NM entry found without previous CONTINUE flag |
|
bit3= Invalid NM entry |
|
bit4= New SL entry found without previous CONTINUE flag |
|
bit5= Invalid SL entry |
|
bit6= Invalid CL entry, no child location / found in CL target |
|
bit7= Invalid PN entry |
|
bit8= Sparse files not supported |
|
bit9= SP entry found in a directory entry other than '.' entry of root |
|
bit10= ER entry found in a directory entry other than '.' entry of root |
|
bit11= Invalid AA entry |
|
bit12= Invalid AL entry |
|
bit13= Invalid ZF entry |
|
bit14= Rock Ridge PX entry is not present or invalid |
|
bit15= Incomplete NM |
|
bit16= Incomplete SL |
|
bit17= Charset conversion error |
|
bit18= Link without destination |
|
bit19= SL with a non-link file |
|
*/ |
|
int rr_err_reported; |
|
int rr_err_repeated; |
|
|
|
size_t joliet_ucs2_failures; |
|
|
|
} _ImageFsData; |
|
|
|
typedef struct image_fs_data ImageFileSourceData; |
|
|
|
/* IMPORTANT: Any change must be reflected by ifs_clone_src */ |
|
struct image_fs_data |
|
{ |
|
IsoImageFilesystem *fs; /**< reference to the image it belongs to */ |
|
IsoFileSource *parent; /**< reference to the parent (NULL if root) */ |
|
|
|
struct stat info; /**< filled struct stat */ |
|
char *name; /**< name of this file */ |
|
|
|
/** |
|
* Location of file extents. |
|
*/ |
|
struct iso_file_section *sections; |
|
int nsections; |
|
|
|
unsigned int opened : 2; /**< 0 not opened, 1 opened file, 2 opened dir */ |
|
|
|
#ifdef Libisofs_with_zliB |
|
uint8_t header_size_div4; |
|
uint8_t block_size_log2; |
|
uint32_t uncompressed_size; |
|
#endif |
|
|
|
/* info for content reading */ |
|
struct |
|
{ |
|
/** |
|
* - For regular files, once opened it points to a temporary data |
|
* buffer of 2048 bytes. |
|
* - For dirs, once opened it points to a IsoFileSource* array with |
|
* its children |
|
* - For symlinks, it points to link destination |
|
*/ |
|
void *content; |
|
|
|
/** |
|
* - For regular files, number of bytes already read. |
|
*/ |
|
off_t offset; |
|
} data; |
|
|
|
/** |
|
* malloc() storage for the string of AAIP fields which represent |
|
* ACLs and XFS-style Extended Attributes. (Not to be confused with |
|
* ECMA-119 Extended Attributes.) |
|
*/ |
|
unsigned char *aa_string; |
|
|
|
}; |
|
|
|
struct child_list |
|
{ |
|
IsoFileSource *file; |
|
struct child_list *next; |
|
}; |
|
|
|
void child_list_free(struct child_list *list) |
|
{ |
|
struct child_list *temp; |
|
struct child_list *next = list; |
|
while (next != NULL) { |
|
temp = next->next; |
|
iso_file_source_unref(next->file); |
|
free(next); |
|
next = temp; |
|
} |
|
} |
|
|
|
static |
|
char* ifs_get_path(IsoFileSource *src) |
|
{ |
|
ImageFileSourceData *data; |
|
data = src->data; |
|
|
|
if (data->parent == NULL) { |
|
return strdup(""); |
|
} else { |
|
char *path, *new_path; |
|
int pathlen; |
|
|
|
if (data->name == NULL) |
|
return NULL; |
|
path = ifs_get_path(data->parent); |
|
if (path == NULL) |
|
return NULL; |
|
pathlen = strlen(path); |
|
new_path = realloc(path, pathlen + strlen(data->name) + 2); |
|
if (new_path == NULL) { |
|
free(path); |
|
return NULL; |
|
} |
|
path= new_path; |
|
path[pathlen] = '/'; |
|
path[pathlen + 1] = '\0'; |
|
return strcat(path, data->name); |
|
} |
|
} |
|
|
|
static |
|
char* ifs_get_name(IsoFileSource *src) |
|
{ |
|
ImageFileSourceData *data; |
|
data = src->data; |
|
return data->name == NULL ? NULL : strdup(data->name); |
|
} |
|
|
|
static |
|
int ifs_lstat(IsoFileSource *src, struct stat *info) |
|
{ |
|
ImageFileSourceData *data; |
|
|
|
if (src == NULL || info == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
|
|
data = src->data; |
|
if (data == NULL) |
|
return ISO_NULL_POINTER; |
|
*info = data->info; |
|
return ISO_SUCCESS; |
|
} |
|
|
|
static |
|
int ifs_stat(IsoFileSource *src, struct stat *info) |
|
{ |
|
ImageFileSourceData *data; |
|
|
|
if (src == NULL || info == NULL || src->data == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
|
|
data = (ImageFileSourceData*)src->data; |
|
|
|
if (S_ISLNK(data->info.st_mode)) { |
|
/* TODO #00012 : support follow symlinks on image filesystem */ |
|
return ISO_FILE_BAD_PATH; |
|
} |
|
*info = data->info; |
|
return ISO_SUCCESS; |
|
} |
|
|
|
static |
|
int ifs_access(IsoFileSource *src) |
|
{ |
|
/* we always have access, it is controlled by DataSource */ |
|
return ISO_SUCCESS; |
|
} |
|
|
|
/** |
|
* Read all directory records in a directory, and creates an IsoFileSource for |
|
* each of them, storing them in the data field of the IsoFileSource for the |
|
* given dir. |
|
*/ |
|
static |
|
int read_dir(ImageFileSourceData *data) |
|
{ |
|
int ret; |
|
uint32_t size; |
|
uint32_t block; |
|
IsoImageFilesystem *fs; |
|
_ImageFsData *fsdata; |
|
struct ecma119_dir_record *record; |
|
uint8_t *buffer = NULL; |
|
IsoFileSource *child = NULL; |
|
uint32_t pos = 0; |
|
uint32_t tlen = 0; |
|
|
|
if (data == NULL) { |
|
ret = ISO_NULL_POINTER; goto ex; |
|
} |
|
|
|
LIBISO_ALLOC_MEM(buffer, uint8_t, BLOCK_SIZE); |
|
fs = data->fs; |
|
fsdata = fs->data; |
|
|
|
/* a dir has always a single extent */ |
|
block = data->sections[0].block; |
|
ret = fsdata->src->read_block(fsdata->src, block, buffer); |
|
if (ret < 0) { |
|
goto ex; |
|
} |
|
|
|
/* "." entry, get size of the dir and skip */ |
|
record = (struct ecma119_dir_record *)(buffer + pos); |
|
size = iso_read_bb(record->length, 4, NULL); |
|
tlen += record->len_dr[0]; |
|
pos += record->len_dr[0]; |
|
|
|
/* skip ".." */ |
|
record = (struct ecma119_dir_record *)(buffer + pos); |
|
tlen += record->len_dr[0]; |
|
pos += record->len_dr[0]; |
|
|
|
while (tlen < size) { |
|
|
|
record = (struct ecma119_dir_record *)(buffer + pos); |
|
if (pos == 2048 || record->len_dr[0] == 0) { |
|
/* |
|
* The directory entries are splitted in several blocks |
|
* read next block |
|
*/ |
|
ret = fsdata->src->read_block(fsdata->src, ++block, buffer); |
|
if (ret < 0) { |
|
goto ex; |
|
} |
|
tlen += 2048 - pos; |
|
pos = 0; |
|
continue; |
|
} |
|
|
|
/* (Vreixo:) |
|
* What about ignoring files with existence flag? |
|
* if (record->flags[0] & 0x01) |
|
* continue; |
|
* ts B20306 : >>> One should rather record that flag and write it |
|
* >>> to the new image. |
|
*/ |
|
|
|
#ifdef Libisofs_wrongly_skip_rr_moveD |
|
/* ts B20306 : |
|
This skipping by name is wrong resp. redundant: |
|
If no rr reading is enabled, then it is the only access point for |
|
the content of relocated directories. So one should not ignore it. |
|
If rr reading is enabled, then the RE entry of mkisofs' RR_MOVED |
|
will cause it to be skipped. |
|
*/ |
|
|
|
/* (Vreixo:) |
|
* For a extrange reason, mkisofs relocates directories under |
|
* a RR_MOVED dir. It seems that it is only used for that purposes, |
|
* and thus it should be removed from the iso tree before |
|
* generating a new image with libisofs, that don't uses it. |
|
*/ |
|
|
|
if (data->parent == NULL && record->len_fi[0] == 8 |
|
&& !strncmp((char*)record->file_id, "RR_MOVED", 8)) { |
|
|
|
iso_msg_debug(fsdata->msgid, "Skipping RR_MOVE entry."); |
|
|
|
tlen += record->len_dr[0]; |
|
pos += record->len_dr[0]; |
|
continue; |
|
} |
|
|
|
#endif /* Libisofs_wrongly_skip_rr_moveD */ |
|
|
|
/* |
|
* We pass a NULL parent instead of dir, to prevent the circular |
|
* reference from child to parent. |
|
*/ |
|
ret = iso_file_source_new_ifs(fs, NULL, record, &child, 0); |
|
if (ret < 0) { |
|
if (child) { |
|
/* |
|
* This can only happen with multi-extent files. |
|
*/ |
|
ImageFileSourceData *ifsdata = child->data; |
|
free(ifsdata->sections); |
|
free(ifsdata->name); |
|
free(ifsdata); |
|
free(child); |
|
} |
|
goto ex; |
|
} |
|
|
|
/* add to the child list */ |
|
if (ret == 1) { |
|
struct child_list *node; |
|
node = malloc(sizeof(struct child_list)); |
|
if (node == NULL) { |
|
iso_file_source_unref(child); |
|
{ret = ISO_OUT_OF_MEM; goto ex;} |
|
} |
|
/* |
|
* Note that we insert in reverse order. This leads to faster |
|
* addition here, but also when adding to the tree, as insertion |
|
* will be done, sorted, in the first position of the list. |
|
*/ |
|
node->next = data->data.content; |
|
node->file = child; |
|
data->data.content = node; |
|
child = NULL; |
|
} |
|
|
|
tlen += record->len_dr[0]; |
|
pos += record->len_dr[0]; |
|
} |
|
|
|
ret = ISO_SUCCESS; |
|
ex:; |
|
LIBISO_FREE_MEM(buffer); |
|
return ret; |
|
} |
|
|
|
static |
|
int ifs_open(IsoFileSource *src) |
|
{ |
|
int ret; |
|
ImageFileSourceData *data; |
|
|
|
if (src == NULL || src->data == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
data = (ImageFileSourceData*)src->data; |
|
|
|
if (data->opened) { |
|
return ISO_FILE_ALREADY_OPENED; |
|
} |
|
|
|
if (S_ISDIR(data->info.st_mode)) { |
|
/* ensure fs is openned */ |
|
ret = data->fs->open(data->fs); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
/* |
|
* Cache all directory entries. |
|
* This can waste more memory, but improves as disc is read in much more |
|
* sequencially way, thus reducing jump between tracks on disc |
|
*/ |
|
ret = read_dir(data); |
|
data->fs->close(data->fs); |
|
|
|
if (ret < 0) { |
|
/* free probably allocated children */ |
|
child_list_free((struct child_list*)data->data.content); |
|
} else { |
|
data->opened = 2; |
|
} |
|
|
|
return ret; |
|
} else if (S_ISREG(data->info.st_mode)) { |
|
/* ensure fs is openned */ |
|
ret = data->fs->open(data->fs); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
data->data.content = malloc(BLOCK_SIZE); |
|
if (data->data.content == NULL) { |
|
return ISO_OUT_OF_MEM; |
|
} |
|
data->data.offset = 0; |
|
data->opened = 1; |
|
} else { |
|
/* symlinks and special files inside image can't be openned */ |
|
return ISO_FILE_ERROR; |
|
} |
|
return ISO_SUCCESS; |
|
} |
|
|
|
static |
|
int ifs_close(IsoFileSource *src) |
|
{ |
|
ImageFileSourceData *data; |
|
|
|
if (src == NULL || src->data == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
data = (ImageFileSourceData*)src->data; |
|
|
|
if (!data->opened) { |
|
return ISO_FILE_NOT_OPENED; |
|
} |
|
|
|
if (data->opened == 2) { |
|
/* |
|
* close a dir, free all pending pre-allocated children. |
|
* not that we don't need to close the filesystem, it was already |
|
* closed |
|
*/ |
|
child_list_free((struct child_list*) data->data.content); |
|
data->data.content = NULL; |
|
data->opened = 0; |
|
} else if (data->opened == 1) { |
|
/* close regular file */ |
|
free(data->data.content); |
|
data->fs->close(data->fs); |
|
data->data.content = NULL; |
|
data->opened = 0; |
|
} else { |
|
/* TODO only dirs and files supported for now */ |
|
return ISO_ERROR; |
|
} |
|
|
|
return ISO_SUCCESS; |
|
} |
|
|
|
/** |
|
* Computes the block where the given offset should start. |
|
*/ |
|
static |
|
uint32_t block_from_offset(int nsections, struct iso_file_section *sections, |
|
off_t offset) |
|
{ |
|
int section = 0; |
|
off_t bytes = 0; |
|
|
|
do { |
|
if ( (offset - bytes) < (off_t) sections[section].size ) { |
|
return sections[section].block + (offset - bytes) / BLOCK_SIZE; |
|
} else { |
|
bytes += (off_t) sections[section].size; |
|
section++; |
|
} |
|
|
|
} while(section < nsections); |
|
return 0; /* should never happen */ |
|
} |
|
|
|
/** |
|
* Get the size available for reading on the corresponding block |
|
*/ |
|
static |
|
uint32_t size_available(int nsections, struct iso_file_section *sections, |
|
off_t offset) |
|
{ |
|
int section = 0; |
|
off_t bytes = 0; |
|
|
|
do { |
|
if ( (offset - bytes) < (off_t) sections[section].size ) { |
|
uint32_t curr_section_offset = (uint32_t)(offset - bytes); |
|
uint32_t curr_section_left = sections[section].size - curr_section_offset; |
|
uint32_t available = BLOCK_SIZE - curr_section_offset % BLOCK_SIZE; |
|
return MIN(curr_section_left, available); |
|
} else { |
|
bytes += (off_t) sections[section].size; |
|
section++; |
|
} |
|
|
|
} while(section < nsections); |
|
return 0; /* should never happen */ |
|
} |
|
|
|
/** |
|
* Get the block offset for reading the given file offset |
|
*/ |
|
static |
|
uint32_t block_offset(int nsections, struct iso_file_section *sections, |
|
off_t offset) |
|
{ |
|
int section = 0; |
|
off_t bytes = 0; |
|
|
|
|
|
do { |
|
if ( (offset - bytes) < (off_t) sections[section].size ) { |
|
return (uint32_t)(offset - bytes) % BLOCK_SIZE; |
|
} else { |
|
bytes += (off_t) sections[section].size; |
|
section++; |
|
} |
|
|
|
} while(section < nsections); |
|
return 0; /* should never happen */ |
|
} |
|
|
|
/** |
|
* Attempts to read up to count bytes from the given source into |
|
* the buffer starting at buf. |
|
* |
|
* The file src must be open() before calling this, and close() when no |
|
* more needed. Not valid for dirs. On symlinks it reads the destination |
|
* file. |
|
* |
|
* @return |
|
* number of bytes read, 0 if EOF, < 0 on error |
|
* Error codes: |
|
* ISO_FILE_ERROR |
|
* ISO_NULL_POINTER |
|
* ISO_FILE_NOT_OPENED |
|
* ISO_FILE_IS_DIR |
|
* ISO_OUT_OF_MEM |
|
* ISO_INTERRUPTED |
|
*/ |
|
static |
|
int ifs_read(IsoFileSource *src, void *buf, size_t count) |
|
{ |
|
int ret; |
|
ImageFileSourceData *data; |
|
uint32_t read = 0; |
|
|
|
if (src == NULL || src->data == NULL || buf == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
if (count == 0) { |
|
return ISO_WRONG_ARG_VALUE; |
|
} |
|
data = (ImageFileSourceData*)src->data; |
|
|
|
if (!data->opened) { |
|
return ISO_FILE_NOT_OPENED; |
|
} else if (data->opened != 1) { |
|
return ISO_FILE_IS_DIR; |
|
} |
|
|
|
while (read < count && data->data.offset < data->info.st_size) { |
|
size_t bytes; |
|
uint8_t *orig; |
|
|
|
if (block_offset(data->nsections, data->sections, data->data.offset) == 0) { |
|
/* we need to buffer next block */ |
|
uint32_t block; |
|
_ImageFsData *fsdata; |
|
|
|
if (data->data.offset >= data->info.st_size) { |
|
/* EOF */ |
|
break; |
|
} |
|
fsdata = data->fs->data; |
|
block = block_from_offset(data->nsections, data->sections, |
|
data->data.offset); |
|
ret = fsdata->src->read_block(fsdata->src, block, |
|
data->data.content); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
} |
|
|
|
/* how much can I read */ |
|
bytes = MIN(size_available(data->nsections, data->sections, data->data.offset), |
|
count - read); |
|
if (data->data.offset + (off_t)bytes > data->info.st_size) { |
|
bytes = data->info.st_size - data->data.offset; |
|
} |
|
orig = data->data.content; |
|
orig += block_offset(data->nsections, data->sections, data->data.offset); |
|
memcpy((uint8_t*)buf + read, orig, bytes); |
|
read += bytes; |
|
data->data.offset += (off_t)bytes; |
|
} |
|
return read; |
|
} |
|
|
|
static |
|
off_t ifs_lseek(IsoFileSource *src, off_t offset, int flag) |
|
{ |
|
ImageFileSourceData *data; |
|
|
|
if (src == NULL) { |
|
return (off_t)ISO_NULL_POINTER; |
|
} |
|
if (offset < (off_t)0) { |
|
return (off_t)ISO_WRONG_ARG_VALUE; |
|
} |
|
|
|
data = src->data; |
|
|
|
if (!data->opened) { |
|
return (off_t)ISO_FILE_NOT_OPENED; |
|
} else if (data->opened != 1) { |
|
return (off_t)ISO_FILE_IS_DIR; |
|
} |
|
|
|
switch (flag) { |
|
case 0: /* SEEK_SET */ |
|
data->data.offset = offset; |
|
break; |
|
case 1: /* SEEK_CUR */ |
|
data->data.offset += offset; |
|
break; |
|
case 2: /* SEEK_END */ |
|
/* do this make sense? */ |
|
data->data.offset = data->info.st_size + offset; |
|
break; |
|
default: |
|
return (off_t)ISO_WRONG_ARG_VALUE; |
|
} |
|
|
|
/* |
|
* We check for block_offset != 0 because if it is already 0, the block |
|
* will be read from image in the read function |
|
*/ |
|
if (block_offset(data->nsections, data->sections, data->data.offset) != 0) { |
|
/* we need to buffer the block */ |
|
uint32_t block; |
|
_ImageFsData *fsdata; |
|
|
|
if (data->data.offset < data->info.st_size) { |
|
int ret; |
|
fsdata = data->fs->data; |
|
block = block_from_offset(data->nsections, data->sections, |
|
data->data.offset); |
|
ret = fsdata->src->read_block(fsdata->src, block, |
|
data->data.content); |
|
if (ret < 0) { |
|
return (off_t)ret; |
|
} |
|
} |
|
} |
|
return data->data.offset; |
|
} |
|
|
|
static |
|
int ifs_readdir(IsoFileSource *src, IsoFileSource **child) |
|
{ |
|
ImageFileSourceData *data, *cdata; |
|
struct child_list *children; |
|
|
|
if (src == NULL || src->data == NULL || child == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
data = (ImageFileSourceData*)src->data; |
|
|
|
if (!data->opened) { |
|
return ISO_FILE_NOT_OPENED; |
|
} else if (data->opened != 2) { |
|
return ISO_FILE_IS_NOT_DIR; |
|
} |
|
|
|
/* return the first child and free it */ |
|
if (data->data.content == NULL) { |
|
return 0; /* EOF */ |
|
} |
|
|
|
children = (struct child_list*)data->data.content; |
|
*child = children->file; |
|
cdata = (ImageFileSourceData*)(*child)->data; |
|
|
|
/* set the ref to the parent */ |
|
cdata->parent = src; |
|
iso_file_source_ref(src); |
|
|
|
/* free the first element of the list */ |
|
data->data.content = children->next; |
|
free(children); |
|
|
|
return ISO_SUCCESS; |
|
} |
|
|
|
/** |
|
* Read the destination of a symlink. You don't need to open the file |
|
* to call this. |
|
* |
|
* @param buf |
|
* allocated buffer of at least bufsiz bytes. |
|
* The dest. will be copied there, and it will be NULL-terminated |
|
* @param bufsiz |
|
* characters to be copied. Destination link will be truncated if |
|
* it is larger than given size. This include the \0 character. |
|
* @return |
|
* 1 on success, < 0 on error |
|
* Error codes: |
|
* ISO_FILE_ERROR |
|
* ISO_NULL_POINTER |
|
* ISO_WRONG_ARG_VALUE -> if bufsiz <= 0 |
|
* ISO_FILE_IS_NOT_SYMLINK |
|
* ISO_OUT_OF_MEM |
|
* ISO_FILE_BAD_PATH |
|
* ISO_FILE_DOESNT_EXIST |
|
* |
|
*/ |
|
static |
|
int ifs_readlink(IsoFileSource *src, char *buf, size_t bufsiz) |
|
{ |
|
char *dest; |
|
size_t len; |
|
int ret; |
|
ImageFileSourceData *data; |
|
|
|
if (src == NULL || buf == NULL || src->data == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
|
|
if (bufsiz <= 0) { |
|
return ISO_WRONG_ARG_VALUE; |
|
} |
|
|
|
data = (ImageFileSourceData*)src->data; |
|
|
|
if (!S_ISLNK(data->info.st_mode)) { |
|
return ISO_FILE_IS_NOT_SYMLINK; |
|
} |
|
|
|
dest = (char*)data->data.content; |
|
len = strlen(dest); |
|
|
|
ret = ISO_SUCCESS; |
|
if (len >= bufsiz) { |
|
ret = ISO_RR_PATH_TOO_LONG; |
|
len = bufsiz - 1; |
|
} |
|
strncpy(buf, dest, len); |
|
buf[len] = '\0'; |
|
return ret; |
|
} |
|
|
|
static |
|
IsoFilesystem* ifs_get_filesystem(IsoFileSource *src) |
|
{ |
|
ImageFileSourceData *data; |
|
|
|
if (src == NULL) { |
|
return NULL; |
|
} |
|
|
|
data = src->data; |
|
return data->fs; |
|
} |
|
|
|
static |
|
void ifs_free(IsoFileSource *src) |
|
{ |
|
ImageFileSourceData *data; |
|
|
|
data = src->data; |
|
|
|
/* close the file if it is already openned */ |
|
if (data->opened) { |
|
src->class->close(src); |
|
} |
|
|
|
/* free destination if it is a link */ |
|
if (S_ISLNK(data->info.st_mode)) { |
|
free(data->data.content); |
|
} |
|
iso_filesystem_unref(data->fs); |
|
if (data->parent != NULL) { |
|
iso_file_source_unref(data->parent); |
|
} |
|
|
|
free(data->sections); |
|
free(data->name); |
|
if (data->aa_string != NULL) |
|
free(data->aa_string); |
|
free(data); |
|
} |
|
|
|
|
|
static |
|
int ifs_get_aa_string(IsoFileSource *src, unsigned char **aa_string, int flag) |
|
{ |
|
size_t len; |
|
ImageFileSourceData *data; |
|
|
|
data = src->data; |
|
|
|
if ((flag & 1) || data->aa_string == NULL) { |
|
*aa_string = data->aa_string; |
|
data->aa_string = NULL; |
|
} else { |
|
len = aaip_count_bytes(data->aa_string, 0); |
|
*aa_string = calloc(len, 1); |
|
if (*aa_string == NULL) |
|
return ISO_OUT_OF_MEM; |
|
memcpy(*aa_string, data->aa_string, len); |
|
} |
|
return 1; |
|
} |
|
|
|
static |
|
int ifs_clone_src(IsoFileSource *old_source, |
|
IsoFileSource **new_source, int flag) |
|
{ |
|
IsoFileSource *src = NULL; |
|
ImageFileSourceData *old_data, *new_data = NULL; |
|
char *new_name = NULL; |
|
struct iso_file_section *new_sections = NULL; |
|
void *new_aa_string = NULL; |
|
int i, ret; |
|
|
|
if (flag) |
|
return ISO_STREAM_NO_CLONE; /* unknown option required */ |
|
|
|
old_data = (ImageFileSourceData *) old_source->data; |
|
*new_source = NULL; |
|
src = calloc(1, sizeof(IsoFileSource)); |
|
if (src == NULL) |
|
goto no_mem; |
|
new_name = strdup(old_data->name); |
|
if (new_name == NULL) |
|
goto no_mem; |
|
new_data = calloc(1, sizeof(ImageFileSourceData)); |
|
|
|
if (new_data == NULL) |
|
goto no_mem; |
|
if (old_data->nsections > 0) { |
|
new_sections = calloc(old_data->nsections, |
|
sizeof(struct iso_file_section)); |
|
if (new_sections == NULL) |
|
goto no_mem; |
|
} |
|
ret = aaip_xinfo_cloner(old_data->aa_string, &new_aa_string, 0); |
|
if (ret < 0) |
|
goto no_mem; |
|
|
|
new_data->fs = old_data->fs; |
|
|
|
new_data->parent = old_data->parent; |
|
|
|
memcpy(&(new_data->info), &(old_data->info), sizeof(struct stat)); |
|
new_data->name = new_name; |
|
new_data->sections = new_sections; |
|
new_data->nsections = old_data->nsections; |
|
for (i = 0; i < new_data->nsections; i++) |
|
memcpy(new_data->sections + i, old_data->sections + i, |
|
sizeof(struct iso_file_section)); |
|
new_data->opened = old_data->opened; |
|
#ifdef Libisofs_with_zliB |
|
new_data->header_size_div4 = old_data->header_size_div4; |
|
new_data->block_size_log2 = old_data->block_size_log2; |
|
new_data->uncompressed_size = old_data->uncompressed_size; |
|
#endif |
|
new_data->data.content = NULL; |
|
new_data->aa_string = (unsigned char *) new_aa_string; |
|
|
|
src->class = old_source->class; |
|
src->refcount = 1; |
|
src->data = new_data; |
|
*new_source = src; |
|
iso_file_source_ref(new_data->parent); |
|
iso_filesystem_ref(new_data->fs); |
|
return ISO_SUCCESS; |
|
no_mem:; |
|
if (src != NULL) |
|
free((char *) src); |
|
if (new_data != NULL) |
|
free((char *) new_data); |
|
if (new_name != NULL) |
|
free(new_name); |
|
if (new_sections != NULL) |
|
free((char *) new_sections); |
|
if (new_aa_string != NULL) |
|
aaip_xinfo_func(new_aa_string, 1); |
|
return ISO_OUT_OF_MEM; |
|
} |
|
|
|
|
|
IsoFileSourceIface ifs_class = { |
|
|
|
2, /* version */ |
|
ifs_get_path, |
|
ifs_get_name, |
|
ifs_lstat, |
|
ifs_stat, |
|
ifs_access, |
|
ifs_open, |
|
ifs_close, |
|
ifs_read, |
|
ifs_readdir, |
|
ifs_readlink, |
|
ifs_get_filesystem, |
|
ifs_free, |
|
ifs_lseek, |
|
ifs_get_aa_string, |
|
ifs_clone_src |
|
|
|
}; |
|
|
|
|
|
/* Used from libisofs/stream.c : iso_stream_get_src_zf() */ |
|
int iso_ifs_source_get_zf(IsoFileSource *src, int *header_size_div4, |
|
int *block_size_log2, uint32_t *uncompressed_size, |
|
int flag) |
|
{ |
|
|
|
#ifdef Libisofs_with_zliB |
|
|
|
ImageFileSourceData *data; |
|
|
|
if (src->class != &ifs_class) |
|
return 0; |
|
data = src->data; |
|
*header_size_div4 = data->header_size_div4; |
|
*block_size_log2 = data->block_size_log2; |
|
*uncompressed_size = data->uncompressed_size; |
|
return 1; |
|
|
|
#else |
|
|
|
return 0; |
|
|
|
#endif /* ! Libisofs_with_zliB */ |
|
} |
|
|
|
|
|
static |
|
int make_hopefully_unique_name(_ImageFsData *fsdata, |
|
char *str, size_t len, char **name) |
|
{ |
|
int ret, name_len, i; |
|
char c, *smashed = NULL, md5[16]; |
|
void *md5_context = NULL; |
|
|
|
/* Shorten so that 32 characters of MD5 fit. |
|
If shorter than 8, pad up to 8 by '_'. |
|
Smash characters to [0-9A-Za-z_.]. |
|
Append MD5 of original str as hex digits. |
|
*/ |
|
name_len = len > 223 ? 223 : len; |
|
LIBISO_ALLOC_MEM(smashed, char, (name_len >= 8 ? name_len : 8) + 32 + 1); |
|
memcpy(smashed, str, name_len); |
|
for (; name_len < 8; name_len++) |
|
smashed[name_len] = '_'; |
|
smashed[name_len] = 0; |
|
for (i = 0; i < name_len; i++) { |
|
c = smashed[i]; |
|
if (c == '.' || (c >= '0' && c <= '9') || |
|
c == '_' || (c >= 'a' && c <= 'z')) |
|
continue; |
|
smashed[i] = '_'; |
|
} |
|
ret = iso_md5_start(&md5_context); |
|
if (ret != 1) |
|
goto ex; |
|
ret = iso_md5_compute(md5_context, str, len); |
|
if (ret != 1) |
|
goto ex; |
|
ret = iso_md5_end(&md5_context, md5); |
|
if (ret != 1) |
|
goto ex; |
|
for (i = 0; i < 16; i++) |
|
sprintf(smashed + i * 2 + name_len, "%2.2x", |
|
((unsigned char *) md5)[i]); |
|
name_len += 32; |
|
smashed[name_len] = 0; |
|
*name = smashed; smashed = NULL; |
|
|
|
ret = ISO_SUCCESS; |
|
ex: |
|
LIBISO_FREE_MEM(smashed); |
|
if (md5_context != NULL) |
|
iso_md5_end(&md5_context, md5); |
|
return ret; |
|
} |
|
|
|
|
|
/** |
|
* Read a file name from a directory record, doing the needed charset |
|
* conversion |
|
*/ |
|
static |
|
char *get_name(_ImageFsData *fsdata, char *str, size_t len) |
|
{ |
|
int ret; |
|
char *name = NULL, *from_ucs = NULL; |
|
|
|
if (strcmp(fsdata->local_charset, fsdata->input_charset)) { |
|
/* charset conversion needed */ |
|
ret = strnconv(str, fsdata->input_charset, fsdata->local_charset, len, |
|
&name); |
|
if (ret == 1) { |
|
if (fsdata->iso_root_block == fsdata->svd_root_block) { |
|
/* Reading from Joliet : Check whether UTF-16 was needed */ |
|
ret = strnconv(str, "UCS-2BE", fsdata->local_charset, |
|
len, &from_ucs); |
|
if (ret == 1) |
|
ret = (strcmp(name, from_ucs) == 0); |
|
if (ret != 1) { |
|
fsdata->joliet_ucs2_failures++; |
|
if (fsdata->joliet_ucs2_failures <= |
|
ISO_JOLIET_UCS2_WARN_MAX) |
|
iso_msg_submit(-1, ISO_NAME_NOT_UCS2, 0, |
|
"Joliet filename valid only with character set UTF-16 : \"%s\"", |
|
name); |
|
|
|
} |
|
if (from_ucs != NULL) |
|
free(from_ucs); |
|
} |
|
return name; |
|
} else { |
|
ret = iso_msg_submit(fsdata->msgid, ISO_FILENAME_WRONG_CHARSET, ret, |
|
"Cannot convert from charset %s to %s", |
|
fsdata->input_charset, fsdata->local_charset); |
|
if (ret < 0) { |
|
return NULL; /* aborted */ |
|
} |
|
/* fallback */ |
|
ret = make_hopefully_unique_name(fsdata, str, len, &name); |
|
if (ret == ISO_SUCCESS) |
|
return name; |
|
return NULL; |
|
} |
|
} |
|
|
|
/* we reach here when the charset conversion is not needed */ |
|
|
|
name = malloc(len + 1); |
|
if (name == NULL) { |
|
return NULL; |
|
} |
|
memcpy(name, str, len); |
|
name[len] = '\0'; |
|
return name; |
|
} |
|
|
|
|
|
static |
|
int iso_rr_msg_submit(_ImageFsData *fsdata, int rr_err_bit, |
|
int errcode, int causedby, const char *msg) |
|
{ |
|
int ret; |
|
|
|
if ((fsdata->rr_err_reported & (1 << rr_err_bit)) && |
|
(fsdata->rr_err_repeated & (1 << rr_err_bit))) { |
|
if (iso_msg_is_abort(errcode)) |
|
return ISO_CANCELED; |
|
return 0; |
|
} |
|
if (fsdata->rr_err_reported & (1 << rr_err_bit)) { |
|
ret = iso_msg_submit(fsdata->msgid, errcode, causedby, |
|
"MORE THAN ONCE : %s", msg); |
|
fsdata->rr_err_repeated |= (1 << rr_err_bit); |
|
} else { |
|
ret = iso_msg_submit(fsdata->msgid, errcode, causedby, "%s", msg); |
|
fsdata->rr_err_reported |= (1 << rr_err_bit); |
|
} |
|
return ret; |
|
} |
|
|
|
|
|
/** |
|
* |
|
* @param src |
|
* if not-NULL, it points to a multi-extent file returned by a previous |
|
* call to this function. |
|
* @param flag |
|
* bit0= this is the root node attribute load call |
|
* (parameter parent is not reliable for this) |
|
* bit1= this is a call caused by CL. Do not obey CL again. |
|
* @return |
|
* 2 node is still incomplete (multi-extent) |
|
* 1 success, 0 record ignored (not an error, can be a relocated dir), |
|
* < 0 error |
|
*/ |
|
static |
|
int iso_file_source_new_ifs(IsoImageFilesystem *fs, IsoFileSource *parent, |
|
struct ecma119_dir_record *record, |
|
IsoFileSource **src, int flag) |
|
{ |
|
int ret, ecma119_map, skip_nm = 0; |
|
struct stat atts; |
|
time_t recorded; |
|
_ImageFsData *fsdata; |
|
IsoFileSource *ifsrc = NULL; |
|
ImageFileSourceData *ifsdata = NULL; |
|
|
|
int namecont = 0; /* 1 if found a NM with CONTINUE flag */ |
|
char *name = NULL; |
|
|
|
/* 1 if found a SL with CONTINUE flag, |
|
* 2 if found a component with continue flag */ |
|
int linkdestcont = 0; |
|
char *linkdest = NULL; |
|
|
|
uint32_t relocated_dir = 0; |
|
|
|
unsigned char *aa_string = NULL; |
|
size_t aa_size = 0, aa_len = 0, prev_field = 0; |
|
int aa_done = 0; |
|
char *msg = NULL; |
|
uint8_t *buffer = NULL; |
|
char *cpt; |
|
|
|
int has_px = 0; |
|
|
|
#ifdef Libisofs_with_zliB |
|
uint8_t zisofs_alg[2], zisofs_hs4 = 0, zisofs_bsl2 = 0; |
|
uint32_t zisofs_usize = 0; |
|
#endif |
|
|
|
if (fs == NULL || fs->data == NULL || record == NULL || src == NULL) { |
|
ret = ISO_NULL_POINTER; goto ex; |
|
} |
|
|
|
fsdata = (_ImageFsData*)fs->data; |
|
|
|
memset(&atts, 0, sizeof(struct stat)); |
|
atts.st_nlink = 1; |
|
|
|
/* Set preliminary file type */ |
|
if (record->flags[0] & 0x02) { |
|
atts.st_mode = S_IFDIR; |
|
} else { |
|
atts.st_mode = S_IFREG; |
|
} |
|
|
|
/* |
|
* First of all, check for unsupported ECMA-119 features |
|
*/ |
|
|
|
/* check for unsupported interleaved mode */ |
|
if (record->file_unit_size[0] || record->interleave_gap_size[0]) { |
|
iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_ECMA119, 0, |
|
"Unsupported image. This image has at least one file recorded " |
|
"in interleaved mode. We do not support this mode, as we think " |
|
"it is not used. If you are reading this, then we are wrong :) " |
|
"Please contact libisofs developers, so we can fix this."); |
|
{ret = ISO_UNSUPPORTED_ECMA119; goto ex;} |
|
} |
|
|
|
/* TODO #00013 : check for unsupported flags when reading a dir record */ |
|
|
|
/* |
|
* If src is not-NULL, it refers to more extents of this file. We ensure |
|
* name matches, otherwise it means we are dealing with wrong image |
|
*/ |
|
if (*src != NULL) { |
|
ImageFileSourceData* data = (*src)->data; |
|
char* new_name = get_name(fsdata, (char*)record->file_id, record->len_fi[0]); |
|
if (new_name == NULL) { |
|
iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, |
|
"Cannot retrieve file name"); |
|
{ret = ISO_WRONG_ECMA119; goto ex;} |
|
} |
|
if (strcmp(new_name, data->name)) { |
|
iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, |
|
"Multi-extent file lacks last entry."); |
|
free(new_name); |
|
{ret = ISO_WRONG_ECMA119; goto ex;} |
|
} |
|
free(new_name); |
|
} |
|
|
|
/* check for multi-extent */ |
|
if (record->flags[0] & 0x80) { |
|
iso_msg_debug(fsdata->msgid, "Found multi-extent file"); |
|
|
|
/* |
|
* Directory entries can only have one section (ECMA-119, 6.8.1) |
|
*/ |
|
if ((record->flags[0] & 0x02) || (flag & 1)) { |
|
iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, |
|
"Directories with more than one section are not allowed."); |
|
{ret = ISO_WRONG_ECMA119; goto ex;} |
|
} |
|
|
|
if (*src == NULL) { |
|
ifsdata = calloc(1, sizeof(ImageFileSourceData)); |
|
if (ifsdata == NULL) { |
|
ret = ISO_OUT_OF_MEM; |
|
goto ifs_cleanup; |
|
} |
|
ifsrc = calloc(1, sizeof(IsoFileSource)); |
|
if (ifsrc == NULL) { |
|
ret = ISO_OUT_OF_MEM; |
|
goto ifs_cleanup; |
|
} |
|
ifsrc->data = ifsdata; |
|
ifsdata->name = get_name(fsdata, (char*)record->file_id, record->len_fi[0]); |
|
if (ifsdata->name == NULL) { |
|
iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, |
|
"Cannot retrieve file name"); |
|
ret = ISO_WRONG_ECMA119; |
|
goto ifs_cleanup; |
|
} |
|
|
|
*src = ifsrc; |
|
} else { |
|
ifsdata = (*src)->data; |
|
} |
|
|
|
/* store current extent */ |
|
ifsdata->sections = realloc(ifsdata->sections, |
|
(1 + ifsdata->nsections) * sizeof(struct iso_file_section)); |
|
if (ifsdata->sections == NULL) { |
|
free(ifsdata->name); |
|
ret = ISO_OUT_OF_MEM; |
|
goto ifs_cleanup; |
|
} |
|
ifsdata->sections[ifsdata->nsections].block = |
|
iso_read_bb(record->block, 4, NULL) + record->len_xa[0]; |
|
ifsdata->sections[ifsdata->nsections].size = iso_read_bb(record->length, 4, NULL); |
|
|
|
ifsdata->info.st_size += (off_t) ifsdata->sections[ifsdata->nsections].size; |
|
ifsdata->nsections++; |
|
{ret = 2; goto ex;} |
|
} |
|
|
|
/* |
|
* The idea is to read all the RR entries (if we want to do that and RR |
|
* extensions exist on image), storing the info we want from that. |
|
* Then, we need some sanity checks. |
|
* Finally, we select what kind of node it is, and set values properly. |
|
*/ |
|
|
|
if (fsdata->rr) { |
|
struct susp_sys_user_entry *sue; |
|
SuspIterator *iter; |
|
|
|
|
|
iter = susp_iter_new(fsdata->src, record, |
|
fsdata->session_lba + fsdata->nblocks, |
|
fsdata->len_skp, fsdata->msgid); |
|
if (iter == NULL) { |
|
{ret = ISO_OUT_OF_MEM; goto ex;} |
|
} |
|
|
|
while ((ret = susp_iter_next(iter, &sue, 0)) > 0) { |
|
|
|
/* ignore entries from different version */ |
|
if (sue->version[0] != 1) |
|
continue; |
|
|
|
if (SUSP_SIG(sue, 'P', 'X')) { |
|
has_px = 1; |
|
ret = read_rr_PX(sue, &atts); |
|
if (ret < 0) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 0, ISO_WRONG_RR_WARN, ret, |
|
"Invalid PX entry"); |
|
fsdata->px_ino_status |= 8; |
|
} if (ret == 2) { |
|
if (fsdata->inode_counter < atts.st_ino) |
|
fsdata->inode_counter = atts.st_ino; |
|
fsdata->px_ino_status |= 1; |
|
|
|
} else { |
|
fsdata->px_ino_status |= 2; |
|
} |
|
|
|
} else if (SUSP_SIG(sue, 'T', 'F')) { |
|
ret = read_rr_TF(sue, &atts); |
|
if (ret < 0) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 1, ISO_WRONG_RR_WARN, ret, |
|
"Invalid TF entry"); |
|
} |
|
} else if (SUSP_SIG(sue, 'N', 'M')) { |
|
if (skip_nm) |
|
continue; /* in NM error bailout mode */ |
|
|
|
if (name != NULL && namecont == 0) { |
|
/* ups, RR standard violation */ |
|
ret = iso_rr_msg_submit(fsdata, 2, ISO_WRONG_RR_WARN, 0, |
|
"New NM entry found without previous" |
|
"CONTINUE flag. Ignored"); |
|
skip_nm = 1; |
|
continue; |
|
} |
|
ret = read_rr_NM(sue, &name, &namecont); |
|
if (ret < 0) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 3, ISO_WRONG_RR_WARN, ret, |
|
"Invalid NM entry"); |
|
continue; |
|
} |
|
if (name != NULL) if (strlen(name) > 4095) { |
|
/* Preliminarily truncate totally oversized name */ |
|
ret = iso_rr_msg_submit(fsdata, 3, ISO_WRONG_RR_WARN, ret, |
|
"Totally oversized NM list"); |
|
skip_nm = 1; |
|
continue; |
|
} |
|
|
|
#ifdef Libisofs_syslinux_tesT |
|
|
|
if (name != NULL && !namecont) { |
|
struct device syslinux_dev; |
|
struct iso_sb_info syslinux_sbi; |
|
struct fs_info syslinux_fsi; |
|
char *syslinux_name = NULL; |
|
int syslinux_name_len; |
|
|
|
syslinux_dev.src = fsdata->src; |
|
memset(&(syslinux_sbi.root), 0, 256); |
|
syslinux_sbi.do_rr = 1; |
|
syslinux_sbi.susp_skip = 0; |
|
syslinux_fsi.fs_dev = &syslinux_dev; |
|
syslinux_fsi.fs_info = &syslinux_sbi; |
|
ret = susp_rr_get_nm(&syslinux_fsi, (char *) record, |
|
&syslinux_name, &syslinux_name_len); |
|
if (ret == 1) { |
|
if (name == NULL || syslinux_name == NULL) |
|
fprintf(stderr, "################ Hoppla. NULL\n"); |
|
else if(strcmp(syslinux_name, name) != 0) |
|
fprintf(stderr, |
|
"################ libisofs '%s' != '%s' susp_rr_get_nm()\n", |
|
name, syslinux_name); |
|
} else if (ret == 0) { |
|
fprintf(stderr, |
|
"################ '%s' not found by susp_rr_get_nm()\n", name); |
|
} else { |
|
fprintf(stderr, "################ 'susp_rr_get_nm() returned error\n"); |
|
} |
|
if (syslinux_name != NULL) |
|
free(syslinux_name); |
|
|
|
} |
|
|
|
#endif /* Libisofs_syslinux_tesT */ |
|
|
|
|
|
} else if (SUSP_SIG(sue, 'S', 'L')) { |
|
if (linkdest != NULL && linkdestcont == 0) { |
|
/* ups, RR standard violation */ |
|
ret = iso_rr_msg_submit(fsdata, 4, ISO_WRONG_RR_WARN, 0, |
|
"New SL entry found without previous" |
|
"CONTINUE flag. Ignored"); |
|
continue; |
|
} |
|
ret = read_rr_SL(sue, &linkdest, &linkdestcont); |
|
if (ret < 0) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 5, ISO_WRONG_RR_WARN, ret, |
|
"Invalid SL entry"); |
|
} |
|
} else if (SUSP_SIG(sue, 'R', 'E')) { |
|
/* |
|
* this directory entry refers to a relocated directory. |
|
* We simply ignore it, as it will be correctly handled |
|
* when found the CL |
|
*/ |
|
susp_iter_free(iter); |
|
free(name); |
|
if (flag & 1) { |
|
ret = iso_rr_msg_submit(fsdata, 3, ISO_NO_ROOT_DIR, 0, |
|
"Root directory is marked by RRIP RE as relocated"); |
|
ret= ISO_NO_ROOT_DIR; |
|
goto ex; |
|
} |
|
{ret = 0; goto ex;} /* it's not an error */ |
|
} else if (SUSP_SIG(sue, 'C', 'L') && (flag & 2)) { |
|
ret = iso_rr_msg_submit(fsdata, 6, ISO_WRONG_RR, 0, |
|
"Invalid CL entry, found in CL target"); |
|
|
|
} else if (SUSP_SIG(sue, 'C', 'L')) { |
|
/* |
|
* This entry is a placeholder for a relocated dir. |
|
* We need to ignore other entries, with the exception of NM. |
|
* Then we create a directory node that represents the |
|
* relocated dir, and iterate over its children. |
|
*/ |
|
relocated_dir = iso_read_bb(sue->data.CL.child_loc, 4, NULL); |
|
if (relocated_dir == 0) { |
|
ret = iso_rr_msg_submit(fsdata, 6, ISO_WRONG_RR, 0, |
|
"Invalid CL entry, no child location"); |
|
break; |
|
} |
|
} else if (SUSP_SIG(sue, 'P', 'N')) { |
|
ret = read_rr_PN(sue, &atts); |
|
if (ret < 0) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 7, ISO_WRONG_RR_WARN, ret, |
|
"Invalid PN entry"); |
|
} |
|
} else if (SUSP_SIG(sue, 'S', 'F')) { |
|
ret = iso_rr_msg_submit(fsdata, 8, ISO_UNSUPPORTED_RR, 0, |
|
"Sparse files not supported."); |
|
break; |
|
} else if (SUSP_SIG(sue, 'R', 'R')) { |
|
/* This was an optional flag byte in RRIP 1.09 which told the |
|
reader what other RRIP fields to expect. |
|
mkisofs emits it. We don't. |
|
*/ |
|
continue; |
|
} else if (SUSP_SIG(sue, 'S', 'P')) { |
|
/* |
|
* Ignore this, to prevent the hint message, if we are dealing |
|
* with root node (SP is only valid in "." of root node) |
|
*/ |
|
if (!(flag & 1)) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 9, ISO_WRONG_RR, 0, |
|
"SP entry found in a directory entry other " |
|
"than '.' entry of root node"); |
|
} |
|
continue; |
|
} else if (SUSP_SIG(sue, 'E', 'R')) { |
|
/* |
|
* Ignore this, to prevent the hint message, if we are dealing |
|
* with root node (ER is only valid in "." of root node) |
|
*/ |
|
if (!(flag & 1)) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 10, ISO_WRONG_RR, 0, |
|
"ER entry found in a directory entry other " |
|
"than '.' entry of root node"); |
|
} |
|
continue; |
|
|
|
/* Need to read AA resp. AL in any case so it is available for |
|
S_IRWXG mapping in case that fsdata->aaip_load != 1 |
|
*/ |
|
} else if (SUSP_SIG(sue, 'A', 'A')) { |
|
|
|
ret = read_aaip_AA(sue, &aa_string, &aa_size, &aa_len, |
|
&prev_field, &aa_done, 0); |
|
if (ret < 0) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 11, ISO_WRONG_RR_WARN, ret, |
|
"Invalid AA entry"); |
|
continue; |
|
} |
|
|
|
} else if (SUSP_SIG(sue, 'A', 'L')) { |
|
|
|
ret = read_aaip_AL(sue, &aa_string, &aa_size, &aa_len, |
|
&prev_field, &aa_done, 0); |
|
if (ret < 0) { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 12, ISO_WRONG_RR_WARN, ret, |
|
"Invalid AL entry"); |
|
continue; |
|
} |
|
|
|
#ifdef Libisofs_with_zliB |
|
|
|
} else if (SUSP_SIG(sue, 'Z', 'F')) { |
|
|
|
ret = read_zisofs_ZF(sue, zisofs_alg, &zisofs_hs4, |
|
&zisofs_bsl2, &zisofs_usize, 0); |
|
if (ret < 0 || zisofs_alg[0] != 'p' || zisofs_alg[1] != 'z') { |
|
/* notify and continue */ |
|
ret = iso_rr_msg_submit(fsdata, 13, ISO_WRONG_RR_WARN, ret, |
|
"Invalid ZF entry"); |
|
zisofs_hs4 = 0; |
|
continue; |
|
} |
|
|
|
#endif /* Libisofs_with_zliB */ |
|
|
|
/* This message is inflationary */ |
|
/* |
|
} else { |
|
ret = iso_msg_submit(fsdata->msgid, ISO_SUSP_UNHANDLED, 0, |
|
"Unhandled SUSP entry %c%c.", sue->sig[0], sue->sig[1]); |
|
*/ |
|
|
|
} |
|
} |
|
|
|
susp_iter_free(iter); |
|
|
|
/* check for RR problems */ |
|
|
|
if (ret < 0) { |
|
/* error was already submitted above */ |
|
iso_msg_debug(fsdata->msgid, "Error parsing RR entries"); |
|
} else if (!relocated_dir && atts.st_mode == (mode_t) 0 ) { |
|
ret = iso_rr_msg_submit(fsdata, 14, ISO_WRONG_RR, 0, "Mandatory " |
|
"Rock Ridge PX entry is not present or it " |
|
"contains invalid values."); |
|
} else { |
|
/* ensure both name and link dest are finished */ |
|
if (namecont != 0) { |
|
ret = iso_rr_msg_submit(fsdata, 15, ISO_WRONG_RR, 0, |
|
"Incomplete Rock Ridge name, last NM entry continues"); |
|
} |
|
if (linkdestcont != 0) { |
|
ret = iso_rr_msg_submit(fsdata, 16, ISO_WRONG_RR, 0, |
|
"Incomplete link destination, last SL entry continues"); |
|
} |
|
} |
|
|
|
if (ret < 0) { |
|
free(name); |
|
goto ex; |
|
} |
|
|
|
/* convert name to needed charset */ |
|
if (strcmp(fsdata->input_charset, fsdata->local_charset) && name) { |
|
/* we need to convert name charset */ |
|
char *newname = NULL; |
|
ret = strconv(name, fsdata->input_charset, fsdata->local_charset, |
|
&newname); |
|
if (ret < 0) { |
|
/* its just a hint message */ |
|
LIBISO_FREE_MEM(msg); |
|
LIBISO_ALLOC_MEM(msg, char, 160); |
|
sprintf(msg, |
|
"Cannot convert from charset %.40s to %.40s", |
|
fsdata->input_charset, fsdata->local_charset); |
|
ret = iso_rr_msg_submit(fsdata, 17, ISO_FILENAME_WRONG_CHARSET, |
|
ret, msg); |
|
free(newname); |
|
if (ret < 0) { |
|
free(name); |
|
goto ex; |
|
} |
|
} else { |
|
free(name); |
|
name = newname; |
|
} |
|
} |
|
|
|
/* convert link destination to needed charset */ |
|
if (strcmp(fsdata->input_charset, fsdata->local_charset) && linkdest) { |
|
/* we need to convert name charset */ |
|
char *newlinkdest = NULL; |
|
ret = strconv(linkdest, fsdata->input_charset, |
|
fsdata->local_charset, &newlinkdest); |
|
if (ret < 0) { |
|
LIBISO_FREE_MEM(msg); |
|
LIBISO_ALLOC_MEM(msg, char, 160); |
|
sprintf(msg, |
|
"Charset conversion error. Cannot convert from %.40s to %.40s", |
|
fsdata->input_charset, fsdata->local_charset); |
|
ret = iso_rr_msg_submit(fsdata, 17, ISO_FILENAME_WRONG_CHARSET, |
|
ret, msg); |
|
free(newlinkdest); |
|
if (ret < 0) { |
|
free(name); |
|
goto ex; |
|
} |
|
} else { |
|
free(linkdest); |
|
linkdest = newlinkdest; |
|
} |
|
} |
|
|
|
} else { |
|
/* RR extensions are not read / used */ |
|
atts.st_gid = fsdata->gid; |
|
atts.st_uid = fsdata->uid; |
|
if (record->flags[0] & 0x02) { |
|
atts.st_mode = S_IFDIR | fsdata->dir_mode; |
|
} else { |
|
atts.st_mode = S_IFREG | fsdata->file_mode; |
|
} |
|
} |
|
|
|
if (!has_px) { |
|
fsdata->px_ino_status |= 4; |
|
} |
|
|
|
/* |
|
* if we haven't RR extensions, or no NM entry is present, |
|
* we use the name in directory record |
|
*/ |
|
if (!name) { |
|
size_t len; |
|
|
|
if (record->len_fi[0] == 1 && record->file_id[0] == 0) { |
|
/* "." entry, we can call this for root node, so... */ |
|
if (!(atts.st_mode & S_IFDIR)) { |
|
ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, |
|
"Wrong ISO file name. \".\" not dir"); |
|
goto ex; |
|
} |
|
} else { |
|
|
|
name = get_name(fsdata, (char*)record->file_id, record->len_fi[0]); |
|
if (name == NULL) { |
|
ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, |
|
"Cannot retrieve file name"); |
|
goto ex; |
|
} |
|
|
|
/* remove trailing version number */ |
|
len = strlen(name); |
|
ecma119_map = fsdata->ecma119_map; |
|
if (fsdata->iso_root_block == fsdata->svd_root_block) |
|
ecma119_map = 0; |
|
if (ecma119_map >= 1 && ecma119_map <= 3 && |
|
len > 2 && name[len-2] == ';' && name[len-1] == '1') { |
|
if (len > 3 && name[len-3] == '.') { |
|
/* |
|
* the "." is mandatory, so in most cases is included only |
|
* for standard compliance |
|
*/ |
|
name[len-3] = '\0'; |
|
} else { |
|
name[len-2] = '\0'; |
|
} |
|
} |
|
|
|
if (ecma119_map == 2 || ecma119_map == 3) { |
|
for (cpt = name; *cpt != 0; cpt++) { |
|
if (ecma119_map == 2) { |
|
if (islower(*cpt)) |
|
*cpt = toupper(*cpt); |
|
} else { |
|
if (isupper(*cpt)) |
|
*cpt = tolower(*cpt); |
|
} |
|
} |
|
} |
|
|
|
} |
|
} |
|
|
|
if (name != NULL) { |
|
if ((int) strlen(name) > fsdata->truncate_length) { |
|
ret = iso_truncate_rr_name(fsdata->truncate_mode, |
|
fsdata->truncate_length, name, 0); |
|
if (ret < 0) |
|
goto ex; |
|
} |
|
} |
|
|
|
if (relocated_dir) { |
|
|
|
/* |
|
* We are dealing with a placeholder for a relocated dir. |
|
* Thus, we need to read attributes for this directory from the "." |
|
* entry of the relocated dir. |
|
*/ |
|
|
|
LIBISO_ALLOC_MEM(buffer, uint8_t, BLOCK_SIZE); |
|
ret = fsdata->src->read_block(fsdata->src, relocated_dir, buffer); |
|
if (ret < 0) { |
|
goto ex; |
|
} |
|
|
|
/* Call with flag bit1 to prevent further CL relocation */ |
|
ret = iso_file_source_new_ifs(fs, parent, (struct ecma119_dir_record*) |
|
buffer, src, flag | 2); |
|
if (ret <= 0) { |
|
goto ex; |
|
} |
|
|
|
/* but the real name is the name of the placeholder */ |
|
ifsdata = (ImageFileSourceData*) (*src)->data; |
|
if (ifsdata->name != NULL) |
|
free(ifsdata->name); |
|
ifsdata->name = name; |
|
|
|
{ret = ISO_SUCCESS; goto ex;} |
|
} |
|
|
|
/* Production of missing inode numbers is delayed until the image is |
|
complete. Then all nodes which shall get a new inode number will |
|
be served. |
|
*/ |
|
|
|
/* |
|
* if we haven't RR extensions, or a needed TF time stamp is not present, |
|
* we use plain iso recording time |
|
*/ |
|
recorded = iso_datetime_read_7(record->recording_time); |
|
if (atts.st_atime == (time_t) 0) { |
|
atts.st_atime = recorded; |
|
} |
|
if (atts.st_ctime == (time_t) 0) { |
|
atts.st_ctime = recorded; |
|
} |
|
if (atts.st_mtime == (time_t) 0) { |
|
atts.st_mtime = recorded; |
|
} |
|
|
|
/* the size is read from iso directory record */ |
|
atts.st_size = iso_read_bb(record->length, 4, NULL); |
|
|
|
/* Fill last entries */ |
|
atts.st_dev = fsdata->id; |
|
atts.st_blksize = BLOCK_SIZE; |
|
atts.st_blocks = DIV_UP(atts.st_size, BLOCK_SIZE); |
|
|
|
/* TODO #00014 : more sanity checks to ensure dir record info is valid */ |
|
if (S_ISLNK(atts.st_mode) && (linkdest == NULL)) { |
|
ret = iso_rr_msg_submit(fsdata, 18, ISO_WRONG_RR, 0, |
|
"Link without destination."); |
|
free(name); |
|
goto ex; |
|
} |
|
|
|
/* ok, we can now create the file source */ |
|
if (*src == NULL) { |
|
ifsdata = calloc(1, sizeof(ImageFileSourceData)); |
|
if (ifsdata == NULL) { |
|
ret = ISO_OUT_OF_MEM; |
|
goto ifs_cleanup; |
|
} |
|
ifsrc = calloc(1, sizeof(IsoFileSource)); |
|
if (ifsrc == NULL) { |
|
ret = ISO_OUT_OF_MEM; |
|
goto ifs_cleanup; |
|
} |
|
} else { |
|
ifsdata = (*src)->data; |
|
ifsrc = (*src); |
|
free(ifsdata->name); /* we will assign a new one */ |
|
ifsdata->name = NULL; |
|
atts.st_size += (off_t)ifsdata->info.st_size; |
|
if (ifsdata->aa_string != NULL) |
|
free(ifsdata->aa_string); |
|
ifsdata->aa_string = NULL; |
|
} |
|
|
|
/* fill data */ |
|
ifsdata->fs = fs; |
|
iso_filesystem_ref(fs); |
|
if (parent != NULL) { |
|
ifsdata->parent = parent; |
|
iso_file_source_ref(parent); |
|
} |
|
ifsdata->info = atts; |
|
ifsdata->name = name; |
|
ifsdata->aa_string = aa_string; |
|
|
|
#ifdef Libisofs_with_zliB |
|
if (zisofs_hs4 > 0) { |
|
ifsdata->header_size_div4 = zisofs_hs4; |
|
ifsdata->block_size_log2 = zisofs_bsl2; |
|
ifsdata->uncompressed_size = zisofs_usize; |
|
} else { |
|
ifsdata->header_size_div4 = 0; |
|
} |
|
#endif |
|
|
|
/* save extents */ |
|
ifsdata->sections = realloc(ifsdata->sections, |
|
(1 + ifsdata->nsections) * sizeof(struct iso_file_section)); |
|
if (ifsdata->sections == NULL) { |
|
free(ifsdata->name); |
|
ret = ISO_OUT_OF_MEM; |
|
goto ifs_cleanup; |
|
} |
|
ifsdata->sections[ifsdata->nsections].block = |
|
iso_read_bb(record->block, 4, NULL) + record->len_xa[0]; |
|
ifsdata->sections[ifsdata->nsections].size = iso_read_bb(record->length, 4, NULL); |
|
ifsdata->nsections++; |
|
|
|
if (S_ISLNK(atts.st_mode)) { |
|
ifsdata->data.content = linkdest; |
|
} else if (linkdest != NULL) { |
|
ret = iso_rr_msg_submit(fsdata, 19, ISO_WRONG_RR_WARN, 0, |
|
"RRIP SL link destination with file that is not a link."); |
|
free(linkdest); |
|
linkdest = NULL; |
|
} |
|
|
|
ifsrc->class = &ifs_class; |
|
ifsrc->data = ifsdata; |
|
ifsrc->refcount = 1; |
|
|
|
*src = ifsrc; |
|
{ret = ISO_SUCCESS; goto ex;} |
|
|
|
ifs_cleanup: ; |
|
free(name); |
|
free(linkdest); |
|
free(ifsdata); |
|
free(ifsrc); |
|
|
|
ex:; |
|
LIBISO_FREE_MEM(msg); |
|
LIBISO_FREE_MEM(buffer); |
|
return ret; |
|
} |
|
|
|
static |
|
int ifs_get_root(IsoFilesystem *fs, IsoFileSource **root) |
|
{ |
|
int ret; |
|
_ImageFsData *data; |
|
uint8_t *buffer = NULL; |
|
|
|
if (fs == NULL || fs->data == NULL || root == NULL) { |
|
ret = ISO_NULL_POINTER; goto ex; |
|
} |
|
|
|
LIBISO_ALLOC_MEM(buffer, uint8_t, BLOCK_SIZE); |
|
data = (_ImageFsData*)fs->data; |
|
|
|
/* open the filesystem */ |
|
ret = ifs_fs_open((IsoImageFilesystem*)fs); |
|
if (ret < 0) { |
|
goto ex; |
|
} |
|
|
|
/* read extend for root record */ |
|
ret = data->src->read_block(data->src, data->iso_root_block, buffer); |
|
if (ret < 0) { |
|
ifs_fs_close((IsoImageFilesystem*)fs); |
|
goto ex; |
|
} |
|
|
|
/* get root attributes from "." entry */ |
|
*root = NULL; |
|
ret = iso_file_source_new_ifs((IsoImageFilesystem*)fs, NULL, |
|
(struct ecma119_dir_record*) buffer, root, 1); |
|
|
|
ifs_fs_close((IsoImageFilesystem*)fs); |
|
ex:; |
|
LIBISO_FREE_MEM(buffer); |
|
return ret; |
|
} |
|
|
|
/** |
|
* Find a file inside a node. |
|
* |
|
* @param file |
|
* it is not modified if requested file is not found |
|
* @return |
|
* 1 success, 0 not found, < 0 error |
|
*/ |
|
static |
|
int ifs_get_file(IsoFileSource *dir, const char *name, IsoFileSource **file) |
|
{ |
|
int ret; |
|
IsoFileSource *src; |
|
|
|
ret = iso_file_source_open(dir); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
while ((ret = iso_file_source_readdir(dir, &src)) == 1) { |
|
char *fname = iso_file_source_get_name(src); |
|
if (!strcmp(name, fname)) { |
|
free(fname); |
|
*file = src; |
|
ret = ISO_SUCCESS; |
|
break; |
|
} |
|
free(fname); |
|
iso_file_source_unref(src); |
|
} |
|
iso_file_source_close(dir); |
|
return ret; |
|
} |
|
|
|
static |
|
int ifs_get_by_path(IsoFilesystem *fs, const char *path, IsoFileSource **file) |
|
{ |
|
int ret; |
|
IsoFileSource *src = NULL; |
|
char *ptr, *brk_info, *component; |
|
|
|
if (fs == NULL || fs->data == NULL || path == NULL || file == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
|
|
if (path[0] != '/') { |
|
/* only absolute paths supported */ |
|
return ISO_FILE_BAD_PATH; |
|
} |
|
|
|
/* open the filesystem */ |
|
ret = ifs_fs_open((IsoImageFilesystem*)fs); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
ret = ifs_get_root(fs, &src); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
if (!strcmp(path, "/")) { |
|
/* we are looking for root */ |
|
*file = src; |
|
ret = ISO_SUCCESS; |
|
goto get_path_exit; |
|
} |
|
|
|
ptr = strdup(path); |
|
if (ptr == NULL) { |
|
iso_file_source_unref(src); |
|
ret = ISO_OUT_OF_MEM; |
|
goto get_path_exit; |
|
} |
|
|
|
component = strtok_r(ptr, "/", &brk_info); |
|
while (component) { |
|
IsoFileSource *child = NULL; |
|
|
|
ImageFileSourceData *fdata; |
|
fdata = src->data; |
|
if (!S_ISDIR(fdata->info.st_mode)) { |
|
ret = ISO_FILE_BAD_PATH; |
|
break; |
|
} |
|
|
|
ret = ifs_get_file(src, component, &child); |
|
iso_file_source_unref(src); |
|
src = NULL; |
|
if (ret <= 0) { |
|
break; |
|
} |
|
|
|
src = child; |
|
component = strtok_r(NULL, "/", &brk_info); |
|
} |
|
|
|
free(ptr); |
|
if (ret < 0) { |
|
if (src != NULL) |
|
iso_file_source_unref(src); |
|
} else if (ret == 0) { |
|
ret = ISO_FILE_DOESNT_EXIST; |
|
} else { |
|
*file = src; |
|
} |
|
|
|
get_path_exit:; |
|
ifs_fs_close((IsoImageFilesystem*)fs); |
|
return ret; |
|
} |
|
|
|
unsigned int ifs_get_id(IsoFilesystem *fs) |
|
{ |
|
return ISO_IMAGE_FS_ID; |
|
} |
|
|
|
static |
|
int ifs_fs_open(IsoImageFilesystem *fs) |
|
{ |
|
_ImageFsData *data; |
|
|
|
if (fs == NULL || fs->data == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
|
|
data = (_ImageFsData*)fs->data; |
|
|
|
if (data->open_count == 0) { |
|
/* we need to actually open the data source */ |
|
int res = data->src->open(data->src); |
|
if (res < 0) { |
|
return res; |
|
} |
|
} |
|
++data->open_count; |
|
return ISO_SUCCESS; |
|
} |
|
|
|
static |
|
int ifs_fs_close(IsoImageFilesystem *fs) |
|
{ |
|
_ImageFsData *data; |
|
|
|
if (fs == NULL || fs->data == NULL) { |
|
return ISO_NULL_POINTER; |
|
} |
|
|
|
data = (_ImageFsData*)fs->data; |
|
|
|
if (--data->open_count == 0) { |
|
/* we need to actually close the data source */ |
|
return data->src->close(data->src); |
|
} |
|
return ISO_SUCCESS; |
|
} |
|
|
|
static |
|
void ifs_fs_free(IsoFilesystem *fs) |
|
{ |
|
_ImageFsData *data; |
|
|
|
data = (_ImageFsData*) fs->data; |
|
|
|
/* close data source if already openned */ |
|
if (data->open_count > 0) { |
|
data->src->close(data->src); |
|
} |
|
|
|
/* free our ref to datasource */ |
|
iso_data_source_unref(data->src); |
|
|
|
/* free volume atts */ |
|
free(data->volset_id); |
|
free(data->volume_id); |
|
free(data->publisher_id); |
|
free(data->data_preparer_id); |
|
free(data->system_id); |
|
free(data->application_id); |
|
free(data->copyright_file_id); |
|
free(data->abstract_file_id); |
|
free(data->biblio_file_id); |
|
free(data->creation_time); |
|
free(data->modification_time); |
|
free(data->expiration_time); |
|
free(data->effective_time); |
|
free(data->input_charset); |
|
free(data->local_charset); |
|
|
|
if(data->catcontent != NULL) |
|
free(data->catcontent); |
|
|
|
free(data); |
|
} |
|
|
|
/** |
|
* Read the SUSP system user entries of the "." entry of the root directory, |
|
* indentifying when Rock Ridge extensions are being used. |
|
* |
|
* @return |
|
* 1 success, 0 ignored, < 0 error |
|
*/ |
|
static |
|
int read_root_susp_entries(_ImageFsData *data, uint32_t block) |
|
{ |
|
int ret; |
|
unsigned char *buffer = NULL; |
|
struct ecma119_dir_record *record; |
|
struct susp_sys_user_entry *sue; |
|
SuspIterator *iter; |
|
|
|
LIBISO_ALLOC_MEM(buffer, unsigned char, 2048); |
|
ret = data->src->read_block(data->src, block, buffer); |
|
if (ret < 0) { |
|
goto ex; |
|
} |
|
|
|
/* record will be the "." directory entry for the root record */ |
|
record = (struct ecma119_dir_record *)buffer; |
|
|
|
#ifdef Libisofs_syslinux_tesT |
|
|
|
{ |
|
struct device syslinux_dev; |
|
struct iso_sb_info syslinux_sbi; |
|
struct fs_info syslinux_fsi; |
|
|
|
syslinux_dev.src = data->src; |
|
memcpy(&(syslinux_sbi.root), (char *) record, 256); |
|
syslinux_sbi.do_rr = 1; |
|
syslinux_sbi.susp_skip = 0; |
|
syslinux_fsi.fs_dev = &syslinux_dev; |
|
syslinux_fsi.fs_info = &syslinux_sbi; |
|
|
|
ret = susp_rr_check_signatures(&syslinux_fsi, 1); |
|
fprintf(stderr, "--------- susp_rr_check_signatures == %d , syslinux_sbi.do_rr == %d\n", ret, syslinux_sbi.do_rr); |
|
} |
|
|
|
#endif /* Libisofs_syslinux_tesT */ |
|
|
|
|
|
/* |
|
* TODO #00015 : take care of CD-ROM XA discs when reading SP entry |
|
* 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!! |
|
*/ |
|
|
|
iter = susp_iter_new(data->src, record, data->session_lba + data->nblocks, |
|
data->len_skp, data->msgid); |
|
if (iter == NULL) { |
|
ret = ISO_OUT_OF_MEM; goto ex; |
|
} |
|
|
|
/* first entry must be an SP system use entry */ |
|
ret = susp_iter_next(iter, &sue, 1); |
|
if (ret < 0) { |
|
/* error */ |
|
susp_iter_free(iter); |
|
goto ex; |
|
} else if (ret == 0 || !SUSP_SIG(sue, 'S', 'P') ) { |
|
iso_msg_debug(data->msgid, "SUSP/RR is not being used."); |
|
susp_iter_free(iter); |
|
{ret = ISO_SUCCESS; goto ex;} |
|
} |
|
|
|
/* it is a SP system use entry */ |
|
if (sue->version[0] != 1 || sue->data.SP.be[0] != 0xBE |
|
|| sue->data.SP.ef[0] != 0xEF) { |
|
|
|
susp_iter_free(iter); |
|
ret = iso_msg_submit(data->msgid, ISO_UNSUPPORTED_SUSP, 0, |
|
"SUSP SP system use entry seems to be wrong. " |
|
"Ignoring Rock Ridge Extensions."); |
|
goto ex; |
|
} |
|
|
|
iso_msg_debug(data->msgid, "SUSP/RR is being used."); |
|
|
|
/* |
|
* The LEN_SKP field, defined in IEEE 1281, SUSP. 5.3, specifies the |
|
* number of bytes to be skipped within each System Use field. |
|
* I think this will be always 0, but given that support this standard |
|
* feature is easy... |
|
*/ |
|
data->len_skp = sue->data.SP.len_skp[0]; |
|
|
|
/* |
|
* Ok, now search for ER entry. |
|
* Just notice that the attributes for root dir are read elsewhere. |
|
* |
|
* TODO #00016 : handle non RR ER entries |
|
* |
|
* if several ER are present, we need to identify the position of |
|
* what refers to RR, and then look for corresponding ES entry in |
|
* each directory record. I have not implemented this (it's not used, |
|
* no?), but if we finally need it, it can be easily implemented in |
|
* the iterator, transparently for the rest of the code. |
|
*/ |
|
while ((ret = susp_iter_next(iter, &sue, 0)) > 0) { |
|
|
|
/* ignore entries from different version */ |
|
if (sue->version[0] != 1) |
|
continue; |
|
|
|
if (SUSP_SIG(sue, 'E', 'R')) { |
|
/* |
|
* it seems that Rock Ridge can be identified with any |
|
* of the following |
|
*/ |
|
if ( sue->data.ER.len_id[0] == 10 && |
|
!strncmp((char*)sue->data.ER.ext_id, "RRIP_1991A", 10) ) { |
|
|
|
iso_msg_debug(data->msgid, |
|
"Suitable Rock Ridge ER found. Version 1.10."); |
|
data->rr_version = 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)) ) { |
|
|
|
iso_msg_debug(data->msgid, |
|
"Suitable Rock Ridge ER found. Version 1.12."); |
|
data->rr_version = RR_EXT_112; |
|
|
|
} else if (sue->data.ER.len_id[0] == 9 && |
|
(strncmp((char*)sue->data.ER.ext_id, "AAIP_0002", 9) == 0 || |
|
strncmp((char*)sue->data.ER.ext_id, "AAIP_0100", 9) == 0 || |
|
strncmp((char*)sue->data.ER.ext_id, "AAIP_0200", 9) == 0)) { |
|
|
|
/* Tolerate AAIP ER even if not supported */ |
|
iso_msg_debug(data->msgid, "Suitable AAIP ER found."); |
|
|
|
if (strncmp((char*)sue->data.ER.ext_id, "AAIP_0200", 9) == 0) |
|
data->aaip_version = 200; |
|
else if ((( |