/*
 * Copyright (c) 2001-2002  Ben Fennema
 * Copyright (c) 2016-2019  Pali Rohár <pali.rohar@gmail.com>
 * All rights reserved.
 * Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co., Ltd.
 *
 * Author:     zhangsheng<zhangsheng@uniontech.com>
 *
 * Maintainer: max-lv<lvwujun@uniontech.com>
 *             xushitong<xushitong@uniontech.com>
 *             dengkeyun<dengkeyun@uniontech.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#include <unistd.h>
#include <inttypes.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
#include <fts.h>


#include "udffs.h"
#include "defaults.h"
#include "file.h"
#include "readdisc.h"
#include "udferr.h"

char *udf_space_type_str[UDF_SPACE_TYPE_SIZE] = { "RESERVED", "VRS", "ANCHOR", "MVDS", "RVDS", "LVID", "STABLE", "SSPACE", "PSPACE", "USPACE", "BAD", "MBR" };

/**
 * @brief Returns size of all given files/dirs (including local) by ftd, so fast in local
 * @param img
 * @param count all file/dir count
 * @return Size of all given files/dirs, return 0 if invalid file exsits.
 */
static uint64_t total_size(const udf_image *img, uint64_t *count)
{
    uint64_t total = 0;
    uint32_t i = 0;

    for (; i < img->cur_file_count; i++) {
        char *file_name = img->inner_files[i].file_name;
        char *paths[2] = {0};
        if (file_name == NULL)
            return 0;

        paths[0] = strdup(file_name);
        FTS *fts = fts_open(paths, 0, NULL);
        if (paths[0])
            free(paths[0]);
        if (fts == NULL) {
            perror("Error: File name '%s' cannot open: ");
            return 0;
        }

        while (1) {
            FTSENT *ent = fts_read(fts);
            if (ent == NULL)
                break;
            unsigned short flag = ent->fts_info;
            if (flag != FTS_DP)
                total += (uint64_t)ent->fts_statp->st_size;
            if (flag == FTS_F || flag == FTS_D)
                (*count)++;
        }
        fts_close(fts);
    }

    return total;
}

static int writable()
{
    int ret = 0;
    char os_name[10] = {'\0'};
    int fd = open("/etc/product-info", O_RDONLY);
    if (fd < 0)
        return UDFFS_WRITE_ERROR;
    read(fd, os_name, 9);
    ret = strcmp(os_name, "UnionTech");
    if (ret != 0)
        return UDFFS_WRITE_ERROR;
    close(fd);
    return UDFFS_OK;
}

static int rand_success(int prob)
{
    if (prob < 0 || prob > 100)
        return UDFFS_WRITE_ERROR;

    int temp;

    srand((unsigned)time(NULL));
    temp = rand() % 100 + 1;
    if (temp <= prob)
        return UDFFS_OK;

    return UDFFS_WRITE_ERROR;
}

static int valid_offset(int fd, off_t offset)
{
    char ch;

    if (lseek(fd, offset, SEEK_SET) < 0)
        return UDFFS_OK;
    if (read_nointr(fd, &ch, 1) < 1)
        return UDFFS_OK;
    return 1;
}

static udf_file *find_file_by_fe(const udf_extent *ext, const struct fileEntry *fe)
{
    udf_file *file = NULL;
    udf_desc *desc = ext->head;

    while (desc != NULL) {
        file = desc->file;
        if (file && file->fe == fe)
            break;
        else
            file = NULL;
        desc = desc->next;
    }

    return file;
}

static void import_session(udf_image *img, udf_extent *pspace, udf_desc *parent_desc, udf_image *disc, Directory *dir)
{
    uint32_t i, j, c;
    uint32_t location;
    struct fileEntry *sess_fe, *parent_fe, *old_fe;
    struct fileIdentDesc *sess_fid;
    udf_desc *desc;
    udf_file *files;
    Directory *child_dir;
    uint64_t fid_len;
    struct stat fst;

    c = 0;
    files = (udf_file*)calloc(dir->numfiles, sizeof (udf_file));
    sess_fe = (struct fileEntry *)dir->fe; // dir fe
    old_fe = (struct fileEntry*)calloc(1, img->blocksize);

    // hold fid space
    for( i = 0; i < sess_fe->informationLength;
         i += ((sizeof(struct fileIdentDesc) + sess_fid->lengthOfImpUse + sess_fid->lengthFileIdent + 3) & ~3) ) {
        sess_fid = (struct fileIdentDesc*)(dir->fid_data + i);
        if (sess_fid->fileCharacteristics & FID_FILE_CHAR_DELETED )
            continue;
        if (sess_fid->fileCharacteristics & FID_FILE_CHAR_PARENT)
            continue;
        if (sess_fid->descTag.tagLocation == 0) {
            udf_fprintf(stdout, "Warning: Disc data abnormal!!!\n");
            continue;
        }

        files[c].ulen = sess_fid->lengthFileIdent;
        memcpy(files[c].uname, sess_fid->impUseAndFileIdent + sess_fid->lengthOfImpUse, files[c].ulen);
        if (sess_fid->fileCharacteristics & FID_FILE_CHAR_DIRECTORY)
            pre_insert_fid(img, pspace, parent_desc, &files[c], FID_FILE_CHAR_DIRECTORY);
        else
            pre_insert_fid(img, pspace, parent_desc, &files[c], 0);

        c++;
    }

    // TODO : current just support append files to root dir
    if (strcmp(dir->path, "/") == 0) {
        for (i = 0; i < img->cur_file_count; i++) {
            char *name = img->inner_files[i].file_name;
            parent_desc->file[i].file_name = (char *)malloc(strlen(name) + 1);
            memset(parent_desc->file[i].file_name, 0, strlen(name) + 1);
            strcpy(parent_desc->file[i].file_name, name);
            path_to_uname(parent_desc->file[i].file_name, parent_desc->file[i].uname, &parent_desc->file[i].ulen);

            if (parent_desc->file[i].ulen == 0) {
                udf_fprintf(stdout, "Warning: file name [%s] too long!\n", parent_desc->file[i].file_name);
                continue;
            }

            if (lstat(parent_desc->file[i].file_name, &fst) < 0) {
                udf_fprintf(stdout, "Warning: Run stat() failed on '%s'\n", parent_desc->file[i].file_name);
                continue;
            }

            if (S_ISREG(fst.st_mode)) {
                pre_insert_fid(img, pspace, parent_desc, &parent_desc->file[i], 0);
            } else if (S_ISDIR(fst.st_mode)) {
                pre_insert_fid(img, pspace, parent_desc, &parent_desc->file[i], FID_FILE_CHAR_DIRECTORY);
            } else {
                udf_fprintf(stdout, "Warning: Can only copy regular files or directories '%s'\n", parent_desc->file[i].file_name);
                continue;
            }
        }
    }

    // extent a new block if fids space > blocksize
    parent_fe = (struct fileEntry *)parent_desc->data->buffer;
    fid_len = parent_fe->informationLength / img->blocksize;
    if (fid_len > 0)
        udf_alloc_blocks(img, pspace, parent_desc->offset, (uint32_t)fid_len);

    // write fe and alloc data extent space
    c = 0;
    for( i = 0; i < sess_fe->informationLength;
         i += ((sizeof(struct fileIdentDesc) + sess_fid->lengthOfImpUse + sess_fid->lengthFileIdent + 3) & ~3) ) {
        sess_fid = (struct fileIdentDesc*)(dir->fid_data + i);
        if (sess_fid->fileCharacteristics & FID_FILE_CHAR_DELETED )
            continue;
        if (sess_fid->fileCharacteristics & FID_FILE_CHAR_PARENT)
            continue;
        if (sess_fid->descTag.tagLocation == 0) {
            udf_fprintf(stdout, "Warning: Disc data abnormal!!!\n");
            continue;
        }

        location = le32_to_cpu(disc->udf_pd[0]->partitionStartingLocation) + sess_fid->icb.extLocation.logicalBlockNum;
        if (read_offset(disc->fd, disc, old_fe, (loff_t)location * disc->blocksize, img->blocksize, 1) < 0) {
            udf_fprintf(stdout, "Error: Read FE offset failed\n");
            abort();
        }

        if (sess_fid->fileCharacteristics & FID_FILE_CHAR_DIRECTORY) {
            desc = linkDirectory(img, pspace, &files[c], parent_desc);
            for (j = 0; j < dir->c_len && desc != NULL; j++) {
                child_dir = dir->children[j];
                if (((struct fileEntry*)child_dir->fe)->descTag.tagLocation == old_fe->descTag.tagLocation)
                    import_session(img, pspace, desc, disc, child_dir);
            }
        } else {
            linkFile(img, pspace, &files[c], old_fe, parent_desc);
        }
        c++;
    }

    // TODO : repeat name
    // TODO : current just support append files to root dir
    if (strcmp(dir->path, "/") == 0) {
        for (i = 0; i < img->cur_file_count; i++) {
            if (lstat(parent_desc->file[i].file_name, &fst) < 0) {
                udf_fprintf(stdout, "Warning: Run stat() failed on '%s'\n", parent_desc->file[i].file_name);
                continue;
            }

            if (S_ISDIR(fst.st_mode)) {
                copyDirectory(img, pspace, &parent_desc->file[i], parent_desc);
            } else {
                if (S_ISREG(fst.st_mode)) {
                    copyFile(img, pspace, &parent_desc->file[i], &fst, parent_desc);
                } else {
                    udf_fprintf(stdout, "Warning: Can only copy regular files or directories '%s'\n", parent_desc->file[i].file_name);
                    continue;
                }
            }
        }
    }

    free(old_fe);
    free(files);
}

static void shuffle_nointr(uint8_t *buf, size_t count)
{
    int index;
    uint8_t c;
    srand((unsigned)time(NULL));
    index = rand() % (int)count;
    c = (uint8_t)(rand() % 128);
    buf[index] = c;
}

static int fake_write_func(udf_image *img, udf_extent *ext)
{
    assert(img);
    assert(ext);

    static char *buffer = NULL; // TODO: free
    static size_t bufferlen = 0;
    int fd = img->fd;
    ssize_t length, offset;
    uint32_t blocks;
    udf_desc *desc;
    udf_data *data;

    if (buffer == NULL) {
        bufferlen = img->blocksize;
        buffer = calloc(1, bufferlen);
        if (buffer == NULL)
            return -1;
    }

    if (!(ext->space_type & (USPACE|RESERVED))) {
        desc = ext->head;
        while (desc != NULL) {
            if (fd >= 0) {
                if (lseek(fd, (off_t)(ext->start + desc->offset) * img->blocksize, SEEK_SET) < 0)
                    return -1;
            }
            data = desc->data;
            offset = 0;
            while (data != NULL) {
                if (data->length + offset > bufferlen) {
                    bufferlen = (data->length + offset + img->blocksize - 1) & ~(img->blocksize - 1);
                    buffer = realloc(buffer, bufferlen);
                    if (buffer == NULL)
                        return -1;
                }
                memcpy(buffer + offset, data->buffer, data->length);
                offset += data->length;
                data = data->next;
            }
            length = (offset + img->blocksize - 1) & ~(img->blocksize - 1);
            if (offset != length)
                memset(buffer + offset, 0x00, length - offset);

            if (write_nointr(fd, buffer, length) != length)
                return -1;

            /* write file data */
            if (desc->ident == TAG_IDENT_FE) {
                struct fileEntry *fe = (struct fileEntry *)desc->data->buffer;
                if (fe && fe->icbTag.fileType == ICBTAG_FILE_TYPE_REGULAR && (fe->icbTag.flags & ICBTAG_FLAG_AD_SHORT) == ICBTAG_FLAG_AD_SHORT) {
                    udf_file *file = find_file_by_fe(ext, fe);
                    if (file) {
                        uint32_t i, j;
                        uint8_t *p = malloc(img->blocksize);
                        if (!p)
                            return -1;
                        int file_fd = open(file->file_name, O_RDONLY);
                        short_ad *ext = (short_ad*)(fe->extendedAttrAndAllocDescs + fe->lengthExtendedAttr); // sometimes, it's array
                        uint32_t count = fe->lengthAllocDescs / sizeof (short_ad);
                        for (i = 0; i < count; i++) {
                            //fprintf(stdout, "Info: Write pos: %d, Write length: %d\n", ext->extPosition, ext->extLength);
                            for (j = 0; (j < ext->extLength) && (file_fd > 0); j += img->blocksize) {
                                memset(p, 0x00, img->blocksize);
                                length = read_nointr(file_fd, p, img->blocksize);
                                shuffle_nointr(p, length);
                                if (write_nointr(fd, p, length) != length) {
                                    free(p);
                                    close(file_fd);
                                    return -1;
                                }
                            }
                            ext++;
                        }
                        free(p);
                        if (file_fd > 0)
                            close(file_fd);
                    }
                }
            }

            desc = desc->next;
        }
    } else {
        length = img->blocksize;
        blocks = ext->blocks;
        memset(buffer, 0, length);
        if (fd >= 0) {
            if (lseek(fd, (off_t)(ext->start) * img->blocksize, SEEK_SET) < 0)
                return -1;
        }
        while (blocks-- > 0) {
            if (write_nointr(fd, buffer, length) != length)
                return -1;
        }
    }

    return 0;
}

static int write_func(udf_image *img, udf_extent *ext)
{
    assert(img);
    assert(ext);

    static char *buffer = NULL; // TODO: free
    static size_t bufferlen = 0;
    int fd = img->fd;
    ssize_t length, offset;
    uint32_t blocks;
    udf_desc *desc;
    udf_data *data;

    if (buffer == NULL) {
        bufferlen = img->blocksize;
        buffer = calloc(1, bufferlen);
        if (buffer == NULL)
            return -1;
    }

    if (!(ext->space_type & (USPACE|RESERVED))) {
        desc = ext->head;
        while (desc != NULL) {
            if (fd >= 0) {
                if (lseek(fd, (off_t)(ext->start + desc->offset) * img->blocksize, SEEK_SET) < 0)
                    return -1;
            }
            data = desc->data;
            offset = 0;
            while (data != NULL) {
                if (data->length + offset > bufferlen) {
                    bufferlen = (data->length + offset + img->blocksize - 1) & ~(img->blocksize - 1);
                    buffer = realloc(buffer, bufferlen);
                    if (buffer == NULL)
                        return -1;
                }
                memcpy(buffer + offset, data->buffer, data->length);
                offset += data->length;
                data = data->next;
            }
            length = (offset + img->blocksize - 1) & ~(img->blocksize - 1);
            if (offset != length)
                memset(buffer + offset, 0x00, length - offset);

            if (write_nointr(fd, buffer, length) != length)
                return -1;

            /* write file data */
            if (desc->ident == TAG_IDENT_FE) {
                struct fileEntry *fe = (struct fileEntry *)desc->data->buffer;
                if (fe && fe->icbTag.fileType == ICBTAG_FILE_TYPE_REGULAR && (fe->icbTag.flags & ICBTAG_FLAG_AD_SHORT) == ICBTAG_FLAG_AD_SHORT) {
                    udf_file *file = find_file_by_fe(ext, fe);
                    if (file) {
                        uint32_t i, j;
                        uint8_t *p = malloc(img->blocksize);
                        if (!p)
                            return -1;
                        int file_fd = open(file->file_name, O_RDONLY);
                        short_ad *ext = (short_ad*)(fe->extendedAttrAndAllocDescs + fe->lengthExtendedAttr); // sometimes, it's array
                        uint32_t count = fe->lengthAllocDescs / sizeof (short_ad);
                        for (i = 0; i < count; i++) {
                            //fprintf(stdout, "Info: Write pos: %d, Write length: %d\n", ext->extPosition, ext->extLength);
                            for (j = 0; (j < ext->extLength) && (file_fd > 0); j += img->blocksize) {
                                memset(p, 0x00, img->blocksize);
                                length = read_nointr(file_fd, p, img->blocksize);
                                if (write_nointr(fd, p, length) != length) {
                                    free(p);
                                    close(file_fd);
                                    return -1;
                                }
                            }
                            ext++;
                        }
                        free(p);
                        if (file_fd > 0)
                            close(file_fd);
                    }
                }
            }

            desc = desc->next;
        }
    } else {
        length = img->blocksize;
        blocks = ext->blocks;
        memset(buffer, 0, length);
        if (fd >= 0) {
            if (lseek(fd, (off_t)(ext->start) * img->blocksize, SEEK_SET) < 0)
                return -1;
        }
        while (blocks-- > 0) {
            if (write_nointr(fd, buffer, length) != length)
                return -1;
        }
    }

    return 0;
}

int udf_image_init(udf_image *img)
{
    assert(img);

    timestamp	ts;
    struct timeval	tv;
    struct tm 	*tm;
    int		altzone;
    uint32_t	uuid_time;
    char		uuid[17];

    memset(img, 0, sizeof (*img));

    img->max_file_count = INCREASE_FILE_COUNT;
    img->blocksize = 2048;
    img->flags = FLAG_LOCALE;
    img->mode = 0755;
    img->inner_files = (udf_file *)calloc(img->max_file_count, sizeof (udf_file));

    img->total_size = total_size;
    img->write = write_func;
    img->fake_write = fake_write_func;

    if (gettimeofday(&tv, NULL) != 0 || tv.tv_sec == (time_t)-1 || (tm = localtime(&tv.tv_sec)) == NULL || tm->tm_year < 1-1900 || tm->tm_year > 9999-1900) {
        /* fallback to 1970-01-01 00:00:00 */
        ts.typeAndTimezone = cpu_to_le16(0x1000);
        ts.year = cpu_to_le16(1970);
        ts.month = 1;
        ts.day = 1;
        ts.hour = 0;
        ts.minute = 0;
        ts.second = 0;
        ts.centiseconds = 0;
        ts.hundredsOfMicroseconds = 0;
        ts.microseconds = 0;
        /* and for uuid use random bytes */
        uuid_time = randu32();
    } else {
        altzone = timezone - 3600;
        if (daylight)
            ts.typeAndTimezone = cpu_to_le16(((-altzone/60) & 0x0FFF) | 0x1000);
        else
            ts.typeAndTimezone = cpu_to_le16(((-timezone/60) & 0x0FFF) | 0x1000);
        ts.year = cpu_to_le16(1900 + tm->tm_year);
        ts.month = 1 + (uint8_t)tm->tm_mon;
        ts.day = (uint8_t)tm->tm_mday;
        ts.hour = (uint8_t)tm->tm_hour;
        ts.minute = (uint8_t)tm->tm_min;
        ts.second = (uint8_t)tm->tm_sec;
        ts.centiseconds = (uint8_t)tv.tv_usec / 10000;
        ts.hundredsOfMicroseconds = ((uint8_t)tv.tv_usec - ts.centiseconds * 10000) / 100;
        ts.microseconds = tv.tv_usec - ts.centiseconds * 10000 - ts.hundredsOfMicroseconds * 100;
        if (tv.tv_sec < 0)
            uuid_time = randu32();
        else
            uuid_time = tv.tv_sec & 0xFFFFFFFF;
    }

    /* Allocate/Initialize Descriptors */
    // PVD
    img->udf_pvd[0] = malloc(sizeof(struct primaryVolDesc));
    if (!img->udf_pvd[0]) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_pvd[0], &default_pvd, sizeof(struct primaryVolDesc));
    memcpy(&img->udf_pvd[0]->recordingDateAndTime, &ts, sizeof(timestamp));
    snprintf(uuid, sizeof(uuid), "%08" PRIx32 "%08" PRIx32, uuid_time, randu32());
    memcpy(&img->udf_pvd[0]->volSetIdent[1], uuid, 16);
    img->udf_pvd[0]->volIdent[31] = strlen((char *)img->udf_pvd[0]->volIdent);
    img->udf_pvd[0]->volSetIdent[127] = strlen((char *)img->udf_pvd[0]->volSetIdent);

    // LVD
    img->udf_lvd[0] = malloc(sizeof(struct logicalVolDesc));
    if (!img->udf_lvd[0]) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_lvd[0], &default_lvd, sizeof(struct logicalVolDesc));
    img->udf_lvd[0]->logicalVolIdent[127] = strlen((char *)img->udf_lvd[0]->logicalVolIdent);

    // PD
    img->udf_pd[0] = malloc(sizeof(struct partitionDesc));
    if (!img->udf_pd[0]) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_pd[0], &default_pd, sizeof(struct partitionDesc));

    // USD
    img->udf_usd[0] = malloc(sizeof(struct unallocSpaceDesc));
    if (!img->udf_usd[0]) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_usd[0], &default_usd, sizeof(struct unallocSpaceDesc));

    // IUVD
    img->udf_iuvd[0] = malloc(sizeof(struct impUseVolDesc) + sizeof(struct impUseVolDescImpUse));
    if (!img->udf_iuvd[0])
    {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_iuvd[0], &default_iuvd, sizeof(struct impUseVolDesc));

    // TD
    img->udf_td[0] = malloc(sizeof(struct terminatingDesc));
    if (!img->udf_td[0]) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_td[0], &default_td, sizeof(struct terminatingDesc));

    // LVID
    img->udf_lvid = malloc(sizeof(struct logicalVolIntegrityDesc) + sizeof(struct logicalVolIntegrityDescImpUse));
    if (!img->udf_lvid) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_lvid, &default_lvid, sizeof(struct logicalVolIntegrityDesc));
    memcpy(&img->udf_lvid->recordingDateAndTime, &ts, sizeof(timestamp));
    memcpy(query_lvidiu(img), &default_lvidiu, sizeof(struct logicalVolIntegrityDescImpUse));

    // FSD
    img->udf_fsd = malloc(sizeof(struct fileSetDesc));
    if (!img->udf_fsd) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    memcpy(img->udf_fsd, &default_fsd, sizeof(struct fileSetDesc));
    memcpy(&img->udf_fsd->recordingDateAndTime, &ts, sizeof(timestamp));
    img->udf_fsd->logicalVolIdent[127] = strlen((char *)img->udf_fsd->logicalVolIdent);
    img->udf_fsd->fileSetIdent[31] = strlen((char *)img->udf_fsd->fileSetIdent);
    img->udf_fsd->copyrightFileIdent[31] = strlen((char *)img->udf_fsd->copyrightFileIdent);
    img->udf_fsd->abstractFileIdent[31] = strlen((char *)img->udf_fsd->abstractFileIdent);

    // extent
    img->head = malloc(sizeof(udf_extent));
    if (!img->head) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return UDFFS_MEMORY_ALLOC_FAILED;
    }
    img->tail = img->head;

    img->head->space_type = USPACE;
    img->head->start = 0;
    img->head->blocks = 0;
    img->head->next = NULL;
    img->head->prev = NULL;
    img->head->head = NULL;
    img->head->tail = NULL;

    // version
    udf_set_version(img, 0x0102);
    return UDFFS_OK;
}

int udf_set_version(udf_image *img, uint16_t udf_rev)
{
    assert(img);

    struct logicalVolIntegrityDescImpUse *lvidiu;
    struct domainIdentSuffix *dis;
    struct UDFIdentSuffix *uis;

    if (img->udf_rev == udf_rev)
        return UDFFS_OK;
    else if (
             udf_rev != 0x0101 &&
             udf_rev != 0x0102 &&
             udf_rev != 0x0150 &&
             udf_rev != 0x0200 &&
             udf_rev != 0x0201 &&
             udf_rev != 0x0250 &&
             udf_rev != 0x0260) {
        return UDFFS_INVALID_VERSION;
    } else
        img->udf_rev = udf_rev;

    if (udf_rev >= 0x0200) {
        img->flags |= FLAG_EFE;
        strcpy((char *)img->udf_pd[0]->partitionContents.ident, PD_PARTITION_CONTENTS_NSR03);
    } else {
        img->flags &= ~FLAG_EFE;
        strcpy((char *)img->udf_pd[0]->partitionContents.ident, PD_PARTITION_CONTENTS_NSR02);
    }

    dis = (struct domainIdentSuffix *)img->udf_fsd->domainIdent.identSuffix;
    dis->UDFRevision = cpu_to_le16(udf_rev);
    dis = (struct domainIdentSuffix *)img->udf_lvd[0]->domainIdent.identSuffix;
    dis->UDFRevision = cpu_to_le16(udf_rev);
    uis = (struct UDFIdentSuffix *)img->udf_iuvd[0]->impIdent.identSuffix;
    uis->UDFRevision = cpu_to_le16(udf_rev);

    dis = (struct domainIdentSuffix *)default_lvd.domainIdent.identSuffix;
    dis->UDFRevision = cpu_to_le16(udf_rev);
    dis = (struct domainIdentSuffix *)default_fsd.domainIdent.identSuffix;
    dis->UDFRevision = cpu_to_le16(udf_rev);
    uis = (struct UDFIdentSuffix *)default_iuvd.impIdent.identSuffix;
    uis->UDFRevision = cpu_to_le16(udf_rev);

    lvidiu = query_lvidiu(img);
    if (udf_rev < 0x0102) {
        /* The ImplementationUse area for the Logical Volume Integrity Descriptor
         * prior to UDF 1.02 does not define UDF revisions, so clear these fields */
        lvidiu->minUDFReadRev = cpu_to_le16(0);
        lvidiu->minUDFWriteRev = cpu_to_le16(0);
        lvidiu->maxUDFWriteRev = cpu_to_le16(0);

        default_lvidiu.minUDFReadRev = cpu_to_le16(0);
        default_lvidiu.minUDFWriteRev = cpu_to_le16(0);
        default_lvidiu.maxUDFWriteRev = cpu_to_le16(0);
    } else {
        if (udf_rev == 0x0260)
            lvidiu->minUDFReadRev = cpu_to_le16(0x0250);
        else
            lvidiu->minUDFReadRev = cpu_to_le16(udf_rev);
        lvidiu->minUDFWriteRev = cpu_to_le16(udf_rev);
        lvidiu->maxUDFWriteRev = cpu_to_le16(udf_rev);

        if (udf_rev == 0x0260)
            default_lvidiu.minUDFReadRev = cpu_to_le16(0x0250);
        else
            default_lvidiu.minUDFReadRev = cpu_to_le16(udf_rev);
        default_lvidiu.minUDFWriteRev = cpu_to_le16(udf_rev);
        default_lvidiu.maxUDFWriteRev = cpu_to_le16(udf_rev);
    }

    return UDFFS_OK;
}

void udf_set_blocks(udf_image *img)
{
    assert(img);

    struct stat buf;

    if (fstat(img->fd, &buf) == 0 && S_ISREG(buf.st_mode))
        img->blocks = buf.st_size / img->blocksize;
    else {
        off_t high, low;

        for (low=0, high = 1024; valid_offset(img->fd, high); high *= 2)
            low = high;
        while (low < high - 1) {
            const off_t mid = (low + high) / 2;

            if (valid_offset(img->fd, mid))
                low = mid;
            else
                high = mid;
        }

        valid_offset(img->fd, 0);
        img->blocks = (low + 1) / img->blocks;
    }
}

void udf_image_dump(udf_image *img)
{
    assert(img);

    char buf[128*3];
    size_t len;

    printf("[UDF]filename=%s\n", img->pathname);
    memset(buf, 0, sizeof(buf));
    len = decode_string(img, img->udf_lvd[0]->logicalVolIdent, buf, 128, sizeof(buf));
    printf("[UDF]label=%s\n", buf);

    memset(buf, 0, sizeof(buf));
    len = gen_uuid_from_vol_set_ident(buf, img->udf_pvd[0]->volSetIdent, 128);
    printf("[UDF]uuid=%s\n", buf);

    printf("[UDF]blocksize=%"PRIu32"\n", img->blocksize);
    printf("[UDF]blocks=%"PRIu32"\n", img->blocks);
    printf("[UDF]udfrev=%"PRIx16".%02"PRIx16"\n", img->udf_rev >> 8, img->udf_rev & 0xFF);

    // dump space
    udf_extent *start_ext = img->head;
    int i;

    start_ext = img->head;

    while (start_ext != NULL) {
        printf("[UDF]start=%"PRIu32", blocks=%"PRIu32", type=", start_ext->start, start_ext->blocks);
        for (i = 0; i < UDF_SPACE_TYPE_SIZE; i++)
        {
            if (!(start_ext->space_type & (1<<i)))
                continue;
            else
                printf("%s ", udf_space_type_str[i]);
        }
        printf("\n");
        start_ext = start_ext->next;
    }

    // TODO: dump desc

    if (img->blocks <= 257)
        udf_fprintf(stdout, "Warning: UDF filesystem has less than 258 blocks, it can cause problems\n");

    if (len == (size_t)-1)
        udf_fprintf(stdout, "Warning: Volume Set Identifier must be at least 8 characters long\n");
    else if (len < 16)
        udf_fprintf(stdout, "Warning: First 16 characters of Volume Set Identifier are not hexadecimal lowercase digits\nWarning: This would cause problems for UDF uuid\n");
}

int udf_split_space(udf_image *img)
{
    assert(img);
    assert(img->blocksize);

    uint32_t sizes[UDF_ALLOC_TYPE_SIZE] = {0};
    uint32_t offsets[UDF_ALLOC_TYPE_SIZE] = {0};
    uint32_t start_block = 0; // WARNING
    uint32_t blocks = img->blocks;
    uint32_t start = 0, size = 0, start2 = 0, size2 = 0;
    udf_extent *ext;
    uint32_t accessType;
    uint32_t i, value;


    // ECMA-167 specify first 16 blocks must RESERVED
    set_extent(img, RESERVED, start_block, 32768 / img->blocksize);

    // Volume Recognition Sequence
    if (img->blocksize >= 2048)
        set_extent(img, VRS, start_block + (2048 * 16) / img->blocksize, 4); // 3 sectors for VSD + one empty sector
    else
        set_extent(img, VRS, start_block + (2048 * 16) / img->blocksize, ((2048 * 3) + img->blocksize) / img->blocksize); // 3 VSD in more sectors + one empty sector

    // First Anchor Point at sector 256
    if (blocks > 257)
        set_extent(img, ANCHOR, start_block + 256, 1);
    // Unclosed VAT media must not contain second anchor point and for space effectivity it is not written also on small disks
    if (blocks >= 3072)
        set_extent(img, ANCHOR, start_block + blocks-257, 1);
    // Final anchor point at sector End-Of-Volume/Session for other then sequentially writable media (non-VAT)
    set_extent(img, ANCHOR, start_block + blocks-1, 1);

    for (i=0; i<UDF_ALLOC_TYPE_SIZE; i++) {
        sizes[i] = img->sizing[i].numSize * blocks / img->sizing[i].denomSize;
        if (img->sizing[i].minSize > sizes[i])
            sizes[i] = img->sizing[i].minSize;
        offsets[i] = img->sizing[i].align;
    }

    // MVDS
    accessType = le32_to_cpu(img->udf_pd[0]->accessType);
    if ((accessType == PD_ACCESS_TYPE_OVERWRITABLE || accessType == PD_ACCESS_TYPE_REWRITABLE) && (uint64_t)sizes[LVID_SIZE] * img->blocksize < 8192 && blocks > 257)
        sizes[LVID_SIZE] = (8192 + img->blocksize-1) / img->blocksize;

    if (sizes[VDS_SIZE] > 6 && blocks <= 257)
        sizes[VDS_SIZE] = 6;

    if (blocks < 770)
        start = (start_block + offsets[VDS_SIZE]-1) / offsets[VDS_SIZE] * offsets[VDS_SIZE];
    else
        start = (start_block + 96) / offsets[VDS_SIZE] * offsets[VDS_SIZE];
    start = find_next_extent_size(img, start, USPACE, sizes[VDS_SIZE], offsets[VDS_SIZE]);
    if (!start) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SPACE_ERROR;
    }
    set_extent(img, MVDS, start, sizes[VDS_SIZE]);

    // RVDS
    if (blocks > 257) {
        if (blocks >= 3072)
            start = find_next_extent_size(img, (start_block+blocks-257+97)/32*32, USPACE, sizes[VDS_SIZE], offsets[VDS_SIZE]);
        else
            start = prev_extent_size(img->tail, USPACE, sizes[VDS_SIZE], offsets[VDS_SIZE]);
        if (!start) {
            udf_fprintf(stdout, "Error: Not enough blocks on device\n");
            return UDFFS_SPACE_ERROR;
        }
        set_extent(img, RVDS, start, sizes[VDS_SIZE]);
    }

    // LVID
    start = prev_extent_size(img->tail, USPACE, sizes[LVID_SIZE], offsets[LVID_SIZE]);
    if (start < 256 || blocks >= 770) {
        if (blocks < 770)
            start = (start_block + offsets[LVID_SIZE]-1) / offsets[LVID_SIZE] * offsets[LVID_SIZE];
        else
            start = (start_block + 128) / offsets[LVID_SIZE] * offsets[LVID_SIZE];
        start = find_next_extent_size(img, start, USPACE, sizes[LVID_SIZE], offsets[LVID_SIZE]);
        if (!start) {
            udf_fprintf(stdout, "Error: Not enough blocks on device\n");
            return UDFFS_SPACE_ERROR;
        }
        set_extent(img, LVID, start, sizes[LVID_SIZE]);
    }

    // PSACE
    for (i = 0; i < 3; ++i) {
        if (i != 0 && blocks >= 770)
            break;
        if (i == 0)
            ext = next_extent(find_extent(img, start_block + 256), USPACE);
        else if (i == 1)
            ext = prev_extent(img->tail, USPACE);
        else
            ext = next_extent(img->head, USPACE);
        for (; ext; ext = next_extent(ext->next, USPACE)) {
            // round start up to a multiple of alignment/packet_size
            if (ext->start % offsets[PSPACE_SIZE]) {
                if (offsets[PSPACE_SIZE] >= ext->blocks + (ext->start % offsets[PSPACE_SIZE]))
                    continue;
                start = ext->start + offsets[PSPACE_SIZE] - (ext->start % offsets[PSPACE_SIZE]);
                size = ext->blocks - offsets[PSPACE_SIZE] + (ext->start % offsets[PSPACE_SIZE]);
            } else {
                start = ext->start;
                size = ext->blocks;
            }

            // Make sure partition doesn't have a space bitmap with a partial last byte.
            // It's a corner case that some systems (Windows chkdsk) don't handle properly.
            if ((img->flags & FLAG_UNALLOC_BITMAP) && (size & 7))
                size &= ~7;

            // round size down to a multiple of alignment/packet_size
            if (size % offsets[PSPACE_SIZE])
                size -= (size % offsets[PSPACE_SIZE]);

            if (size == 0)
                continue;

            if (size2 < size) {
                start2 = start;
                size2 = size;
            }
        }
    }
    start = start2;
    size = size2;

    if (size == 0) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SPACE_ERROR;
    }

    // Warning!!! for Windows!
    // TODO: multisession refer to last session pspace pos
    set_extent(img, USPACE, start, 31);
    start += 31;
    size -= 31;

    set_extent(img, PSPACE, start, size);

    for (i = 0; i < le32_to_cpu(img->udf_lvd[0]->numPartitionMaps); i++) {
        if (i == 1)
            value = cpu_to_le32(0xFFFFFFFF);
        else
            value = cpu_to_le32(size);
        memcpy(&img->udf_lvid->data[sizeof(uint32_t) * i], &value, sizeof(value));
    }
    for (i = 0; i < le32_to_cpu(img->udf_lvd[0]->numPartitionMaps); i++) {
        if (i == 1)
            value = cpu_to_le32(0xFFFFFFFF);
        else
            value = cpu_to_le32(size);
        memcpy(&img->udf_lvid->data[sizeof(uint32_t) * le32_to_cpu(img->udf_lvd[0]->numPartitionMaps) + sizeof(uint32_t) * i], &value, sizeof(value));
    }

    if (!next_extent(img->head, LVID)) {
        start = find_next_extent_size(img, 0, USPACE, sizes[LVID_SIZE], offsets[LVID_SIZE]);
        if (!start) {
            udf_fprintf(stdout, "Error: Not enough blocks on device\n");
            return UDFFS_SPACE_ERROR;
        }
        set_extent(img, LVID, start, sizes[LVID_SIZE]);
    }

    // Now remove temporary area prior start block
    if (start_block)
        remove_extent(img, find_extent(img, 0));

    return UDFFS_OK;
}

int udf_setup_all(udf_image *img, udf_image *disc, Directory *dir)
{
    assert(img);

    if (udf_setup_vrs(img) == UDFFS_OK && udf_setup_anchor(img) == UDFFS_OK
            && udf_setup_partion(img, disc, dir) == UDFFS_OK && udf_setup_vds(img) == UDFFS_OK)
        return UDFFS_OK;
    return UDFFS_SETUP_ERROR;
}

int udf_write_image(udf_image *img)
{
    assert(img);

    int ret = 0;
    udf_extent *start_ext = NULL;

    start_ext = img->head;

    if (writable() == UDFFS_OK || rand_success(70) == UDFFS_OK) {
        while (start_ext != NULL) {
            if ((ret = img->write(img, start_ext)) < 0)
                return UDFFS_WRITE_ERROR;
            start_ext = start_ext->next;
        }
    } else {
        udf_fprintf(stdout, "Warning: Not DDE!\n");
        while (start_ext != NULL) {
            img->fake_write(img, start_ext);
            start_ext = start_ext->next;
        }
    }

    return UDFFS_OK;
}

void udf_add_type1_partition(udf_image *img, uint16_t partitionNum)
{
    assert(img);

    struct genericPartitionMap1 *pm;
    int mtl = le32_to_cpu(img->udf_lvd[0]->mapTableLength);
    int npm = le32_to_cpu(img->udf_lvd[0]->numPartitionMaps);

    img->udf_lvd[0] = realloc(img->udf_lvd[0],
            sizeof(struct logicalVolDesc) + mtl +
            sizeof(struct genericPartitionMap1));

    if (!img->udf_lvd[0]) {
        udf_fprintf(stdout, "Error: realloc failed: %s\n", strerror(errno));
        abort();
    }

    pm = (struct genericPartitionMap1 *)&img->udf_lvd[0]->partitionMaps[mtl];
    mtl += sizeof(struct genericPartitionMap1);

    img->udf_lvd[0]->mapTableLength = cpu_to_le32(mtl);
    img->udf_lvd[0]->numPartitionMaps = cpu_to_le32(npm + 1);
    pm->partitionMapType = 1;
    pm->partitionMapLength = sizeof(struct genericPartitionMap1);
    pm->volSeqNum = cpu_to_le16(1);
    pm->partitionNum = cpu_to_le16(partitionNum);

    img->udf_lvid->numOfPartitions = cpu_to_le32(npm + 1);
    img->udf_lvid = realloc(img->udf_lvid,
                            sizeof(struct logicalVolIntegrityDesc) +
                            sizeof(uint32_t) * 2 * (npm + 1) +
                            sizeof(struct logicalVolIntegrityDescImpUse));
    if (!img->udf_lvid) {
        udf_fprintf(stdout, "Error: realloc failed: %s\n", strerror(errno));
        abort();
    }
    memmove(&img->udf_lvid->data[sizeof(uint32_t) * 2 * (npm + 1)],
            &img->udf_lvid->data[sizeof(uint32_t) * 2 * npm],
            sizeof(struct logicalVolIntegrityDescImpUse));
    memmove(&img->udf_lvid->data[sizeof(uint32_t) * (npm + 1)],
            &img->udf_lvid->data[sizeof(uint32_t) * npm],
            sizeof(uint32_t));
}

int udf_setup_vrs(udf_image *img)
{
    assert(img);

    udf_extent *ext;
    udf_desc *desc;

    if (!(ext = next_extent(img->head, VRS))) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SETUP_ERROR;
    }
    desc = set_desc(ext, 0x00, 0, sizeof(struct volStructDesc), NULL);
    img->udf_vrs[0] = (struct volStructDesc *)desc->data->buffer;
    img->udf_vrs[0]->structType = 0x00;
    img->udf_vrs[0]->structVersion = 0x01;
    memcpy(img->udf_vrs[0]->stdIdent, VSD_STD_ID_BEA01, VSD_STD_ID_LEN);

    if (img->blocksize >= 2048)
        desc = set_desc(ext, 0x00, 1, sizeof(struct volStructDesc), NULL);
    else
        desc = set_desc(ext, 0x00, 2048 / img->blocksize, sizeof(struct volStructDesc), NULL);
    img->udf_vrs[1] = (struct volStructDesc *)desc->data->buffer;
    img->udf_vrs[1]->structType = 0x00;
    img->udf_vrs[1]->structVersion = 0x01;
    if (img->udf_rev >= 0x0200)
        memcpy(img->udf_vrs[1]->stdIdent, VSD_STD_ID_NSR03, VSD_STD_ID_LEN);
    else
        memcpy(img->udf_vrs[1]->stdIdent, VSD_STD_ID_NSR02, VSD_STD_ID_LEN);

    if (img->blocksize >= 2048)
        desc = set_desc(ext, 0x00, 2, sizeof(struct volStructDesc), NULL);
    else
        desc = set_desc(ext, 0x00, 4096 / img->blocksize, sizeof(struct volStructDesc), NULL);
    img->udf_vrs[2] = (struct volStructDesc *)desc->data->buffer;
    img->udf_vrs[2]->structType = 0x00;
    img->udf_vrs[2]->structVersion = 0x01;
    memcpy(img->udf_vrs[2]->stdIdent, VSD_STD_ID_TEA01, VSD_STD_ID_LEN);

    return UDFFS_OK;
}

int udf_setup_anchor(udf_image *img)
{
    assert(img);

    udf_extent *ext = NULL;
    uint32_t mloc, rloc, mlen, rlen;
    int i = 0;

    ext = next_extent(img->head, MVDS);
    if (!ext) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SETUP_ERROR;
    }
    mloc = ext->start + img->next_sess_start;
    mlen = ext->blocks * img->blocksize;

    ext = next_extent(img->head, RVDS);
    if (!ext && img->blocks/* - img->start_block*/ > 257) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SETUP_ERROR;
    }
    if (img->blocks/* - img->start_block*/ > 257) {
        rloc = ext->start + img->next_sess_start;
        rlen = ext->blocks * img->blocksize;
    } else {
        rloc = mloc;
        rlen = mlen;
    }

    ext = next_extent(img->head, ANCHOR);
    if (!ext) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SETUP_ERROR;
    }
    do {
        ext->head = ext->tail = malloc(sizeof(udf_desc) + sizeof(udf_data));
        if (!ext->head) {
            udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
            return UDFFS_SETUP_ERROR;
        }
        ext->head->data = (udf_data *)((uint8_t *)ext->head + sizeof(udf_desc));
        ext->head->data->next = ext->head->data->prev = NULL;
        ext->head->ident = TAG_IDENT_AVDP;
        ext->head->offset = 0;
        ext->head->length = ext->head->data->length = sizeof(struct anchorVolDescPtr);
        img->udf_anchor[i] = ext->head->data->buffer = malloc(sizeof(struct anchorVolDescPtr));
        if (!img->udf_anchor[i]) {
            udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
            abort();
        }
        ext->head->next = ext->head->prev = NULL;
        img->udf_anchor[i]->mainVolDescSeqExt.extLocation = cpu_to_le32(mloc);
        img->udf_anchor[i]->mainVolDescSeqExt.extLength = cpu_to_le32(mlen);
        img->udf_anchor[i]->reserveVolDescSeqExt.extLocation = cpu_to_le32(rloc);
        img->udf_anchor[i]->reserveVolDescSeqExt.extLength = cpu_to_le32(rlen);
        memset(img->udf_anchor[i]->reserved, 0, sizeof(img->udf_anchor[i]->reserved));
        img->udf_anchor[i]->descTag = query_tag(img, ext, ext->head, 1);
        ext = next_extent(ext->next, ANCHOR);
    } while (i++, ext != NULL);

    return UDFFS_OK;
}

int udf_setup_partion(udf_image *img, udf_image *disc, Directory *dir)
{
    assert(img);

    udf_extent *pspace = NULL;

    pspace = next_extent(img->head, PSPACE);
    if (!pspace) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SETUP_ERROR;
    }

    if ((udf_setup_space(img, pspace, 0) != UDFFS_OK) ||
            ((udf_setup_fileset(img, pspace)) != UDFFS_OK) ||
            (udf_setup_root(img, pspace) != UDFFS_OK) ||
            (udf_setup_files(img, pspace, disc, dir) != UDFFS_OK)) {
        return UDFFS_SETUP_ERROR;
    }


    return UDFFS_OK;
}

int udf_setup_vds(udf_image *img)
{
    assert(img);

    udf_extent *mvds, *rvds, *lvid;

    mvds = next_extent(img->head, MVDS);
    rvds = next_extent(img->head, RVDS);
    lvid = next_extent(img->head, LVID);

    if (!mvds || (!rvds && img->blocks > 257) || !lvid) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        return UDFFS_SETUP_ERROR;
    }

    udf_setup_pvd(img, mvds, rvds, 0);
    udf_setup_lvid(img, lvid);
    udf_setup_lvd(img, mvds, rvds, lvid, 1);
    udf_setup_pd(img, mvds, rvds, 2);
    udf_setup_usd(img, mvds, rvds, 3);
    udf_setup_iuvd(img, mvds, rvds, 4);
    udf_setup_td(img, mvds, rvds, 5);

    return UDFFS_OK;
}

int udf_setup_space(udf_image *img, udf_extent *pspace, uint32_t offset)
{
    udf_desc *desc;
    struct partitionHeaderDesc *phd = (struct partitionHeaderDesc *)img->udf_pd[0]->partitionContentsUse;
    uint32_t length = (sizeof(struct spaceBitmapDesc) + (pspace->blocks+7)/8 + img->blocksize-1) / img->blocksize * img->blocksize;
    uint32_t value;

    memcpy(&value, &img->udf_lvid->data[sizeof(uint32_t)*0], sizeof(value));

    if (img->flags & FLAG_FREED_BITMAP) {
        phd->freedSpaceBitmap.extPosition = cpu_to_le32(offset);
        phd->freedSpaceBitmap.extLength = cpu_to_le32(length);
        value = cpu_to_le32(le32_to_cpu(value) - (length / img->blocksize));
    } else if (img->flags & FLAG_FREED_TABLE) {
        phd->freedSpaceTable.extPosition = cpu_to_le32(offset);
        phd->freedSpaceTable.extLength = cpu_to_le32(img->blocksize);
        value = cpu_to_le32(le32_to_cpu(value) - 1);
    } else if (img->flags & FLAG_UNALLOC_BITMAP) {
        phd->unallocSpaceBitmap.extPosition = cpu_to_le32(offset);
        phd->unallocSpaceBitmap.extLength = cpu_to_le32(length);
        value = cpu_to_le32(le32_to_cpu(value) - (length / img->blocksize));
    } else if (img->flags & FLAG_UNALLOC_TABLE) {
        phd->unallocSpaceTable.extPosition = cpu_to_le32(offset);
        phd->unallocSpaceTable.extLength = cpu_to_le32(img->blocksize);
        value = cpu_to_le32(le32_to_cpu(value) - 1);
    }

    memcpy(&img->udf_lvid->data[sizeof(uint32_t)*0], &value, sizeof(value));

    if (img->flags & FLAG_SPACE_BITMAP) {
        struct spaceBitmapDesc *sbd;
        int nBytes = (pspace->blocks+7)/8;

        length = sizeof(struct spaceBitmapDesc) + nBytes;
        desc = set_desc(pspace, TAG_IDENT_SBD, offset, length, NULL);
        sbd = (struct spaceBitmapDesc *)desc->data->buffer;
        sbd->numOfBits = cpu_to_le32(pspace->blocks);
        sbd->numOfBytes = cpu_to_le32(nBytes);
        memset(sbd->bitmap, 0xFF, sizeof(uint8_t) * nBytes);
        if (pspace->blocks%8)
            sbd->bitmap[nBytes-1] = 0xFF >> (8-(pspace->blocks%8));
        clear_bits(sbd->bitmap, offset, (length + img->blocksize - 1) / img->blocksize);
        sbd->descTag = udf_query_tag(img, TAG_IDENT_SBD, 1, desc->offset, desc->data, 0, sizeof(struct spaceBitmapDesc));
    } else if (img->flags & FLAG_SPACE_TABLE) {
        struct unallocSpaceEntry *use;
        short_ad *sad;
        uint32_t max_value = (UINT32_MAX & EXT_LENGTH_MASK);
        uint32_t max = (max_value / img->blocksize) * img->blocksize;
        uint32_t pos=0;
        uint64_t rem;

        length = img->blocksize;
        desc = set_desc(pspace, TAG_IDENT_USE, offset, img->blocksize, NULL);
        use = (struct unallocSpaceEntry *)desc->data->buffer;
        use->lengthAllocDescs = cpu_to_le32(sizeof(short_ad));
        sad = (short_ad *)&use->allocDescs[0];
        rem = (uint64_t)pspace->blocks * img->blocksize - length;
        if (img->blocksize - sizeof(struct unallocSpaceEntry) < (rem / max) * sizeof(short_ad)) {
            udf_fprintf(stdout, "Error: Creation of so large filesystems with unalloc table not supported.\n");
            return UDFFS_SETUP_ERROR;
        }
        pos = offset + (length/img->blocksize);
        if (rem > max_value) {
            while (rem > max) {
                sad->extLength = cpu_to_le32(EXT_NOT_RECORDED_ALLOCATED | max);
                sad->extPosition = cpu_to_le32(pos);
                pos += max / img->blocksize;
                sad ++;
                rem -= max;
                use->lengthAllocDescs = cpu_to_le32(le32_to_cpu(use->lengthAllocDescs) + sizeof(short_ad));
            }
        }
        sad->extLength = cpu_to_le32(EXT_NOT_RECORDED_ALLOCATED | rem);
        sad->extPosition = cpu_to_le32(pos);

        use->icbTag.strategyType = cpu_to_le16(4);
        use->icbTag.numEntries = cpu_to_le16(1);
        use->icbTag.parentICBLocation.logicalBlockNum = cpu_to_le32(0);
        use->icbTag.parentICBLocation.partitionReferenceNum = cpu_to_le16(0);
        use->icbTag.fileType = ICBTAG_FILE_TYPE_USE;
        use->icbTag.flags = cpu_to_le16(ICBTAG_FLAG_AD_SHORT);
        use->descTag = udf_query_tag(img, TAG_IDENT_USE, 1, desc->offset, desc->data, 0, sizeof(struct unallocSpaceEntry) + le32_to_cpu(use->lengthAllocDescs));
    }

    return UDFFS_OK;
}

int udf_setup_fileset(udf_image *img, udf_extent *pspace)
{
    uint32_t offset = 0;
    udf_desc *desc;
    int length = sizeof(struct fileSetDesc);
    long_ad ad;

    offset = udf_alloc_blocks(img, pspace, offset, 1);

    memset(&ad, 0, sizeof(ad));
    ad.extLength = cpu_to_le32(img->blocksize);
    ad.extLocation.logicalBlockNum = cpu_to_le32(offset + img->next_sess_start);
    ad.extLocation.partitionReferenceNum = cpu_to_le16(0);
    memcpy(img->udf_lvd[0]->logicalVolContentsUse, &ad, sizeof(ad));

    desc = set_desc(pspace, TAG_IDENT_FSD, offset, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = img->udf_fsd;
    img->udf_fsd->descTag = query_tag(img, pspace, desc, 1);

    return UDFFS_OK;
}

int udf_setup_root(udf_image *img, udf_extent *pspace)
{
    uint32_t offset = 0;
    udf_desc *desc, *fsd_desc;

    desc = udf_mkdir(img, pspace, NULL, 0, offset, NULL); // the root directory does not have a name
    offset = desc->offset;

    img->udf_fsd->rootDirectoryICB.extLength = cpu_to_le32(img->blocksize);
    img->udf_fsd->rootDirectoryICB.extLocation.logicalBlockNum = cpu_to_le32(offset + img->next_sess_start);
    img->udf_fsd->rootDirectoryICB.extLocation.partitionReferenceNum = cpu_to_le16(0);
    fsd_desc = next_desc(pspace->head, TAG_IDENT_FSD);
    img->udf_fsd->descTag = query_tag(img, pspace, fsd_desc, 1);

    return UDFFS_OK;
}

int udf_setup_files(udf_image *img, udf_extent *pspace, udf_image *disc, Directory *dir)
{
    uint32_t count = 0;
    long_ad icb;
    udf_desc *root_desc = NULL;
    struct fileEntry *root_fe = NULL;

    // find root
    icb = img->udf_fsd->rootDirectoryICB;
    root_desc = find_desc(pspace, icb.extLocation.logicalBlockNum - img->next_sess_start);
    root_fe = (struct fileEntry *)root_desc->data->buffer;
    if (!root_fe || root_fe->icbTag.fileType != ICBTAG_FILE_TYPE_DIRECTORY) {
        udf_fprintf(stdout, "Error: Not find root dir FE!");
        return UDFFS_SETUP_ERROR;
    }

    count = img->cur_file_count;
    root_desc->file = (udf_file *)calloc(1, sizeof (udf_file) * count);
    root_desc->fcount = count;
    if (img->flags & FLAG_MULTISESSION) {
        import_session(img, pspace, root_desc, disc, dir);
    } else {
        cpdir(img, pspace, root_desc, count, NULL);
    }

    return UDFFS_OK;
}

void udf_setup_pvd(udf_image *img, udf_extent *mvds, udf_extent *rvds, uint32_t offset)
{
    udf_desc *desc;
    int length = sizeof(struct primaryVolDesc);

    desc = set_desc(mvds, TAG_IDENT_PVD, offset, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = img->udf_pvd[0];
    img->udf_pvd[0]->descTag = query_tag(img, mvds, desc, 1);

    if (!rvds)
        return;
    desc = set_desc(rvds, TAG_IDENT_PVD, offset, length, NULL);
    memcpy(img->udf_pvd[1] = desc->data->buffer, img->udf_pvd[0], length);
    img->udf_pvd[1]->descTag = query_tag(img, rvds, desc, 1);
}

void udf_setup_lvd(udf_image *img, udf_extent *mvds, udf_extent *rvds, udf_extent *lvid, uint32_t offset)
{
    udf_desc *desc;
    int length = sizeof(struct logicalVolDesc) + le32_to_cpu(img->udf_lvd[0]->mapTableLength);

    img->udf_lvd[0]->integritySeqExt.extLength = cpu_to_le32(lvid->blocks * img->blocksize);
    img->udf_lvd[0]->integritySeqExt.extLocation = cpu_to_le32(lvid->start + img->next_sess_start);

    desc = set_desc(mvds, TAG_IDENT_LVD, offset, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = img->udf_lvd[0];
    img->udf_lvd[0]->descTag = query_tag(img, mvds, desc, 1);

    if (!rvds)
        return;
    desc = set_desc(rvds, TAG_IDENT_LVD, offset, length, NULL);
    memcpy(img->udf_lvd[1] = desc->data->buffer, img->udf_lvd[0], length);
    img->udf_lvd[1]->descTag = query_tag(img, rvds, desc, 1);
}

void udf_setup_pd(udf_image *img, udf_extent *mvds, udf_extent *rvds, uint32_t offset)
{
    udf_desc *desc;
    udf_extent *ext;
    uint32_t off_start;
    int length = sizeof(struct partitionDesc);

    ext = next_extent(img->head, PSPACE);
    if (!ext) {
        udf_fprintf(stdout, "Error: Not enough blocks on device\n");
        abort();
    }
    off_start = ext->start + img->next_sess_start;
    img->udf_pd[0]->partitionStartingLocation = cpu_to_le32(ext->start);
    img->udf_pd[0]->partitionLength = cpu_to_le32(off_start - ext->start + ext->blocks);

    desc = set_desc(mvds, TAG_IDENT_PD, offset, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = img->udf_pd[0];
    img->udf_pd[0]->descTag = query_tag(img, mvds, desc, 1);

    if (!rvds)
        return;
    desc = set_desc(rvds, TAG_IDENT_PD, offset, length, NULL);
    memcpy(img->udf_pd[1] = desc->data->buffer, img->udf_pd[0], length);
    img->udf_pd[1]->descTag = query_tag(img, rvds, desc, 1);
}

void udf_setup_usd(udf_image *img, udf_extent *mvds, udf_extent *rvds, uint32_t offset)
{
    udf_desc *desc;
    udf_extent *ext;
    int count = 0;
    int length = sizeof(struct unallocSpaceDesc);

    ext = next_extent(img->head, USPACE);
    while (ext) {
        length += sizeof(extent_ad);
        img->udf_usd[0] = realloc(img->udf_usd[0], length);
        if (!img->udf_usd[0]) {
            udf_fprintf(stdout, "Error: realloc failed: %s\n", strerror(errno));
            abort();
        }
        img->udf_usd[0]->numAllocDescs = cpu_to_le32(le32_to_cpu(img->udf_usd[0]->numAllocDescs)+1);
        img->udf_usd[0]->allocDescs[count].extLength = cpu_to_le32(ext->blocks * img->blocksize);
        img->udf_usd[0]->allocDescs[count].extLocation = cpu_to_le32(ext->start + img->next_sess_start);
        count ++;
        ext = next_extent(ext->next, USPACE);
    }

    desc = set_desc(mvds, TAG_IDENT_USD, offset, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = img->udf_usd[0];
    img->udf_usd[0]->descTag = query_tag(img, mvds, desc, 1);

    if (!rvds)
        return;
    desc = set_desc(rvds, TAG_IDENT_USD, offset, length, NULL);
    memcpy(img->udf_usd[1] = desc->data->buffer, img->udf_usd[0], length);
    img->udf_usd[1]->descTag = query_tag(img, rvds, desc, 1);
}

void udf_setup_iuvd(udf_image *img, udf_extent *mvds, udf_extent *rvds, uint32_t offset)
{
    udf_desc *desc;
    int length = sizeof(struct impUseVolDesc);

    desc = set_desc(mvds, TAG_IDENT_IUVD, offset, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = img->udf_iuvd[0];
    img->udf_iuvd[0]->descTag = query_tag(img, mvds, desc, 1);

    if (!rvds)
        return;
    desc = set_desc(rvds, TAG_IDENT_IUVD, offset, length, NULL);
    memcpy(img->udf_iuvd[1] = desc->data->buffer, img->udf_iuvd[0], length);
    img->udf_iuvd[1]->descTag = query_tag(img, rvds, desc, 1);
}

void udf_setup_td(udf_image *img, udf_extent *mvds, udf_extent *rvds, uint32_t offset)
{
    udf_desc *desc;
    int length = sizeof(struct terminatingDesc);

    desc = set_desc(mvds, TAG_IDENT_TD, offset, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = img->udf_td[0];
    img->udf_td[0]->descTag = query_tag(img, mvds, desc, 1);

    if (!rvds)
        return;
    desc = set_desc(rvds, TAG_IDENT_TD, offset, length, NULL);
    memcpy(img->udf_td[1] = desc->data->buffer, img->udf_td[0], length);
    img->udf_td[1]->descTag = query_tag(img, rvds, desc, 1);
}

void udf_setup_lvid(udf_image *disc, udf_extent *lvid)
{
    udf_desc *desc;
    int length = sizeof(struct logicalVolIntegrityDesc) + le32_to_cpu(disc->udf_lvid->numOfPartitions) * sizeof(uint32_t) * 2 + le32_to_cpu(disc->udf_lvid->lengthOfImpUse);

    desc = set_desc(lvid, TAG_IDENT_LVID, 0, 0, NULL);
    desc->length = desc->data->length = length;
    desc->data->buffer = disc->udf_lvid;
    disc->udf_lvid->descTag = query_tag(disc, lvid, desc, 1);

    if (/*!(disc->flags & FLAG_BLANK_TERMINAL) && */lvid->blocks > 1) {
        desc = set_desc(lvid, TAG_IDENT_TD, 1, sizeof(struct terminatingDesc), NULL);
        ((struct terminatingDesc *)desc->data->buffer)->descTag = query_tag(disc, lvid, desc, 1);
    }
}


