1324 lines
38 KiB
C
1324 lines
38 KiB
C
/*
|
|
* Copyright (c) 2007 Vreixo Formoso
|
|
* Copyright (c) 2009 - 2016 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "../config.h"
|
|
#endif
|
|
|
|
/* Must be before ecma119.h because of eventual Libisofs_with_rrip_rR */
|
|
#include "libisofs.h"
|
|
|
|
#include "ecma119_tree.h"
|
|
#include "ecma119.h"
|
|
#include "node.h"
|
|
#include "util.h"
|
|
#include "filesrc.h"
|
|
#include "messages.h"
|
|
#include "image.h"
|
|
#include "stream.h"
|
|
#include "eltorito.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
/* @param flag bit0= Do not issue error messages
|
|
*/
|
|
int iso_get_ecma119_name(IsoWriteOpts *opts, char *input_charset, int imgid,
|
|
char *node_name, enum IsoNodeType node_type,
|
|
char **name, int flag)
|
|
{
|
|
int ret, relaxed, free_ascii_name = 0, force_dots = 0;
|
|
char *ascii_name;
|
|
char *isoname = NULL;
|
|
|
|
if (node_name == NULL) {
|
|
/* it is not necessarily an error, it can be the root */
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
if (opts->untranslated_name_len > 0) {
|
|
ascii_name = node_name;
|
|
ret = 1;
|
|
} else {
|
|
ret = str2ascii(input_charset, node_name, &ascii_name);
|
|
free_ascii_name = 1;
|
|
}
|
|
if (ret < 0) {
|
|
if (!(flag & 512))
|
|
iso_msg_submit(imgid, ret, 0,
|
|
"Cannot convert name '%s' to ASCII", node_name);
|
|
return ret;
|
|
}
|
|
|
|
if (opts->allow_full_ascii) {
|
|
relaxed = 2;
|
|
} else {
|
|
relaxed = (int)opts->allow_lowercase;
|
|
}
|
|
if (opts->allow_7bit_ascii)
|
|
relaxed |= 4;
|
|
if (node_type == LIBISO_DIR && !(opts->allow_dir_id_ext)) {
|
|
if (opts->untranslated_name_len > 0) {
|
|
if (strlen(ascii_name) > opts->untranslated_name_len) {
|
|
needs_transl:;
|
|
if (!(flag & 512))
|
|
iso_msg_submit(imgid, ISO_NAME_NEEDS_TRANSL, 0,
|
|
"File name too long (%d > %d) for untranslated recording: '%s'",
|
|
strlen(ascii_name), opts->untranslated_name_len,
|
|
ascii_name);
|
|
return ISO_NAME_NEEDS_TRANSL;
|
|
}
|
|
isoname = strdup(ascii_name);
|
|
} else if (opts->max_37_char_filenames) {
|
|
isoname = iso_r_dirid(ascii_name, 37, relaxed);
|
|
} else if (opts->iso_level == 1) {
|
|
|
|
#ifdef Libisofs_old_ecma119_nameS
|
|
|
|
if (relaxed) {
|
|
isoname = iso_r_dirid(ascii_name, 8, relaxed);
|
|
} else {
|
|
isoname = iso_1_dirid(ascii_name, 0);
|
|
}
|
|
|
|
#else /* Libisofs_old_ecma119_nameS */
|
|
|
|
isoname = iso_1_dirid(ascii_name, relaxed);
|
|
|
|
#endif /* ! Libisofs_old_ecma119_nameS */
|
|
|
|
|
|
} else {
|
|
if (relaxed) {
|
|
isoname = iso_r_dirid(ascii_name, 31, relaxed);
|
|
} else {
|
|
isoname = iso_2_dirid(ascii_name);
|
|
}
|
|
}
|
|
} else {
|
|
force_dots = !((opts->no_force_dots & 1) ||
|
|
node_type == LIBISO_DIR);
|
|
if (opts->untranslated_name_len > 0) {
|
|
if (strlen(ascii_name) > opts->untranslated_name_len)
|
|
goto needs_transl;
|
|
isoname = strdup(ascii_name);
|
|
} else if (opts->max_37_char_filenames) {
|
|
isoname = iso_r_fileid(ascii_name, 36, relaxed, force_dots);
|
|
} else if (opts->iso_level == 1) {
|
|
|
|
#ifdef Libisofs_old_ecma119_nameS
|
|
|
|
int max_len;
|
|
|
|
if (relaxed) {
|
|
if (strchr(ascii_name, '.') == NULL)
|
|
max_len = 8;
|
|
else
|
|
max_len = 11;
|
|
isoname = iso_r_fileid(ascii_name, max_len, relaxed,
|
|
force_dots);
|
|
} else {
|
|
isoname = iso_1_fileid(ascii_name, 0, force_dots);
|
|
}
|
|
|
|
#else /* Libisofs_old_ecma119_nameS */
|
|
|
|
isoname = iso_1_fileid(ascii_name, relaxed, force_dots);
|
|
|
|
#endif /* ! Libisofs_old_ecma119_nameS */
|
|
|
|
} else {
|
|
if (relaxed || !force_dots) {
|
|
isoname = iso_r_fileid(ascii_name, 30, relaxed, force_dots);
|
|
} else {
|
|
isoname = iso_2_fileid(ascii_name);
|
|
}
|
|
}
|
|
}
|
|
if (free_ascii_name)
|
|
free(ascii_name);
|
|
if (isoname != NULL) {
|
|
*name = isoname;
|
|
return ISO_SUCCESS;
|
|
} else {
|
|
/*
|
|
* only possible if mem error, as check for empty names is done
|
|
* in public tree
|
|
*/
|
|
return ISO_OUT_OF_MEM;
|
|
}
|
|
}
|
|
|
|
static
|
|
int get_iso_name(Ecma119Image *img, IsoNode *iso, char **name)
|
|
{
|
|
int ret;
|
|
|
|
ret = iso_get_ecma119_name(img->opts, img->input_charset, img->image->id,
|
|
iso->name, iso->type, name, 0);
|
|
return ret;
|
|
}
|
|
|
|
int ecma119_is_dedicated_reloc_dir(Ecma119Image *img, Ecma119Node *node)
|
|
{
|
|
if (img->rr_reloc_node == node &&
|
|
node != img->root && node != img->partition_root &&
|
|
(img->opts->rr_reloc_flags & 2))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
int create_ecma119_node(Ecma119Image *img, IsoNode *iso, Ecma119Node **node)
|
|
{
|
|
Ecma119Node *ecma;
|
|
|
|
ecma = calloc(1, sizeof(Ecma119Node));
|
|
if (ecma == NULL) {
|
|
return ISO_OUT_OF_MEM;
|
|
}
|
|
|
|
ecma->node = iso;
|
|
iso_node_ref(iso);
|
|
ecma->nlink = 1;
|
|
*node = ecma;
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Create a new ECMA-119 node representing a directory from a iso directory
|
|
* node.
|
|
*/
|
|
static
|
|
int create_dir(Ecma119Image *img, IsoDir *iso, Ecma119Node **node)
|
|
{
|
|
int ret;
|
|
Ecma119Node **children = NULL;
|
|
struct ecma119_dir_info *dir_info;
|
|
|
|
if (iso->nchildren > 0) {
|
|
children = calloc(1, sizeof(void*) * iso->nchildren);
|
|
if (children == NULL)
|
|
return ISO_OUT_OF_MEM;
|
|
}
|
|
|
|
dir_info = calloc(1, sizeof(struct ecma119_dir_info));
|
|
if (dir_info == NULL) {
|
|
if (children != NULL)
|
|
free(children);
|
|
return ISO_OUT_OF_MEM;
|
|
}
|
|
|
|
ret = create_ecma119_node(img, (IsoNode*)iso, node);
|
|
if (ret < 0) {
|
|
if (children != NULL)
|
|
free(children);
|
|
free(dir_info);
|
|
return ret;
|
|
}
|
|
(*node)->type = ECMA119_DIR;
|
|
(*node)->info.dir = dir_info;
|
|
(*node)->info.dir->nchildren = 0;
|
|
(*node)->info.dir->children = children;
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
|
|
static
|
|
int create_file_src(Ecma119Image *img, IsoFile *iso, IsoFileSrc **src)
|
|
{
|
|
int ret;
|
|
off_t size;
|
|
|
|
size = iso_stream_get_size(iso->stream);
|
|
if (size > (off_t)MAX_ISO_FILE_SECTION_SIZE && img->opts->iso_level != 3) {
|
|
char *ipath = iso_tree_get_node_path(ISO_NODE(iso));
|
|
iso_msg_submit(img->image->id, ISO_FILE_TOO_BIG, 0,
|
|
"File \"%s\" cannot be added to image because "
|
|
"its size is 4 GiB or larger", ipath);
|
|
free(ipath);
|
|
return ISO_FILE_TOO_BIG;
|
|
}
|
|
ret = iso_file_src_create(img, iso, src);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a new ECMA-119 node representing a regular file from a iso file
|
|
* node.
|
|
*/
|
|
static
|
|
int create_file(Ecma119Image *img, IsoFile *iso, Ecma119Node **node)
|
|
{
|
|
int ret;
|
|
IsoFileSrc *src;
|
|
|
|
ret = create_file_src(img, iso, &src);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = create_ecma119_node(img, (IsoNode*)iso, node);
|
|
if (ret < 0) {
|
|
/*
|
|
* the src doesn't need to be freed, it is free together with
|
|
* the Ecma119Image
|
|
*/
|
|
return ret;
|
|
}
|
|
(*node)->type = ECMA119_FILE;
|
|
(*node)->info.file = src;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Create a new ECMA-119 node representing a regular file from an El-Torito
|
|
* boot catalog
|
|
*/
|
|
static
|
|
int create_boot_cat(Ecma119Image *img, IsoBoot *iso, Ecma119Node **node)
|
|
{
|
|
int ret;
|
|
IsoFileSrc *src;
|
|
|
|
ret = el_torito_catalog_file_src_create(img, &src);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = create_ecma119_node(img, (IsoNode*)iso, node);
|
|
if (ret < 0) {
|
|
/*
|
|
* the src doesn't need to be freed, it is free together with
|
|
* the Ecma119Image
|
|
*/
|
|
return ret;
|
|
}
|
|
(*node)->type = ECMA119_FILE;
|
|
(*node)->info.file = src;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Create a new ECMA-119 node representing a symbolic link from a iso symlink
|
|
* node.
|
|
*/
|
|
static
|
|
int create_symlink(Ecma119Image *img, IsoSymlink *iso, Ecma119Node **node)
|
|
{
|
|
int ret;
|
|
|
|
ret = create_ecma119_node(img, (IsoNode*)iso, node);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
(*node)->type = ECMA119_SYMLINK;
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Create a new ECMA-119 node representing a special file.
|
|
*/
|
|
static
|
|
int create_special(Ecma119Image *img, IsoSpecial *iso, Ecma119Node **node)
|
|
{
|
|
int ret;
|
|
|
|
ret = create_ecma119_node(img, (IsoNode*)iso, node);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
(*node)->type = ECMA119_SPECIAL;
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
void ecma119_node_free(Ecma119Node *node)
|
|
{
|
|
if (node == NULL) {
|
|
return;
|
|
}
|
|
if (node->type == ECMA119_DIR) {
|
|
size_t i;
|
|
for (i = 0; i < node->info.dir->nchildren; i++) {
|
|
ecma119_node_free(node->info.dir->children[i]);
|
|
}
|
|
if (node->info.dir->children != NULL)
|
|
free(node->info.dir->children);
|
|
free(node->info.dir);
|
|
}
|
|
free(node->iso_name);
|
|
iso_node_unref(node->node);
|
|
free(node);
|
|
}
|
|
|
|
|
|
static
|
|
int add_to_hidden_list(Ecma119Image *image, IsoFileSrc *src)
|
|
{
|
|
int ret;
|
|
struct iso_filesrc_list_item *item;
|
|
|
|
LIBISO_ALLOC_MEM(item, struct iso_filesrc_list_item, 1);
|
|
item->src = src;
|
|
item->next = image->ecma119_hidden_list;
|
|
image->ecma119_hidden_list = item;
|
|
ret = ISO_SUCCESS;
|
|
ex:
|
|
return ret;
|
|
}
|
|
|
|
|
|
int iso_filesrc_list_destroy(struct iso_filesrc_list_item **start_item)
|
|
{
|
|
struct iso_filesrc_list_item *item, *next;
|
|
|
|
for (item = *start_item; item != NULL; item = next) {
|
|
next = item->next;
|
|
LIBISO_FREE_MEM(item);
|
|
}
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param flag
|
|
* bit0= iso is in a hidden directory. Thus hide it.
|
|
* @return
|
|
* 1 success, 0 node ignored, < 0 error
|
|
*
|
|
*/
|
|
static
|
|
int create_tree(Ecma119Image *image, IsoNode *iso, Ecma119Node **tree,
|
|
int depth, int pathlen, int flag)
|
|
{
|
|
int ret, hidden;
|
|
Ecma119Node *node = NULL;
|
|
int max_path;
|
|
char *iso_name= NULL, *ipath = NULL;
|
|
IsoFileSrc *src = NULL;
|
|
IsoWriteOpts *opts;
|
|
|
|
if (image == NULL || iso == NULL || tree == NULL) {
|
|
return ISO_NULL_POINTER;
|
|
}
|
|
opts = image->opts;
|
|
*tree = NULL;
|
|
|
|
hidden = flag & 1;
|
|
if (iso->hidden & LIBISO_HIDE_ON_RR) {
|
|
hidden = 1;
|
|
if (!((iso->hidden & LIBISO_HIDE_BUT_WRITE) ||
|
|
iso->type == LIBISO_BOOT)) {
|
|
return 0; /* file will be ignored */
|
|
}
|
|
}
|
|
|
|
if (hidden) {
|
|
max_path= pathlen;
|
|
} else {
|
|
ret = get_iso_name(image, iso, &iso_name);
|
|
if (ret < 0) {
|
|
iso_name = NULL; /* invalid, do not free */
|
|
goto ex;
|
|
}
|
|
max_path = pathlen + 1 + (iso_name ? strlen(iso_name) : 0);
|
|
if (!opts->rockridge) {
|
|
if ((iso->type == LIBISO_DIR && depth > 8) &&
|
|
!opts->allow_deep_paths) {
|
|
ipath = iso_tree_get_node_path(iso);
|
|
ret = iso_msg_submit(image->image->id, ISO_FILE_IMGPATH_WRONG,
|
|
0, "File \"%s\" can't be added, "
|
|
"because directory depth "
|
|
"is greater than 8.", ipath);
|
|
goto ex;
|
|
} else if (max_path > 255 && !opts->allow_longer_paths) {
|
|
ipath = iso_tree_get_node_path(iso);
|
|
ret = iso_msg_submit(image->image->id, ISO_FILE_IMGPATH_WRONG,
|
|
0, "File \"%s\" can't be added, "
|
|
"because path length "
|
|
"is greater than 255 characters", ipath);
|
|
goto ex;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (iso->type) {
|
|
case LIBISO_FILE:
|
|
if (hidden) {
|
|
ret = create_file_src(image, (IsoFile *) iso, &src);
|
|
if (ret <= 0)
|
|
goto ex;
|
|
ret = add_to_hidden_list(image, src);
|
|
} else {
|
|
ret = create_file(image, (IsoFile*)iso, &node);
|
|
}
|
|
break;
|
|
case LIBISO_SYMLINK:
|
|
if (hidden) {
|
|
ret = 0; /* Hidden means non-existing */
|
|
goto ex;
|
|
}
|
|
if (opts->rockridge) {
|
|
ret = create_symlink(image, (IsoSymlink*)iso, &node);
|
|
} else {
|
|
/* symlinks are only supported when RR is enabled */
|
|
char *ipath = iso_tree_get_node_path(iso);
|
|
ret = iso_msg_submit(image->image->id, ISO_FILE_IGNORED, 0,
|
|
"File \"%s\" ignored. Symlinks need RockRidge extensions.",
|
|
ipath);
|
|
free(ipath);
|
|
}
|
|
break;
|
|
case LIBISO_SPECIAL:
|
|
if (hidden) {
|
|
ret = 0; /* Hidden means non-existing */
|
|
goto ex;
|
|
}
|
|
if (opts->rockridge) {
|
|
ret = create_special(image, (IsoSpecial*)iso, &node);
|
|
} else {
|
|
/* special files are only supported when RR is enabled */
|
|
char *ipath = iso_tree_get_node_path(iso);
|
|
ret = iso_msg_submit(image->image->id, ISO_FILE_IGNORED, 0,
|
|
"File \"%s\" ignored. Special files need RockRidge extensions.",
|
|
ipath);
|
|
free(ipath);
|
|
}
|
|
break;
|
|
case LIBISO_BOOT:
|
|
if (image->eltorito) {
|
|
if (hidden) {
|
|
ret = el_torito_catalog_file_src_create(image, &src);
|
|
if (ret <= 0)
|
|
goto ex;
|
|
ret = add_to_hidden_list(image, src);
|
|
} else {
|
|
ret = create_boot_cat(image, (IsoBoot*)iso, &node);
|
|
}
|
|
} else {
|
|
/* log and ignore */
|
|
ret = iso_msg_submit(image->image->id, ISO_FILE_IGNORED, 0,
|
|
"El-Torito catalog found on a image without El-Torito.");
|
|
}
|
|
break;
|
|
case LIBISO_DIR:
|
|
{
|
|
IsoNode *pos;
|
|
IsoDir *dir = (IsoDir*)iso;
|
|
|
|
if (!hidden) {
|
|
ret = create_dir(image, dir, &node);
|
|
if (ret < 0) {
|
|
goto ex;
|
|
}
|
|
if (depth == 1) { /* root is default */
|
|
image->rr_reloc_node = node;
|
|
} else if (depth == 2) {
|
|
/* Directories in root may be used as relocation dir */
|
|
if (opts->rr_reloc_dir != NULL)
|
|
if (opts->rr_reloc_dir[0] != 0 &&
|
|
strcmp(iso->name, opts->rr_reloc_dir) == 0)
|
|
image->rr_reloc_node = node;
|
|
}
|
|
}
|
|
ret = ISO_SUCCESS;
|
|
pos = dir->children;
|
|
while (pos) {
|
|
int cret;
|
|
Ecma119Node *child;
|
|
cret = create_tree(image, pos, &child, depth + 1, max_path,
|
|
!!hidden);
|
|
if (cret < 0) {
|
|
/* error */
|
|
ret = cret;
|
|
break;
|
|
} else if (cret == ISO_SUCCESS && !hidden) {
|
|
/* add child to this node */
|
|
int nchildren = node->info.dir->nchildren++;
|
|
node->info.dir->children[nchildren] = child;
|
|
child->parent = node;
|
|
}
|
|
pos = pos->next;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
/* should never happen */
|
|
ret = ISO_ASSERT_FAILURE;
|
|
goto ex;
|
|
}
|
|
if (ret <= 0) {
|
|
goto ex;
|
|
}
|
|
if (!hidden) {
|
|
node->iso_name = iso_name;
|
|
iso_name = NULL; /* now owned by node, do not free */
|
|
*tree = node;
|
|
node = NULL; /* now owned by caller, do not free */
|
|
}
|
|
ret = ISO_SUCCESS;
|
|
ex:
|
|
if (iso_name != NULL)
|
|
free(iso_name);
|
|
if (ipath != NULL)
|
|
free(ipath);
|
|
if (node != NULL)
|
|
ecma119_node_free(node);
|
|
if (hidden && ret == ISO_SUCCESS)
|
|
ret = 0;
|
|
/* The sources of hidden files are now owned by the rb-tree */
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Compare the iso name of two ECMA-119 nodes
|
|
*/
|
|
static
|
|
int cmp_node_name(const void *f1, const void *f2)
|
|
{
|
|
Ecma119Node *f = *((Ecma119Node**)f1);
|
|
Ecma119Node *g = *((Ecma119Node**)f2);
|
|
return strcmp(f->iso_name, g->iso_name);
|
|
}
|
|
|
|
/**
|
|
* Sorts a the children of each directory in the ECMA-119 tree represented
|
|
* by \p root, according to the order specified in ECMA-119, section 9.3.
|
|
*/
|
|
static
|
|
void sort_tree(Ecma119Node *root)
|
|
{
|
|
size_t i;
|
|
|
|
if (root->info.dir->children == NULL)
|
|
return;
|
|
qsort(root->info.dir->children, root->info.dir->nchildren, sizeof(void*),
|
|
cmp_node_name);
|
|
for (i = 0; i < root->info.dir->nchildren; i++) {
|
|
if (root->info.dir->children[i]->type == ECMA119_DIR)
|
|
sort_tree(root->info.dir->children[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures that the ISO name of each children of the given dir is unique,
|
|
* changing some of them if needed.
|
|
* It also ensures that resulting filename is always <= than given
|
|
* max_name_len, including extension. If needed, the extension will be reduced,
|
|
* but never under 3 characters.
|
|
*/
|
|
static
|
|
int mangle_single_dir(Ecma119Image *img, Ecma119Node *dir, int max_file_len,
|
|
int max_dir_len)
|
|
{
|
|
int ret;
|
|
int i, nchildren;
|
|
Ecma119Node **children;
|
|
IsoHTable *table;
|
|
int need_sort = 0;
|
|
|
|
nchildren = dir->info.dir->nchildren;
|
|
children = dir->info.dir->children;
|
|
|
|
if (nchildren <= 0)
|
|
return ISO_SUCCESS; /* nothing to do */
|
|
|
|
/* a hash table will temporary hold the names, for fast searching */
|
|
ret = iso_htable_create((nchildren * 100) / 80, iso_str_hash,
|
|
(compare_function_t)strcmp, &table);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
for (i = 0; i < nchildren; ++i) {
|
|
char *name = children[i]->iso_name;
|
|
ret = iso_htable_add(table, name, name);
|
|
if (ret < 0) {
|
|
goto mangle_cleanup;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nchildren; ++i) {
|
|
char *name, *ext;
|
|
char full_name[40];
|
|
const int full_max_len = 40 - 1;
|
|
int max; /* computed max len for name, without extension */
|
|
int j = i;
|
|
int digits = 1; /* characters to change per name */
|
|
|
|
/* first, find all child with same name */
|
|
while (j + 1 < nchildren && !cmp_node_name(children + i, children + j
|
|
+ 1)) {
|
|
++j;
|
|
}
|
|
if (j == i) {
|
|
/* name is unique */
|
|
continue;
|
|
}
|
|
|
|
if (img->opts->untranslated_name_len) {
|
|
/* This should not happen because no two IsoNode names should be
|
|
identical and only unaltered IsoNode names should be seen here.
|
|
Thus the Ema119Node names should be unique.
|
|
*/
|
|
iso_msg_submit(img->image->id, ISO_NAME_NEEDS_TRANSL, 0,
|
|
"ECMA-119 file name collision: '%s'",
|
|
children[i]->iso_name);
|
|
ret = ISO_NAME_NEEDS_TRANSL;
|
|
goto mangle_cleanup;
|
|
}
|
|
|
|
/*
|
|
* A max of 7 characters is good enough, it allows handling up to
|
|
* 9,999,999 files with same name. We can increment this to
|
|
* max_name_len, but the int_pow() function must then be modified
|
|
* to return a bigger integer.
|
|
*/
|
|
while (digits < 8) {
|
|
int ok, k;
|
|
char *dot;
|
|
int change = 0; /* number to be written */
|
|
|
|
/* copy name to buffer */
|
|
strncpy(full_name, children[i]->iso_name, full_max_len);
|
|
full_name[full_max_len] = 0;
|
|
|
|
/* compute name and extension */
|
|
dot = strrchr(full_name, '.');
|
|
if (dot != NULL &&
|
|
(children[i]->type != ECMA119_DIR ||
|
|
img->opts->allow_dir_id_ext)) {
|
|
|
|
/*
|
|
* File (normally not dir) with extension
|
|
* Note that we don't need to check for placeholders, as
|
|
* tree reparent happens later, so no placeholders can be
|
|
* here at this time.
|
|
*/
|
|
int extlen;
|
|
full_name[dot - full_name] = '\0';
|
|
name = full_name;
|
|
ext = dot + 1;
|
|
|
|
/*
|
|
* For iso level 1 we force ext len to be 3, as name
|
|
* can't grow on the extension space
|
|
*/
|
|
extlen = (max_file_len == 12) ? 3 : strlen(ext);
|
|
max = max_file_len - extlen - 1 - digits;
|
|
if (max <= 0) {
|
|
/* this can happen if extension is too long */
|
|
if (extlen + max > 3) {
|
|
/*
|
|
* reduce extension len, to give name an extra char
|
|
* note that max is negative or 0
|
|
*/
|
|
extlen = extlen + max - 1;
|
|
ext[extlen] = '\0';
|
|
max = max_file_len - extlen - 1 - digits;
|
|
} else {
|
|
/*
|
|
* error, we don't support extensions < 3
|
|
* This can't happen with current limit of digits.
|
|
*/
|
|
ret = ISO_ERROR;
|
|
goto mangle_cleanup;
|
|
}
|
|
}
|
|
/* ok, reduce name by digits */
|
|
if (name + max < dot) {
|
|
name[max] = '\0';
|
|
}
|
|
} else {
|
|
/* Directory (normally), or file without extension */
|
|
if (children[i]->type == ECMA119_DIR) {
|
|
max = max_dir_len - digits;
|
|
dot = NULL; /* dots (normally) have no meaning in dirs */
|
|
} else {
|
|
max = max_file_len - digits;
|
|
}
|
|
name = full_name;
|
|
if ((size_t) max < strlen(name)) {
|
|
name[max] = '\0';
|
|
}
|
|
/* let ext be an empty string */
|
|
ext = name + strlen(name);
|
|
}
|
|
|
|
ok = 1;
|
|
/* change name of each file */
|
|
for (k = i; k <= j; ++k) {
|
|
char tmp[40];
|
|
char fmt[16];
|
|
if (dot != NULL) {
|
|
sprintf(fmt, "%%s%%0%dd.%%s", digits);
|
|
} else {
|
|
sprintf(fmt, "%%s%%0%dd%%s", digits);
|
|
}
|
|
while (1) {
|
|
sprintf(tmp, fmt, name, change, ext);
|
|
++change;
|
|
if (change > int_pow(10, digits)) {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
if (!iso_htable_get(table, tmp, NULL)) {
|
|
/* the name is unique, so it can be used */
|
|
break;
|
|
}
|
|
}
|
|
if (ok) {
|
|
char *new = strdup(tmp);
|
|
if (new == NULL) {
|
|
ret = ISO_OUT_OF_MEM;
|
|
goto mangle_cleanup;
|
|
}
|
|
|
|
#ifdef Libisofs_extra_verbose_debuG
|
|
iso_msg_debug(img->image->id, "\"%s\" renamed to \"%s\"",
|
|
children[k]->iso_name, new);
|
|
#endif
|
|
|
|
iso_htable_remove_ptr(table, children[k]->iso_name, NULL);
|
|
free(children[k]->iso_name);
|
|
children[k]->iso_name = new;
|
|
iso_htable_add(table, new, new);
|
|
|
|
/*
|
|
* if we change a name we need to sort again children
|
|
* at the end
|
|
*/
|
|
need_sort = 1;
|
|
} else {
|
|
/* we need to increment digits */
|
|
break;
|
|
}
|
|
}
|
|
if (ok) {
|
|
break;
|
|
} else {
|
|
++digits;
|
|
}
|
|
}
|
|
if (digits == 8) {
|
|
ret = ISO_MANGLE_TOO_MUCH_FILES;
|
|
goto mangle_cleanup;
|
|
}
|
|
i = j;
|
|
}
|
|
|
|
/*
|
|
* If needed, sort again the files inside dir
|
|
*/
|
|
if (need_sort) {
|
|
qsort(children, nchildren, sizeof(void*), cmp_node_name);
|
|
}
|
|
|
|
ret = ISO_SUCCESS;
|
|
|
|
mangle_cleanup : ;
|
|
iso_htable_destroy(table, NULL);
|
|
return ret;
|
|
}
|
|
|
|
static
|
|
int mangle_dir(Ecma119Image *img, Ecma119Node *dir, int max_file_len,
|
|
int max_dir_len)
|
|
{
|
|
int ret;
|
|
size_t i;
|
|
|
|
ret = mangle_single_dir(img, dir, max_file_len, max_dir_len);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* recurse */
|
|
for (i = 0; i < dir->info.dir->nchildren; ++i) {
|
|
if (dir->info.dir->children[i]->type == ECMA119_DIR) {
|
|
ret = mangle_dir(img, dir->info.dir->children[i], max_file_len,
|
|
max_dir_len);
|
|
if (ret < 0) {
|
|
/* error */
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
static
|
|
int mangle_tree(Ecma119Image *img, Ecma119Node *dir, int recurse)
|
|
{
|
|
int max_file, max_dir;
|
|
Ecma119Node *root;
|
|
|
|
if (img->opts->untranslated_name_len > 0) {
|
|
max_file = max_dir = img->opts->untranslated_name_len;
|
|
} else if (img->opts->max_37_char_filenames) {
|
|
max_file = max_dir = 37;
|
|
} else if (img->opts->iso_level == 1) {
|
|
max_file = 12; /* 8 + 3 + 1 */
|
|
max_dir = 8;
|
|
} else {
|
|
max_file = max_dir = 31;
|
|
}
|
|
if (dir != NULL) {
|
|
root = dir;
|
|
} else if (img->eff_partition_offset > 0) {
|
|
root = img->partition_root;
|
|
} else {
|
|
root = img->root;
|
|
}
|
|
if (recurse) {
|
|
return mangle_dir(img, root, max_file, max_dir);
|
|
} else {
|
|
return mangle_single_dir(img, root, max_file, max_dir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new ECMA-119 node representing a placeholder for a relocated
|
|
* dir.
|
|
*
|
|
* See IEEE P1282, section 4.1.5 for details
|
|
*/
|
|
static
|
|
int create_placeholder(Ecma119Node *parent, Ecma119Node *real,
|
|
Ecma119Node **node)
|
|
{
|
|
Ecma119Node *ret;
|
|
|
|
ret = calloc(1, sizeof(Ecma119Node));
|
|
if (ret == NULL) {
|
|
return ISO_OUT_OF_MEM;
|
|
}
|
|
|
|
/*
|
|
* TODO
|
|
* If real is a dir, while placeholder is a file, ISO name restricctions
|
|
* are different, what to do?
|
|
*/
|
|
ret->iso_name = strdup(real->iso_name);
|
|
if (ret->iso_name == NULL) {
|
|
free(ret);
|
|
return ISO_OUT_OF_MEM;
|
|
}
|
|
|
|
/* take a ref to the IsoNode */
|
|
ret->node = real->node;
|
|
iso_node_ref(real->node);
|
|
ret->parent = parent;
|
|
ret->type = ECMA119_PLACEHOLDER;
|
|
ret->info.real_me = real;
|
|
ret->ino = real->ino;
|
|
ret->nlink = real->nlink;
|
|
|
|
*node = ret;
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
static
|
|
size_t max_child_name_len(Ecma119Node *dir)
|
|
{
|
|
size_t ret = 0, i;
|
|
for (i = 0; i < dir->info.dir->nchildren; i++) {
|
|
size_t len = strlen(dir->info.dir->children[i]->iso_name);
|
|
ret = MAX(ret, len);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Relocates a directory, as specified in Rock Ridge Specification
|
|
* (see IEEE P1282, section 4.1.5). This is needed when the number of levels
|
|
* on a directory hierarchy exceeds 8, or the length of a path is higher
|
|
* than 255 characters, as specified in ECMA-119, section 6.8.2.1
|
|
*/
|
|
static
|
|
int reparent(Ecma119Node *child, Ecma119Node *parent)
|
|
{
|
|
int ret;
|
|
size_t i;
|
|
Ecma119Node *placeholder;
|
|
|
|
/* replace the child in the original parent with a placeholder */
|
|
for (i = 0; i < child->parent->info.dir->nchildren; i++) {
|
|
if (child->parent->info.dir->children[i] == child) {
|
|
ret = create_placeholder(child->parent, child, &placeholder);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
child->parent->info.dir->children[i] = placeholder;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* just for debug, this should never happen... */
|
|
if (i == child->parent->info.dir->nchildren) {
|
|
return ISO_ASSERT_FAILURE;
|
|
}
|
|
|
|
/* keep track of the real parent */
|
|
child->info.dir->real_parent = child->parent;
|
|
|
|
/* add the child to its new parent */
|
|
child->parent = parent;
|
|
parent->info.dir->nchildren++;
|
|
parent->info.dir->children = realloc(parent->info.dir->children,
|
|
sizeof(void*) * parent->info.dir->nchildren);
|
|
parent->info.dir->children[parent->info.dir->nchildren - 1] = child;
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Reorder the tree, if necessary, to ensure that
|
|
* - the depth is at most 8
|
|
* - each path length is at most 255 characters
|
|
* This restriction is imposed by ECMA-119 specification (ECMA-119, 6.8.2.1).
|
|
*
|
|
* @param dir
|
|
* Dir we are currently processing
|
|
* @param level
|
|
* Level of the directory in the hierarchy
|
|
* @param pathlen
|
|
* Length of the path until dir, including it
|
|
* @return
|
|
* 1 success, < 0 error
|
|
*/
|
|
static
|
|
int reorder_tree(Ecma119Image *img, Ecma119Node *dir,
|
|
int dir_level, int dir_pathlen)
|
|
{
|
|
int ret, level, pathlen, newpathlen;
|
|
size_t max_path, i;
|
|
Ecma119Node *reloc, *child;
|
|
|
|
/* might change by relocation */
|
|
level = dir_level;
|
|
pathlen = dir_pathlen;
|
|
|
|
max_path = pathlen + 1 + max_child_name_len(dir);
|
|
|
|
if (level > 8 || max_path > 255) {
|
|
reloc = img->rr_reloc_node;
|
|
if (reloc == NULL) {
|
|
if (img->eff_partition_offset > 0) {
|
|
reloc = img->partition_root;
|
|
} else {
|
|
reloc = img->root;
|
|
}
|
|
}
|
|
ret = reparent(dir, reloc);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (reloc == img->root || reloc == img->partition_root) {
|
|
/*
|
|
* we are appended to the root's children now, so there is no
|
|
* need to recurse (the root will hit us again)
|
|
*/
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
/* dir is now the relocated Ecma119Node */
|
|
pathlen = 37 + 1; /* The dir name might get longer by mangling */
|
|
level = 2;
|
|
if (img->opts->rr_reloc_dir != NULL) {
|
|
pathlen += strlen(img->rr_reloc_node->iso_name) + 1;
|
|
if(img->opts->rr_reloc_dir[0] != 0)
|
|
level = 3;
|
|
}
|
|
}
|
|
|
|
if (ecma119_is_dedicated_reloc_dir(img, (Ecma119Node *) dir))
|
|
return ISO_SUCCESS;
|
|
|
|
for (i = 0; i < dir->info.dir->nchildren; i++) {
|
|
child = dir->info.dir->children[i];
|
|
if (child->type == ECMA119_DIR) {
|
|
newpathlen = pathlen + 1 + strlen(child->iso_name);
|
|
ret = reorder_tree(img, child, level + 1, newpathlen);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @param flag
|
|
* bit0= recursion
|
|
* bit1= count nodes rather than fill them into *nodes
|
|
* @return
|
|
* <0 error
|
|
* bit0= saw ino == 0
|
|
* bit1= saw ino != 0
|
|
*/
|
|
static
|
|
int make_node_array(Ecma119Image *img, Ecma119Node *dir,
|
|
Ecma119Node **nodes, size_t nodes_size, size_t *node_count,
|
|
int flag)
|
|
{
|
|
int ret, result = 0;
|
|
size_t i;
|
|
Ecma119Node *child;
|
|
|
|
if (!(flag & 1)) {
|
|
*node_count = 0;
|
|
if (!(flag & 2)) {
|
|
/* Register the tree root node */
|
|
if (*node_count >= nodes_size) {
|
|
iso_msg_submit(img->image->id, ISO_ASSERT_FAILURE, 0,
|
|
"Programming error: Overflow of hardlink sort array");
|
|
return ISO_ASSERT_FAILURE;
|
|
}
|
|
nodes[*node_count] = dir;
|
|
}
|
|
result|= (dir->ino == 0 ? 1 : 2);
|
|
(*node_count)++;
|
|
}
|
|
|
|
for (i = 0; i < dir->info.dir->nchildren; i++) {
|
|
child = dir->info.dir->children[i];
|
|
if (!(flag & 2)) {
|
|
if (*node_count >= nodes_size) {
|
|
iso_msg_submit(img->image->id, ISO_ASSERT_FAILURE, 0,
|
|
"Programming error: Overflow of hardlink sort array");
|
|
return ISO_ASSERT_FAILURE;
|
|
}
|
|
nodes[*node_count] = child;
|
|
}
|
|
result|= (child->ino == 0 ? 1 : 2);
|
|
(*node_count)++;
|
|
|
|
if (child->type == ECMA119_DIR) {
|
|
ret = make_node_array(img, child,
|
|
nodes, nodes_size, node_count, flag | 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* @param flag
|
|
* bit0= compare stat properties and attributes
|
|
* bit1= treat all nodes with image ino == 0 as unique
|
|
*/
|
|
static
|
|
int ecma119_node_cmp_flag(const void *v1, const void *v2, int flag)
|
|
{
|
|
int ret;
|
|
Ecma119Node *n1, *n2;
|
|
|
|
n1 = *((Ecma119Node **) v1);
|
|
n2 = *((Ecma119Node **) v2);
|
|
if (n1 == n2)
|
|
return 0;
|
|
|
|
ret = iso_node_cmp_flag(n1->node, n2->node, flag & (1 | 2));
|
|
return ret;
|
|
}
|
|
|
|
static
|
|
int ecma119_node_cmp_hard(const void *v1, const void *v2)
|
|
{
|
|
return ecma119_node_cmp_flag(v1, v2, 1);
|
|
}
|
|
|
|
static
|
|
int ecma119_node_cmp_nohard(const void *v1, const void *v2)
|
|
{
|
|
return ecma119_node_cmp_flag(v1, v2, 1 | 2);
|
|
}
|
|
|
|
static
|
|
int family_set_ino(Ecma119Image *img, Ecma119Node **nodes, size_t family_start,
|
|
size_t next_family, ino_t img_ino, ino_t prev_ino, int flag)
|
|
{
|
|
size_t i;
|
|
|
|
if (img_ino != 0) {
|
|
/* Check whether this is the same img_ino as in the previous
|
|
family (e.g. by property divergence of imported hardlink).
|
|
*/
|
|
if (img_ino == prev_ino)
|
|
img_ino = 0;
|
|
|
|
/* Accept only if it is within the 32 bit range. */
|
|
if (((uint64_t) img_ino) > 0xffffffff)
|
|
img_ino = 0;
|
|
|
|
}
|
|
if (img_ino == 0) {
|
|
img_ino = img_give_ino_number(img->image, 0);
|
|
}
|
|
for (i = family_start; i < next_family; i++) {
|
|
nodes[i]->ino = img_ino;
|
|
nodes[i]->nlink = next_family - family_start;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static
|
|
int match_hardlinks(Ecma119Image *img, Ecma119Node *dir, int flag)
|
|
{
|
|
int ret;
|
|
size_t nodes_size = 0, node_count = 0, i, family_start;
|
|
Ecma119Node **nodes = NULL;
|
|
unsigned int fs_id;
|
|
dev_t dev_id;
|
|
ino_t img_ino = 0, prev_ino = 0;
|
|
|
|
ret = make_node_array(img, dir, nodes, nodes_size, &node_count, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
nodes_size = node_count;
|
|
nodes = (Ecma119Node **) calloc(sizeof(Ecma119Node *), nodes_size);
|
|
if (nodes == NULL)
|
|
return ISO_OUT_OF_MEM;
|
|
ret = make_node_array(img, dir, nodes, nodes_size, &node_count, 0);
|
|
if (ret < 0)
|
|
goto ex;
|
|
|
|
/* Sort according to id tuples, IsoFileSrc identity, properties, xattr. */
|
|
if (img->opts->hardlinks)
|
|
qsort(nodes, node_count, sizeof(Ecma119Node *), ecma119_node_cmp_hard);
|
|
else
|
|
qsort(nodes, node_count, sizeof(Ecma119Node *),
|
|
ecma119_node_cmp_nohard);
|
|
|
|
/* Hand out image inode numbers to all Ecma119Node.ino == 0 .
|
|
Same sorting rank gets same inode number.
|
|
Split those image inode number families where the sort criterion
|
|
differs.
|
|
*/
|
|
iso_node_get_id(nodes[0]->node, &fs_id, &dev_id, &img_ino, 1);
|
|
family_start = 0;
|
|
for (i = 1; i < node_count; i++) {
|
|
if (nodes[i]->type != ECMA119_DIR &&
|
|
ecma119_node_cmp_hard(nodes + (i - 1), nodes + i) == 0) {
|
|
/* Still in same ino family */
|
|
if (img_ino == 0) { /* Just in case any member knows its img_ino */
|
|
iso_node_get_id(nodes[0]->node, &fs_id, &dev_id, &img_ino, 1);
|
|
}
|
|
continue;
|
|
}
|
|
family_set_ino(img, nodes, family_start, i, img_ino, prev_ino, 0);
|
|
prev_ino = img_ino;
|
|
iso_node_get_id(nodes[i]->node, &fs_id, &dev_id, &img_ino, 1);
|
|
family_start = i;
|
|
}
|
|
family_set_ino(img, nodes, family_start, i, img_ino, prev_ino, 0);
|
|
|
|
ret = ISO_SUCCESS;
|
|
ex:;
|
|
if (nodes != NULL)
|
|
free((char *) nodes);
|
|
return ret;
|
|
}
|
|
|
|
int ecma119_tree_create(Ecma119Image *img)
|
|
{
|
|
int ret;
|
|
Ecma119Node *root;
|
|
|
|
ret = create_tree(img, (IsoNode*)img->image->root, &root, 1, 0, 0);
|
|
if (ret <= 0) {
|
|
if (ret == 0) {
|
|
/* unexpected error, root ignored!! This can't happen */
|
|
ret = ISO_ASSERT_FAILURE;
|
|
}
|
|
return ret;
|
|
}
|
|
if (img->eff_partition_offset > 0) {
|
|
img->partition_root = root;
|
|
} else {
|
|
img->root = root;
|
|
}
|
|
|
|
iso_msg_debug(img->image->id, "Matching hardlinks...");
|
|
ret = match_hardlinks(img, root, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
iso_msg_debug(img->image->id, "Sorting the low level tree...");
|
|
sort_tree(root);
|
|
|
|
iso_msg_debug(img->image->id, "Mangling names...");
|
|
ret = mangle_tree(img, NULL, 1);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (img->opts->rockridge && !img->opts->allow_deep_paths) {
|
|
|
|
/* Relocate deep directories, according to RRIP, 4.1.5 */
|
|
ret = reorder_tree(img, root, 1, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* and we need to remangle the root directory, as the function
|
|
* above could insert new directories into the relocation directory.
|
|
* Note that recurse = 0, as we don't need to recurse.
|
|
*/
|
|
ret = mangle_tree(img, img->rr_reloc_node, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ISO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Search the tree for a certain IsoNode and return its owning Ecma119Node
|
|
* or NULL.
|
|
*/
|
|
static
|
|
Ecma119Node *search_iso_node(Ecma119Node *root, IsoNode *node)
|
|
{
|
|
size_t i;
|
|
Ecma119Node *res = NULL;
|
|
|
|
if (root->node == node)
|
|
return root;
|
|
for (i = 0; i < root->info.dir->nchildren && res == NULL; i++) {
|
|
if (root->info.dir->children[i]->type == ECMA119_DIR)
|
|
res = search_iso_node(root->info.dir->children[i], node);
|
|
else if (root->info.dir->children[i]->node == node)
|
|
res = root->info.dir->children[i];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
Ecma119Node *ecma119_search_iso_node(Ecma119Image *img, IsoNode *node)
|
|
{
|
|
Ecma119Node *res = NULL;
|
|
|
|
if (img->root != NULL)
|
|
res = search_iso_node(img->root, node);
|
|
return res;
|
|
}
|
|
|