/*
 * 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.
 */

#include "../config.h"

/* 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;
}

