/*
 * Copyright (c) 2007 Vreixo Formoso
 * Copyright (c) 2007 Mario Danic
 * 
 * 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 as 
 * published by the Free Software Foundation. See COPYING file for details.
 */

#include "rockridge.h"
#include "node.h"
#include "ecma119_tree.h"
#include "writer.h"
#include "messages.h"
#include "image.h"

#include <string.h>

static
int susp_append(Ecma119Image *t, struct susp_info *susp, uint8_t *data)
{
    susp->n_susp_fields++;
    susp->susp_fields = realloc(susp->susp_fields, sizeof(void*)
                                * susp->n_susp_fields);
    if (susp->susp_fields == NULL) {
        return ISO_OUT_OF_MEM;
    }
    susp->susp_fields[susp->n_susp_fields - 1] = data;
    susp->suf_len += data[2];
    return ISO_SUCCESS;
}

static
int susp_append_ce(Ecma119Image *t, struct susp_info *susp, uint8_t *data)
{
    susp->n_ce_susp_fields++;
    susp->ce_susp_fields = realloc(susp->ce_susp_fields, sizeof(void*)
                                   * susp->n_ce_susp_fields);
    if (susp->ce_susp_fields == NULL) {
        return ISO_OUT_OF_MEM;
    }
    susp->ce_susp_fields[susp->n_ce_susp_fields - 1] = data;
    susp->ce_len += data[2];
    return ISO_SUCCESS;
}

static
uid_t px_get_uid(Ecma119Image *t, Ecma119Node *n)
{
    if (t->replace_uid) {
        return t->uid;
    } else {
        return n->node->uid;
    }
}

static
uid_t px_get_gid(Ecma119Image *t, Ecma119Node *n)
{
    if (t->replace_gid) {
        return t->gid;
    } else {
        return n->node->gid;
    }
}

static
mode_t px_get_mode(Ecma119Image *t, Ecma119Node *n)
{
    if ((n->type == ECMA119_DIR || n->type == ECMA119_PLACEHOLDER)) {
        if (t->replace_dir_mode) {
            return (n->node->mode & S_IFMT) | t->dir_mode;
        }
    } else {
        if (t->replace_file_mode) {
            return (n->node->mode & S_IFMT) | t->file_mode;
        }
    }
    return n->node->mode;
}

/**
 * Add a PX System Use Entry. The PX System Use Entry is used to add POSIX 
 * file attributes, such as access permissions or user and group id, to a 
 * ECMA 119 directory record. (RRIP, 4.1.1)
 */
static
int rrip_add_PX(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp)
{
    uint8_t *PX = malloc(44);
    if (PX == NULL) {
        return ISO_OUT_OF_MEM;
    }

    PX[0] = 'P';
    PX[1] = 'X';
    PX[2] = 44;
    PX[3] = 1;
    iso_bb(&PX[4], px_get_mode(t, n), 4);
    iso_bb(&PX[12], n->nlink, 4);
    iso_bb(&PX[20], px_get_uid(t, n), 4);
    iso_bb(&PX[28], px_get_gid(t, n), 4);
    iso_bb(&PX[36], n->ino, 4);

    return susp_append(t, susp, PX);
}

/**
 * Add to the given tree node a TF System Use Entry, used to record some
 * time stamps related to the file (RRIP, 4.1.6).
 */
static
int rrip_add_TF(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp)
{
    IsoNode *iso;
    uint8_t *TF = malloc(5 + 3 * 7);
    if (TF == NULL) {
        return ISO_OUT_OF_MEM;
    }

    TF[0] = 'T';
    TF[1] = 'F';
    TF[2] = 5 + 3 * 7;
    TF[3] = 1;
    TF[4] = (1 << 1) | (1 << 2) | (1 << 3);
    
    iso = n->node;
    iso_datetime_7(&TF[5], t->replace_timestamps ? t->timestamp : iso->mtime,
                   t->always_gmt);
    iso_datetime_7(&TF[12], t->replace_timestamps ? t->timestamp : iso->atime,
                   t->always_gmt);
    iso_datetime_7(&TF[19], t->replace_timestamps ? t->timestamp : iso->ctime,
                   t->always_gmt);
    return susp_append(t, susp, TF);
}

/**
 * Add a PL System Use Entry, used to record the location of the original 
 * parent directory of a directory which has been relocated.
 * 
 * This is special because it doesn't modify the susp fields of the directory
 * that gets passed to it; it modifies the susp fields of the ".." entry in
 * that directory.
 * 
 * See RRIP, 4.1.5.2 for more details.
 */
static
int rrip_add_PL(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp)
{
    uint8_t *PL;

    if (n->type != ECMA119_DIR || n->info.dir->real_parent == NULL) {
        /* should never occur */
        return ISO_ASSERT_FAILURE;
    }

    PL = malloc(12);
    if (PL == NULL) {
        return ISO_OUT_OF_MEM;
    }

    PL[0] = 'P';
    PL[1] = 'L';
    PL[2] = 12;
    PL[3] = 1;

    /* write the location of the real parent, already computed */
    iso_bb(&PL[4], n->info.dir->real_parent->info.dir->block, 4);
    return susp_append(t, susp, PL);
}

/**
 * Add a RE System Use Entry to the given tree node. The purpose of the 
 * this System Use Entry is to indicate to an RRIP-compliant receiving
 * system that the Directory Record in which an "RE" System Use Entry is 
 * recorded has been relocated from another position in the original 
 * Directory Hierarchy.
 * 
 * See RRIP, 4.1.5.3 for more details.
 */
static
int rrip_add_RE(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp)
{
    uint8_t *RE = malloc(4);
    if (RE == NULL) {
        return ISO_OUT_OF_MEM;
    }

    RE[0] = 'R';
    RE[1] = 'E';
    RE[2] = 4;
    RE[3] = 1;
    return susp_append(t, susp, RE);
}

/**
 * Add a PN System Use Entry to the given tree node. 
 * The PN System Use Entry is used to store the device number, and it's
 * mandatory if the tree node corresponds to a character or block device.
 * 
 * See RRIP, 4.1.2 for more details.
 */
static
int rrip_add_PN(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp)
{
    IsoSpecial *node;
    uint8_t *PN;

    node = (IsoSpecial*)n->node;
    if (node->node.type != LIBISO_SPECIAL) {
        /* should never occur */
        return ISO_ASSERT_FAILURE;
    }

    PN = malloc(20);
    if (PN == NULL) {
        return ISO_OUT_OF_MEM;
    }

    PN[0] = 'P';
    PN[1] = 'N';
    PN[2] = 20;
    PN[3] = 1;
    iso_bb(&PN[4], node->dev >> 32, 4);
    iso_bb(&PN[12], node->dev & 0xffffffff, 4);
    return susp_append(t, susp, PN);
}

/**
 * Add to the given tree node a CL System Use Entry, that is used to record 
 * the new location of a directory which has been relocated.
 * 
 * See RRIP, 4.1.5.1 for more details.
 */
static
int rrip_add_CL(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp)
{
    uint8_t *CL;
    if (n->type != ECMA119_PLACEHOLDER) {
        /* should never occur */
        return ISO_ASSERT_FAILURE;
    }
    CL = malloc(12);
    if (CL == NULL) {
        return ISO_OUT_OF_MEM;
    }

    CL[0] = 'C';
    CL[1] = 'L';
    CL[2] = 12;
    CL[3] = 1;
    iso_bb(&CL[4], n->info.real_me->info.dir->block, 4);
    return susp_append(t, susp, CL);
}

/**
 * Convert a RR filename to the requested charset. On any conversion error, 
 * the original name will be used.
 */
static
char *get_rr_fname(Ecma119Image *t, const char *str)
{
    int ret;
    char *name;

    if (!strcmp(t->input_charset, t->output_charset)) {
        /* no conversion needed */
        return strdup(str);
    }

    ret = strconv(str, t->input_charset, t->output_charset, &name);
    if (ret < 0) {
        /* TODO we should check for possible cancelation */
        iso_msg_submit(t->image->id, ISO_FILENAME_WRONG_CHARSET, ret,
                  "Charset conversion error. Can't convert %s from %s to %s",
                  str, t->input_charset, t->output_charset);

        /* use the original name, it's the best we can do */
        name = strdup(str);
    }

    return name;
}

/**
 * Add a NM System Use Entry to the given tree node. The purpose of this
 * System Use Entry is to store the content of an Alternate Name to support 
 * POSIX-style or other names. 
 * 
 * See RRIP, 4.1.4 for more details.
 * 
 * @param size
 *     Length of the name to be included into the NM
 * @param flags
 * @param ce
 *     Whether to add or not to CE
 */
static
int rrip_add_NM(Ecma119Image *t, struct susp_info *susp, char *name, int size,
                int flags, int ce)
{
    uint8_t *NM = malloc(size + 5);
    if (NM == NULL) {
        return ISO_OUT_OF_MEM;
    }

    NM[0] = 'N';
    NM[1] = 'M';
    NM[2] = size + 5;
    NM[3] = 1;
    NM[4] = flags;
    if (size) {
        memcpy(&NM[5], name, size);
    }
    if (ce) {
        return susp_append_ce(t, susp, NM);
    } else {
        return susp_append(t, susp, NM);
    }
}

/**
 * Add a new SL component (RRIP, 4.1.3.1) to a list of components.
 * 
 * @param n
 *     Number of components. It will be updated.
 * @param compos
 *     Pointer to the list of components.
 * @param s
 *     The component content
 * @param size
 *     Size of the component content
 * @param fl
 *     Flags
 * @return
 *     1 on success, < 0 on error
 */
static
int rrip_SL_append_comp(size_t *n, uint8_t ***comps, char *s, int size, char fl)
{
    uint8_t *comp = malloc(size + 2);
    if (comp == NULL) {
        return ISO_OUT_OF_MEM;
    }

    (*n)++;
    comp[0] = fl;
    comp[1] = size;
    *comps = realloc(*comps, (*n) * sizeof(void*));
    if (*comps == NULL) {
        free(comp);
        return ISO_OUT_OF_MEM;
    }
    (*comps)[(*n) - 1] = comp;

    if (size) {
        memcpy(&comp[2], s, size);
    }
    return ISO_SUCCESS;
}

/**
 * Add a SL System Use Entry to the given tree node. This is used to store 
 * the content of a symbolic link, and is mandatory if the tree node
 * indicates a symbolic link (RRIP, 4.1.3).
 * 
 * @param comp
 *     Components of the SL System Use Entry. If they don't fit in a single
 *     SL, more than one SL will be added.
 * @param n
 *     Number of components in comp
 * @param ce
 *     Whether to add to a continuation area or system use field.
 */
static
int rrip_add_SL(Ecma119Image *t, struct susp_info *susp, uint8_t **comp,
                size_t n, int ce)
{
    int ret, i, j;

    int total_comp_len = 0;
    size_t pos, written = 0;

    uint8_t *SL;

    for (i = 0; i < n; i++) {

        total_comp_len += comp[i][1] + 2;
        if (total_comp_len > 250) {
            /* we need a new SL entry */
            total_comp_len -= comp[i][1] + 2;
            SL = malloc(total_comp_len + 5);
            if (SL == NULL) {
                return ISO_OUT_OF_MEM;
            }

            SL[0] = 'S';
            SL[1] = 'L';
            SL[2] = total_comp_len + 5;
            SL[3] = 1;
            SL[4] = 1; /* CONTINUE */
            pos = 5;
            for (j = written; j < i; j++) {
                memcpy(&SL[pos], comp[j], comp[j][1] + 2);
                pos += comp[j][1] + 2;
            }

            /*
             * In this case we are sure we're writting to CE. Check for
             * debug purposes
             */
            if (ce == 0) {
                return ISO_ASSERT_FAILURE;
            }
            ret = susp_append_ce(t, susp, SL);
            if (ret < 0) {
                return ret;
            }
            written = i;
            total_comp_len = comp[i][1] + 2;
        }
    }

    SL = malloc(total_comp_len + 5);
    if (SL == NULL) {
        return ISO_OUT_OF_MEM;
    }

    SL[0] = 'S';
    SL[1] = 'L';
    SL[2] = total_comp_len + 5;
    SL[3] = 1;
    SL[4] = 0;
    pos = 5;

    for (j = written; j < n; j++) {
        memcpy(&SL[pos], comp[j], comp[j][1] + 2);
        pos += comp[j][1] + 2;
    }
    if (ce) {
        ret = susp_append_ce(t, susp, SL);
    } else {
        ret = susp_append(t, susp, SL);
    }
    return ret;
}

/**
 * Add a SUSP "ER" System Use Entry to identify the Rock Ridge specification.
 * 
 * The "ER" System Use Entry is used to uniquely identify a specification
 * compliant with SUSP. This method adds to the given tree node "." entry
 * the "ER" corresponding to the RR protocol.
 * 
 * See SUSP, 5.5 and RRIP, 4.3 for more details.
 */
static
int rrip_add_ER(Ecma119Image *t, struct susp_info *susp)
{
    unsigned char *ER = malloc(182);
    if (ER == NULL) {
        return ISO_OUT_OF_MEM;
    }

    ER[0] = 'E';
    ER[1] = 'R';
    ER[2] = 182;
    ER[3] = 1;
    ER[4] = 9;
    ER[5] = 72;
    ER[6] = 93;
    ER[7] = 1;
    memcpy(&ER[8], "IEEE_1282", 9);
    memcpy(&ER[17], "THE IEEE 1282 PROTOCOL PROVIDES SUPPORT FOR POSIX "
        "FILE SYSTEM SEMANTICS.", 72);
    memcpy(&ER[89], "PLEASE CONTACT THE IEEE STANDARDS DEPARTMENT, "
        "PISCATAWAY, NJ, USA FOR THE 1282 SPECIFICATION.", 93);

    /** This always goes to continuation area */
    return susp_append_ce(t, susp, ER);
}

/**
 * Add a CE System Use Entry to the given tree node. A "CE" is used to add
 * a continuation area, where additional System Use Entry can be written.
 * (SUSP, 5.1).
 */
static
int susp_add_CE(Ecma119Image *t, size_t ce_len, struct susp_info *susp)
{
    uint8_t *CE = malloc(28);
    if (CE == NULL) {
        return ISO_OUT_OF_MEM;
    }

    CE[0] = 'C';
    CE[1] = 'E';
    CE[2] = 28;
    CE[3] = 1;
    iso_bb(&CE[4], susp->ce_block, 4);
    iso_bb(&CE[12], susp->ce_len, 4);
    iso_bb(&CE[20], ce_len, 4);

    return susp_append(t, susp, CE);
}

/**
 * Add a SP System Use Entry. The SP provide an identifier that the SUSP is 
 * used within the volume. The SP shall be recorded in the "." entry of the 
 * root directory. See SUSP, 5.3 for more details.
 */
static
int susp_add_SP(Ecma119Image *t, struct susp_info *susp)
{
    unsigned char *SP = malloc(7);
    if (SP == NULL) {
        return ISO_OUT_OF_MEM;
    }

    SP[0] = 'S';
    SP[1] = 'P';
    SP[2] = (char)7;
    SP[3] = (char)1;
    SP[4] = 0xbe;
    SP[5] = 0xef;
    SP[6] = 0;
    return susp_append(t, susp, SP);
}

/**
 * Compute the length needed for write all RR and SUSP entries for a given
 * node.
 * 
 * @param type
 *      0 normal entry, 1 "." entry for that node (it is a dir), 2 ".."
 *      for that node (i.e., it will refer to the parent)
 * @param space
 *      Available space in the System Use Area for the directory record.
 * @param ce
 *      Will be filled with the space needed in a CE
 * @return
 *      The size needed for the RR entries in the System Use Area
 */
size_t rrip_calc_len(Ecma119Image *t, Ecma119Node *n, int type, size_t space,
                     size_t *ce)
{
    size_t su_size;

    /* space min is 255 - 33 - 37 = 185
     * At the same time, it is always an odd number, but we need to pad it
     * propertly to ensure the length of a directory record is a even number
     * (ECMA-119, 9.1.13). Thus, in fact the real space is always space - 1
     */
    space--;
    *ce = 0;

    /* PX and TF, we are sure they always fit in SUA */
    su_size = 44 + 26;

    if (n->type == ECMA119_DIR) {
        if (n->info.dir->real_parent != NULL) {
            /* it is a reallocated entry */
            if (type == 2) {
                /* we need to add a PL entry */
                su_size += 12;
            } else if (type == 0) {
                /* we need to add a RE entry */
                su_size += 4;
            }
        }
    } else if (n->type == ECMA119_SPECIAL) {
        if (S_ISBLK(n->node->mode) || S_ISCHR(n->node->mode)) {
            /* block or char device, we need a PN entry */
            su_size += 20;
        }
    } else if (n->type == ECMA119_PLACEHOLDER) {
        /* we need the CL entry */
        su_size += 12;
    }

    if (type == 0) {
        char *name = get_rr_fname(t, n->node->name);
        size_t namelen = strlen(name);
        free(name);

        /* NM entry */
        if (su_size + 5 + namelen <= space) {
            /* ok, it fits in System Use Area */
            su_size += 5 + namelen;
        } else {
            /* the NM will be divided in a CE */
            namelen = namelen - (space - su_size - 5 - 28);
            *ce = 5 + namelen;
            su_size = space;
        }
        if (n->type == ECMA119_SYMLINK) {
            /* 
             * for symlinks, we also need to write the SL
             */
            char *dest, *cur, *prev;
            size_t sl_len = 5;
            int cew = (*ce != 0); /* are we writing to CE? */

            dest = get_rr_fname(t, ((IsoSymlink*)n->node)->dest);
            prev = dest;
            cur = strchr(prev, '/');
            while (1) {
                size_t clen;
                if (cur) {
                    clen = cur - prev;
                } else {
                    /* last component */
                    clen = strlen(prev);
                }

                if (clen == 1 && prev[0] == '.') {
                    clen = 0;
                } else if (clen == 2 && prev[0] == '.' && prev[1] == '.') {
                    clen = 0;
                }

                /* flags and len for each component record (RRIP, 4.1.3.1) */
                clen += 2;

                if (!cew) {
                    /* we are still writing to the SUA */
                    if (su_size + sl_len + clen > space) {
                        /* 
                         * ok, we need a Continuation Area anyway
                         * TODO this can be handled better, but for now SL
                         * will be completelly moved into the CA
                         */
                        if (su_size + 28 <= space) {
                            /* the CE entry fills without reducing NM */
                            su_size += 28;
                        } else {
                            /* we need to reduce NM */
                            *ce = (28 - (space - su_size)) + 5;
                            su_size = space;
                        }
                        cew = 1;
                    } else {
                        sl_len += clen;
                    }
                }
                if (cew) {
                    if (sl_len + clen > 255) {
                        /* we need an additional SL entry */
                        if (clen > 250) {
                            /* 
                             * case 1, component too large to fit in a 
                             * single SL entry. Thus, the component need
                             * to be divided anyway.
                             * Note than clen can be up to 255 + 2 = 257.
                             * 
                             * First, we check how many bytes fit in current
                             * SL field 
                             */
                            int fit = 255 - sl_len - 2;
                            if (clen - 250 <= fit) {
                                /* 
                                 * the component can be divided between this
                                 * and another SL entry
                                 */
                                *ce += 255; /* this SL, full */
                                sl_len = 5 + (clen - fit);
                            } else {
                                /*
                                 * the component will need a 2rd SL entry in
                                 * any case, so we prefer to don't write 
                                 * anything in this SL
                                 */
                                *ce += sl_len + 255;
                                sl_len = 5 + (clen - 250) + 2;
                            }
                        } else {
                            /* case 2, create a new SL entry */
                            *ce += sl_len;
                            sl_len = 5 + clen;
                        }
                    } else {
                        sl_len += clen;
                    }
                }

                if (!cur || cur[1] == '\0') {
                    /* cur[1] can be \0 if dest ends with '/' */
                    break;
                }
                prev = cur + 1;
                cur = strchr(prev, '/');
            }

            free(dest);

            /* and finally write the pending SL field */
            if (!cew) {
                /* the whole SL fits into the SUA */
                su_size += sl_len;
            } else {
                *ce += sl_len;
            }

        }
    } else {

        /* "." or ".." entry */
        su_size += 5; /* NM field */
        if (type == 1 && n->parent == NULL) {
            /* 
             * "." for root directory 
             * we need to write SP and ER entries. The first fits in SUA,
             * ER needs a Continuation Area, thus we also need a CE entry
             */
            su_size += 7 + 28; /* SP + CE */
            *ce = 182; /* ER */
        }
    }

    /*
     * The System Use field inside the directory record must be padded if
     * it is an odd number (ECMA-119, 9.1.13)
     */
    su_size += (su_size % 2);
    return su_size;
}

/**
 * Free all info in a struct susp_info.
 */
static
void susp_info_free(struct susp_info* susp)
{
    size_t i;

    for (i = 0; i < susp->n_susp_fields; ++i) {
        free(susp->susp_fields[i]);
    }
    free(susp->susp_fields);

    for (i = 0; i < susp->n_ce_susp_fields; ++i) {
        free(susp->ce_susp_fields[i]);
    }
    free(susp->ce_susp_fields);
}

/**
 * Fill a struct susp_info with the RR/SUSP entries needed for a given
 * node.
 * 
 * @param type
 *      0 normal entry, 1 "." entry for that node (it is a dir), 2 ".."
 *      for that node (i.e., it will refer to the parent)
 * @param space
 *      Available space in the System Use Area for the directory record.
 * @param info
 *      Pointer to the struct susp_info where the entries will be stored.
 *      If some entries need to go to a Continuation Area, they will be added
 *      to the existing ce_susp_fields, and ce_len will be incremented
 *      propertly. Please ensure ce_block is initialized propertly.
 * @return
 *      1 success, < 0 error
 */
int rrip_get_susp_fields(Ecma119Image *t, Ecma119Node *n, int type,
                         size_t space, struct susp_info *info)
{
    int ret;
    size_t i;
    Ecma119Node *node;
    char *name = NULL;
    char *dest = NULL;

    if (t == NULL || n == NULL || info == NULL) {
        return ISO_NULL_POINTER;
    }
    if (type < 0 || type > 2 || space < 185) {
        /* space min is 255 - 33 - 37 = 185 */
        return ISO_WRONG_ARG_VALUE;
    }

    if (type == 2 && n->parent != NULL) {
        node = n->parent;
    } else {
        node = n;
    }

    /* space min is 255 - 33 - 37 = 185
     * At the same time, it is always an odd number, but we need to pad it
     * propertly to ensure the length of a directory record is a even number
     * (ECMA-119, 9.1.13). Thus, in fact the real space is always space - 1
     */
    space--;

    /* 
     * SP must be the first entry for the "." record of the root directory
     * (SUSP, 5.3)
     */
    if (type == 1 && n->parent == NULL) {
        ret = susp_add_SP(t, info);
        if (ret < 0) {
            goto add_susp_cleanup;
        }
    }

    /* PX and TF, we are sure they always fit in SUA */
    ret = rrip_add_PX(t, node, info);
    if (ret < 0) {
        goto add_susp_cleanup;
    }
    ret = rrip_add_TF(t, node, info);
    if (ret < 0) {
        goto add_susp_cleanup;
    }

    if (n->type == ECMA119_DIR) {
        if (n->info.dir->real_parent != NULL) {
            /* it is a reallocated entry */
            if (type == 2) {
                /* 
                 * we need to add a PL entry
                 * Note that we pass "n" as parameter, not "node" 
                 */
                ret = rrip_add_PL(t, n, info);
                if (ret < 0) {
                    goto add_susp_cleanup;
                }
            } else if (type == 0) {
                /* we need to add a RE entry */
                ret = rrip_add_RE(t, node, info);
                if (ret < 0) {
                    goto add_susp_cleanup;
                }
            }
        }
    } else if (n->type == ECMA119_SPECIAL) {
        if (S_ISBLK(n->node->mode) || S_ISCHR(n->node->mode)) {
            /* block or char device, we need a PN entry */
            ret = rrip_add_PN(t, node, info);
            if (ret < 0) {
                goto add_susp_cleanup;
            }
        }
    } else if (n->type == ECMA119_PLACEHOLDER) {
        /* we need the CL entry */
        ret = rrip_add_CL(t, node, info);
        if (ret < 0) {
            goto add_susp_cleanup;
        }
    }

    if (type == 0) {
        size_t sua_free; /* free space in the SUA */
        int nm_type = 0; /* 0 whole entry in SUA, 1 part in CE */
        size_t ce_len = 0; /* len of the CE */
        size_t namelen;

        /* this two are only defined for symlinks */
        uint8_t **comps= NULL; /* components of the SL field */
        size_t n_comp = 0; /* number of components */

        name = get_rr_fname(t, n->node->name);
        namelen = strlen(name);

        sua_free = space - info->suf_len;

        /* NM entry */
        if (5 + namelen <= sua_free) {
            /* ok, it fits in System Use Area */
            sua_free -= (5 + namelen);
            nm_type = 0;
        } else {
            /* the NM will be divided in a CE */
            nm_type = 1;
            namelen = namelen - (sua_free - 5 - 28);
            ce_len = 5 + namelen;
            sua_free = 0;
        }
        if (n->type == ECMA119_SYMLINK) {
            /* 
             * for symlinks, we also need to write the SL
             */
            char *cur, *prev;
            size_t sl_len = 5;
            int cew = (nm_type == 1); /* are we writing to CE? */

            dest = get_rr_fname(t, ((IsoSymlink*)n->node)->dest);
            prev = dest;
            cur = strchr(prev, '/');
            while (1) {
                size_t clen;
                char cflag = 0; /* component flag (RRIP, 4.1.3.1) */
                if (cur) {
                    clen = cur - prev;
                } else {
                    /* last component */
                    clen = strlen(prev);
                }

                if (clen == 0) {
                    /* this refers to the roor directory, '/' */
                    cflag = 1 << 3;
                }
                if (clen == 1 && prev[0] == '.') {
                    clen = 0;
                    cflag = 1 << 1;
                } else if (clen == 2 && prev[0] == '.' && prev[1] == '.') {
                    clen = 0;
                    cflag = 1 << 2;
                }

                /* flags and len for each component record (RRIP, 4.1.3.1) */
                clen += 2;

                if (!cew) {
                    /* we are still writing to the SUA */
                    if (sl_len + clen > sua_free) {
                        /* 
                         * ok, we need a Continuation Area anyway
                         * TODO this can be handled better, but for now SL
                         * will be completelly moved into the CA
                         */
                        if (28 <= sua_free) {
                            /* the CE entry fills without reducing NM */
                            sua_free -= 28;
                            cew = 1;
                        } else {
                            /* we need to reduce NM */
                            nm_type = 1;
                            ce_len = (28 - sua_free) + 5;
                            sua_free = 0;
                            cew = 1;
                        }
                    } else {
                        /* add the component */
                        ret = rrip_SL_append_comp(&n_comp, &comps, prev, 
                                                  clen - 2, cflag);
                        if (ret < 0) {
                            goto add_susp_cleanup;
                        }
                        sl_len += clen;
                    }
                }
                if (cew) {
                    if (sl_len + clen > 255) {
                        /* we need an addition SL entry */
                        if (clen > 250) {
                            /* 
                             * case 1, component too large to fit in a 
                             * single SL entry. Thus, the component need
                             * to be divided anyway.
                             * Note than clen can be up to 255 + 2 = 257.
                             * 
                             * First, we check how many bytes fit in current
                             * SL field 
                             */
                            int fit = 255 - sl_len - 2;
                            if (clen - 250 <= fit) {
                                /* 
                                 * the component can be divided between this
                                 * and another SL entry
                                 */
                                ret = rrip_SL_append_comp(&n_comp, &comps,
                                                          prev, fit, 0x01);
                                if (ret < 0) {
                                    goto add_susp_cleanup;
                                }
                                /* 
                                 * and another component, that will go in 
                                 * other SL entry
                                 */
                                ret = rrip_SL_append_comp(&n_comp, &comps, prev
                                        + fit, clen - fit - 2, 0);
                                if (ret < 0) {
                                    goto add_susp_cleanup;
                                }
                                ce_len += 255; /* this SL, full */
                                sl_len = 5 + (clen - fit);
                            } else {
                                /*
                                 * the component will need a 2rd SL entry in
                                 * any case, so we prefer to don't write 
                                 * anything in this SL
                                 */
                                ret = rrip_SL_append_comp(&n_comp, &comps,
                                                          prev, 248, 0x01);
                                if (ret < 0) {
                                    goto add_susp_cleanup;
                                }
                                ret = rrip_SL_append_comp(&n_comp, &comps, prev
                                        + 248, strlen(prev + 248), 0x00);
                                if (ret < 0) {
                                    goto add_susp_cleanup;
                                }
                                ce_len += sl_len + 255;
                                sl_len = 5 + (clen - 250) + 2;
                            }
                        } else {
                            /* case 2, create a new SL entry */
                            ret = rrip_SL_append_comp(&n_comp, &comps, prev,
                                                      clen - 2, cflag);
                            if (ret < 0) {
                                goto add_susp_cleanup;
                            }
                            ce_len += sl_len;
                            sl_len = 5 + clen;
                        }
                    } else {
                        /* the component fit in the SL entry */
                        ret = rrip_SL_append_comp(&n_comp, &comps, prev, 
                                                  clen - 2, cflag);
                        if (ret < 0) {
                            goto add_susp_cleanup;
                        }
                        sl_len += clen;
                    }
                }

                if (!cur || cur[1] == '\0') {
                    /* cur[1] can be \0 if dest ends with '/' */
                    break;
                }
                prev = cur + 1;
                cur = strchr(prev, '/');
            }

            if (cew) {
                ce_len += sl_len;
            }
        }

        /*
         * We we reach here:
         * - We know if NM fill in the SUA (nm_type == 0)
         * - If SL needs an to be written in CE (ce_len > 0)
         * - The components for SL entry (or entries)
         */

        if (nm_type == 0) {
            /* the full NM fills in SUA */
            ret = rrip_add_NM(t, info, name, strlen(name), 0, 0);
            if (ret < 0) {
                goto add_susp_cleanup;
            }
        } else {
            /* 
             * Write the NM part that fits in SUA...  Note that CE
             * entry and NM in the continuation area is added below 
             */
            namelen = space - info->suf_len - 28 - 5;
            ret = rrip_add_NM(t, info, name, namelen, 1, 0);
            if (ret < 0) {
                goto add_susp_cleanup;
            }
        }

        if (ce_len > 0) {
            /* Add the CE entry */
            ret = susp_add_CE(t, ce_len, info);
            if (ret < 0) {
                goto add_susp_cleanup;
            }
        }

        if (nm_type == 1) {
            /* 
             * ..and the part that goes to continuation area.
             */
            ret = rrip_add_NM(t, info, name + namelen, strlen(name + namelen),
                              0, 1);
            if (ret < 0) {
                goto add_susp_cleanup;
            }
        }

        if (n->type == ECMA119_SYMLINK) {

            /* add the SL entry (or entries) */
            ret = rrip_add_SL(t, info, comps, n_comp, (ce_len > 0));

            /* free the components */
            for (i = 0; i < n_comp; i++) {
                free(comps[i]);
            }
            free(comps);

            if (ret < 0) {
                goto add_susp_cleanup;
            }
        }

    } else {

        /* "." or ".." entry */

        /* write the NM entry */
        ret = rrip_add_NM(t, info, NULL, 0, 1 << type, 0);
        if (ret < 0) {
            goto add_susp_cleanup;
        }
        if (type == 1 && n->parent == NULL) {
            /* 
             * "." for root directory 
             * we need to write SP and ER entries. The first fits in SUA,
             * ER needs a Continuation Area, thus we also need a CE entry.
             * Note that SP entry was already added above
             */
            ret = susp_add_CE(t, 182, info); /* 182 is ER length */
            if (ret < 0) {
                goto add_susp_cleanup;
            }
            ret = rrip_add_ER(t, info);
            if (ret < 0) {
                goto add_susp_cleanup;
            }
        }
    }

    /*
     * The System Use field inside the directory record must be padded if
     * it is an odd number (ECMA-119, 9.1.13)
     */
    info->suf_len += (info->suf_len % 2);

    free(name);
    free(dest);
    return ISO_SUCCESS;

    add_susp_cleanup: ;
    free(name);
    free(dest);
    susp_info_free(info);
    return ret;
}

/**
 * Write the given SUSP fields into buf. Note that Continuation Area
 * fields are not written.
 * If info does not contain any SUSP entry this function just return. 
 * After written, the info susp_fields array will be freed, and the counters
 * updated propertly.
 */
void rrip_write_susp_fields(Ecma119Image *t, struct susp_info *info,
                            uint8_t *buf)
{
    size_t i;
    size_t pos = 0;

    if (info->n_susp_fields == 0) {
        return;
    }

    for (i = 0; i < info->n_susp_fields; i++) {
        memcpy(buf + pos, info->susp_fields[i], info->susp_fields[i][2]);
        pos += info->susp_fields[i][2];
    }

    /* free susp_fields */
    for (i = 0; i < info->n_susp_fields; ++i) {
        free(info->susp_fields[i]);
    }
    free(info->susp_fields);
    info->susp_fields = NULL;
    info->n_susp_fields = 0;
    info->suf_len = 0;
}

/**
 * Write the Continuation Area entries for the given struct susp_info, using
 * the iso_write() function.
 * After written, the ce_susp_fields array will be freed.
 */
int rrip_write_ce_fields(Ecma119Image *t, struct susp_info *info)
{
    size_t i;
    uint8_t padding[BLOCK_SIZE];
    int ret= ISO_SUCCESS;

    if (info->n_ce_susp_fields == 0) {
        return ret;
    }

    for (i = 0; i < info->n_ce_susp_fields; i++) {
        ret = iso_write(t, info->ce_susp_fields[i], 
                        info->ce_susp_fields[i][2]);
        if (ret < 0) {
            goto write_ce_field_cleanup;
        }
    }

    /* pad continuation area until block size */
    i = BLOCK_SIZE - (info->ce_len % BLOCK_SIZE);
    if (i > 0 && i < BLOCK_SIZE) {
        memset(padding, 0, i);
        ret = iso_write(t, padding, i);
    }

    write_ce_field_cleanup: ;
    /* free ce_susp_fields */
    for (i = 0; i < info->n_ce_susp_fields; ++i) {
        free(info->ce_susp_fields[i]);
    }
    free(info->ce_susp_fields);
    info->ce_susp_fields = NULL;
    info->n_ce_susp_fields = 0;
    info->ce_len = 0;
    return ret;
}