libisofs/libisofs/ecma119_tree.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;
}