/*
 * 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 <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <assert.h>
#include <time.h>

#include <sys/stat.h>
#include <sys/time.h>
#include <libgen.h>
#include <glob.h>

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

#define BITS_PER_LONG 32

#define leBPL_to_cpup(x) leNUM_to_cpup(BITS_PER_LONG, x)
#define leNUM_to_cpup(x,y) xleNUM_to_cpup(x,y)
#define xleNUM_to_cpup(x,y) (le ## x ## _to_cpup(y))
#define uintBPL uint(BITS_PER_LONG)
#define uint(x) xuint(x)
#define xuint(x) uint ## x ## _t

#define MAX_SINGLE_FILE_SIZE 1073739776 // 1GB - 2048

void path_to_uname(char *pathname, dchars *uname, uint8_t *ulen)
{
    char *name;
    size_t len;
    name = basename(pathname); // Do not pass it pointers to  free(3)
    len = (uint8_t)encode_locale(uname, name, 256);
    if (len <= 0 || len == (size_t)-1) {
        *ulen = 0;
    } else {
        *ulen = (uint8_t)len;
    }
}


void updateTimestamp(time_t t, uint32_t ut, timestamp *tm)
{
    assert(tm);

    struct timeval	timev;
    struct tm		time;
    int			offset;

    if (t == 0) {
        gettimeofday(&timev, NULL);
    } else {
        timev.tv_sec = t;
        timev.tv_usec = ut;
    }

    localtime_r( &timev.tv_sec, &time);
    offset = time.tm_gmtoff / 60;
    tm->typeAndTimezone = 0x1000 + (offset & 0x0FFF);		/* 1/3.7.1 */
    tm->year = time.tm_year + 1900;
    tm->month = time.tm_mon + 1;;
    tm->day = time.tm_mday;
    tm->hour = time.tm_hour;
    tm->minute = time.tm_min;
    tm->second = time.tm_sec;
    tm->centiseconds = timev.tv_usec / 10000;
    tm->hundredsOfMicroseconds = (timev.tv_usec / 100) % 100;
    tm->microseconds = timev.tv_usec % 100 ;
}

typedef uint32_t __attribute__((may_alias)) uint32_t_a;
int	getExtents(uint32_t requestedLength, short_ad *extents, struct spaceBitmapDesc *spaceMap)
{
    uint32_t    blkno, lengthFound = 0;
    short_ad    *ext;
    uint32_t    mask;
    uint32_t_a  *bm;

    // clear extents
    ext = extents;
    ext->extLength = 0;
    bm = ((uint32_t_a*)spaceMap->bitmap) - 1;
    mask = 0;
    blkno = 0xFFFFFFFF;

    while( lengthFound < requestedLength ) {
    blkno++;
    mask <<= 1;
    if( mask == 0 ) {
        bm++;
        mask = 1;
        // check bm within limits
    }
    if( (*bm & mask) == 0 ) {			// if allocated
        if( ext->extLength != 0 ) {
            ext++;
        if( ext - extents > 31 ) {
            printf("GetExtents: Too many extents\n");
            return 0;
        }
        ext->extLength = 0;
        }
        continue;
    }
    if( ext->extLength == 0 )
        ext->extPosition = blkno;
    lengthFound += 2048;
        ext->extLength += 2048;
    }

    if( requestedLength != lengthFound  ) {
        ext->extLength = ((ext->extLength & ~2047) | (requestedLength & 2047)) - 2048;
    } else {
//        ext++;
//        ext->extLength = 0;
//        ext->extPosition = 0;
    }
    return (uint8_t*)ext - (uint8_t*)extents + sizeof(short_ad);
}

/**
 * @brief append a udf_data item containing a FID in the payload to the
 *        udf_data list for a directory tag:FE/EFE udf_descriptor
 * @param disc the udf_disc
 * @param pspace the type:PSPACE udf_extent for on-disc allocations
 * @param desc the file tag:FE/EFE udf_descriptor (for offset)
 * @param parent the directory tag:FE/EFE udf_descriptor
 * @param data the udf_data item containing the FID
 * @return the block number of the on-disc tag:FID udf_desciptor
 */
int insert_desc(udf_image *img, udf_extent *pspace, udf_desc *desc, udf_desc *parent, udf_data *data)
{
    uint32_t block = 0;

    struct fileEntry *fe = (struct fileEntry *)parent->data->buffer;

    if ((le16_to_cpu(fe->icbTag.flags) & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_IN_ICB)
    {
        block = parent->offset;
        append_data(parent, data);
        fe->lengthAllocDescs = cpu_to_le32(le32_to_cpu(fe->lengthAllocDescs) + data->length);
    }
    else
    {
        udf_desc *fiddesc = NULL;

        if (le32_to_cpu(fe->lengthAllocDescs) == 0)
        {
            block = udf_alloc_blocks(img, pspace, desc->offset, 1);
            fiddesc = set_desc(pspace, TAG_IDENT_FID, block, data->length, data);
            if ((le16_to_cpu(fe->icbTag.flags) & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_SHORT)
            {
                short_ad *sad;

                parent->length += sizeof(short_ad);
                parent->data->length += sizeof(short_ad);
                parent->data->buffer = realloc(parent->data->buffer, parent->length);
                if (!parent->data->buffer)
                {
                    udf_fprintf(stdout, "Error: realloc failed: %s\n", strerror(errno));
                    abort();
                }
                fe = (struct fileEntry *)parent->data->buffer;
                sad = (short_ad *)&fe->extendedAttrAndAllocDescs[le32_to_cpu(fe->lengthExtendedAttr) + le32_to_cpu(fe->lengthAllocDescs)];
                sad->extPosition = cpu_to_le32(block + img->next_sess_start);
                sad->extLength = cpu_to_le32(data->length);
                fe->lengthAllocDescs = cpu_to_le32(sizeof(short_ad));
            }
            else if ((le16_to_cpu(fe->icbTag.flags) & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_LONG)
            {
                long_ad *lad;

                parent->length += sizeof(long_ad);
                parent->data->length += sizeof(long_ad);
                parent->data->buffer = realloc(parent->data->buffer, parent->length);
                if (!parent->data->buffer)
                {
                    udf_fprintf(stdout, "Error: realloc failed: %s\n", strerror(errno));
                    abort();
                }
                fe = (struct fileEntry *)parent->data->buffer;
                lad = (long_ad *)&fe->extendedAttrAndAllocDescs[le32_to_cpu(fe->lengthExtendedAttr) + le32_to_cpu(fe->lengthAllocDescs)];
                lad->extLocation.logicalBlockNum = cpu_to_le32(block + img->next_sess_start);
                lad->extLocation.partitionReferenceNum = cpu_to_le16(0);
                lad->extLength = cpu_to_le32(data->length);
                fe->lengthAllocDescs = cpu_to_le32(sizeof(long_ad));
            }
            fe->logicalBlocksRecorded = cpu_to_le32(1);
        }
        else
        {
            if ((le16_to_cpu(fe->icbTag.flags) & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_SHORT)
            {
                short_ad *sad;

                sad = (short_ad *)&fe->extendedAttrAndAllocDescs[le32_to_cpu(fe->lengthExtendedAttr) + le32_to_cpu(fe->lengthAllocDescs) - sizeof(short_ad)];
                fiddesc = find_desc(pspace, le32_to_cpu(sad->extPosition));
                block = fiddesc->offset;
                append_data(fiddesc, data);
                sad->extLength = cpu_to_le32(le32_to_cpu(sad->extLength) + data->length);
            }
            else if ((le16_to_cpu(fe->icbTag.flags) & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_LONG)
            {
                long_ad *lad;

                lad = (long_ad *)&fe->extendedAttrAndAllocDescs[le32_to_cpu(fe->lengthExtendedAttr) + le32_to_cpu(fe->lengthAllocDescs) - sizeof(long_ad)];
                fiddesc = find_desc(pspace, le32_to_cpu(lad->extLocation.logicalBlockNum));
                block = fiddesc->offset;
                append_data(fiddesc, data);
                lad->extLength = cpu_to_le32(le32_to_cpu(lad->extLength) + data->length);
            }
        }
    }

    return block;
}

/**
 * @brief helper function to compute tag:FID udf_descriptor size and padding
 *        to a multiple of 4 bytes
 * @param length the length of the file name in bytes
 * @return the length of the required memory allocation
 */
uint32_t compute_ident_length(uint32_t length)
{
    return length + (4 - (length % 4)) %4;
}

/**
 * @brief utility function to find the first zero bit in an unsigned long
 * @param word the unsigned long to search
 * @return the 0 based bit position from the lsb or BITS_PER_LONG
 */
static inline unsigned long ffz(unsigned long word)
{
    unsigned long result;

    result = 0;
    while (word & 1)
    {
        result ++;
        word >>= 1;
    }

    return result;
}
/**
 * @brief find the first one bit in a space bitmap
 * @param addr the in-memory address of the space bitmap
 * @param size the size of the space bitmap in bits
 * @param offset the starting bit position for the search
 * @return the 0 based bit position or size
 */
static inline unsigned long udf_find_next_one_bit (void * addr, unsigned long size, unsigned long offset)
{
    uintBPL * p = ((uintBPL *) addr) + (offset / BITS_PER_LONG);
    uintBPL result = offset & ~(BITS_PER_LONG-1);
    uintBPL tmp;

    if (offset >= size)
        return size;
    size -= result;
    offset &= (BITS_PER_LONG-1);
    if (offset)
    {
        if (size >= BITS_PER_LONG)
            tmp = leBPL_to_cpup(p++);
        else
        {
            tmp = 0;
            memcpy(&tmp, p, (size+7)/8);
            tmp = leBPL_to_cpup(&tmp);
        }
        tmp &= ~0UL << offset;
        if (size < BITS_PER_LONG)
            goto found_first;
        if (tmp)
            goto found_middle;
        size -= BITS_PER_LONG;
        result += BITS_PER_LONG;
    }
    while (size & ~(BITS_PER_LONG-1))
    {
        if ((tmp = leBPL_to_cpup(p++)))
            goto found_middle;
        result += BITS_PER_LONG;
        size -= BITS_PER_LONG;
    }
    if (!size)
        return result;
    tmp = 0;
    memcpy(&tmp, p, (size+7)/8);
    tmp = leBPL_to_cpup(&tmp);
found_first:
    tmp &= ~0UL >> (BITS_PER_LONG-size);
    tmp |= (1UL << size);
found_middle:
    return result + ffz(~tmp);
}

/**
 * @brief find the first zero bit in a space bitmap
 * @param addr the in-memory address of the space bitmap
 * @param size the size of the space bitmap in bits
 * @param offset the starting bit position for the search
 * @return the 0 based bit position or size
 */
static inline unsigned long udf_find_next_zero_bit(void * addr, unsigned long size, unsigned long offset)
{
    uintBPL * p = ((uintBPL *) addr) + (offset / BITS_PER_LONG);
    uintBPL result = offset & ~(BITS_PER_LONG-1);
    uintBPL tmp;

    if (offset >= size)
        return size;
    size -= result;
    offset &= (BITS_PER_LONG-1);
    if (offset)
    {
        if (size >= BITS_PER_LONG)
            tmp = leBPL_to_cpup(p++);
        else
        {
            tmp = 0;
            memcpy(&tmp, p, (size+7)/8);
            tmp = leBPL_to_cpup(&tmp);
        }
        tmp |= (~0UL >> (BITS_PER_LONG-offset));
        if (size < BITS_PER_LONG)
            goto found_first;
        if (~tmp)
            goto found_middle;
        size -= BITS_PER_LONG;
        result += BITS_PER_LONG;
    }
    while (size & ~(BITS_PER_LONG-1))
    {
        if (~(tmp = leBPL_to_cpup(p++)))
            goto found_middle;
        result += BITS_PER_LONG;
        size -= BITS_PER_LONG;
    }
    if (!size)
        return result;
    tmp = 0;
    memcpy(&tmp, p, (size+7)/8);
    tmp = leBPL_to_cpup(&tmp);
found_first:
    tmp |= (~0UL << size);
    if (tmp == (uintBPL)~0UL)	/* Are any bits zero? */
        return result + size; /* Nope. */
found_middle:
    return result + ffz(tmp);
}

/**
 * @brief allocate an aligned space bitmap on-disc
 * @param disc the udf disc
 * @param bitmap the space bitmap tag:USB/FSB udf_descriptor
 * @param start the starting block number to search for on-disc allocations
 * @param blocks the number of blocks in the space bitmap
 * @return the starting block number of the on-disc aligned space bitmap
 */
int udf_alloc_bitmap_blocks(udf_image *disc, udf_desc *bitmap, uint32_t start, uint32_t blocks)
{
    uint32_t alignment = disc->sizing[PSPACE_SIZE].align;
    struct spaceBitmapDesc *sbd = (struct spaceBitmapDesc *)bitmap->data->buffer;
    uint32_t end;

    do {
        start = ((start + alignment - 1) / alignment) * alignment;
        if (start + blocks > le32_to_cpu(sbd->numOfBits)) {
            udf_fprintf(stdout, "Error: Not enough blocks on device\n");
            abort();
        }
        if (sbd->bitmap[start/8] & (1 << (start%8))) {
            end = udf_find_next_zero_bit(sbd->bitmap, le32_to_cpu(sbd->numOfBits), start);
        } else
            start = end = udf_find_next_one_bit(sbd->bitmap, le32_to_cpu(sbd->numOfBits), start);
    } while ((end - start) < blocks);

    clear_bits(sbd->bitmap, start, blocks);
    return start;
}

/**
 * @brief allocate a space table on-disc
 * @param disc the udf_disc
 * @param table the space table tag:USE/FSE udf_descriptor
 * @param start the starting block offset for the allocation search
 * @param blocks the number of blocks in the space table
 * @return the starting block number of the on-disc space table
 */
int udf_alloc_table_blocks(udf_image *disc, udf_desc *table, uint32_t start, uint32_t blocks)
{
    uint32_t alignment = disc->sizing[PSPACE_SIZE].align;
    struct unallocSpaceEntry *use = (struct unallocSpaceEntry *)table->data->buffer;
    uint32_t end, offset = 0;
    short_ad *sad;

    do {
        if (offset >= le32_to_cpu(use->lengthAllocDescs)) {
            udf_fprintf(stdout, "Error: Not enough blocks on device\n");
            abort();
        }
        sad = (short_ad *)&use->allocDescs[offset];
        if (start < le32_to_cpu(sad->extPosition))
            start = le32_to_cpu(sad->extPosition);
        start = ((start + alignment - 1) / alignment) * alignment;
        end = le32_to_cpu(sad->extPosition) + ((le32_to_cpu(sad->extLength) & EXT_LENGTH_MASK) / disc->blocksize);
        if (start > end)
            start = end;
        offset += sizeof(short_ad);
    } while ((end - start) < blocks);

    if (start == le32_to_cpu(sad->extPosition) && start + blocks == end) {
        /* deleted extent */
        memmove(&use->allocDescs[offset-sizeof(short_ad)],
                &use->allocDescs[offset],
                le32_to_cpu(use->lengthAllocDescs) - offset);
        use->lengthAllocDescs = cpu_to_le32(le32_to_cpu(use->lengthAllocDescs) - sizeof(short_ad));
        memset(&use->allocDescs[le32_to_cpu(use->lengthAllocDescs)], 0x00, sizeof(short_ad));
    } else if (start == le32_to_cpu(sad->extPosition)) {
        sad->extPosition = cpu_to_le32(start + blocks);
        sad->extLength = cpu_to_le32(le32_to_cpu(sad->extLength) - blocks * disc->blocksize);
    } else if (start + blocks == end) {
        sad->extLength = cpu_to_le32(le32_to_cpu(sad->extLength) - blocks * disc->blocksize);
    } else {
        memmove(&use->allocDescs[offset+sizeof(short_ad)],
                &use->allocDescs[offset],
                le32_to_cpu(use->lengthAllocDescs) - offset);
        sad->extLength = cpu_to_le32(EXT_NOT_RECORDED_ALLOCATED | (start - le32_to_cpu(sad->extPosition)) * disc->blocksize);
        sad = (short_ad *)&use->allocDescs[offset];
        sad->extPosition = cpu_to_le32(start+blocks);
        sad->extLength = cpu_to_le32(EXT_NOT_RECORDED_ALLOCATED | (end - start - blocks) * disc->blocksize);
        use->lengthAllocDescs = cpu_to_le32(le32_to_cpu(use->lengthAllocDescs) + sizeof(short_ad));
    }
    use->descTag = udf_query_tag(disc, TAG_IDENT_USE, 1, table->offset, table->data, 0, sizeof(struct unallocSpaceEntry) + le32_to_cpu(use->lengthAllocDescs));
    return start;
}

/**
 * @brief create an on-disc format tag for a udf_descriptor of a
 *        udf_extent with any space_type. For type:PSPACE the block
 *        number will be relative to to the first block of the partition,
 *        otherwise it will be relative to the first block of the media
 * @param img the udf_image
 * @param ext the udf_extent containing the udf_descriptor
 * @param desc the udf_descriptor
 * @param SerialNum the serial number to assign
 * @return the tag
 */
tag query_tag(udf_image *img, udf_extent *ext, udf_desc *desc, uint16_t SerialNum)
{
    tag ret;
    int i;
    udf_data *data;
    uint16_t crc = 0;
    int offset = sizeof(tag);

    ret.tagIdent = cpu_to_le16(desc->ident);
    if (img->udf_rev >= 0x0200)
        ret.descVersion = cpu_to_le16(3);
    else
        ret.descVersion = cpu_to_le16(2);
    ret.tagChecksum = 0;
    ret.reserved = 0;
    ret.tagSerialNum = cpu_to_le16(SerialNum);
    ret.descCRCLength = cpu_to_le16(desc->length - sizeof(tag));
    data = desc->data;
    while (data != NULL) {
        crc = udf_crc((uint8_t *)data->buffer + offset, data->length - offset, crc);
        offset = 0;
        data = data->next;
    }
    ret.descCRC = cpu_to_le16(crc);
    if (ext->space_type & PSPACE)
        ret.tagLocation = cpu_to_le32(desc->offset + img->next_sess_start);
    else
        ret.tagLocation = cpu_to_le32(ext->start + desc->offset + img->next_sess_start);
    for (i = 0; i < 16; i++)
        if (i != 4)
            ret.tagChecksum += (uint8_t)(((char *)&ret)[i]);

    return ret;
}

/**
 * @brief create an on-disc format tag from udf_descriptor components
 * @param img the udf_image
 * @param Ident the tag:Ident to assign
 * @param SerialNum the serial number to assign
 * @param Location the block number of the on-disc tag:Ident udf_descriptor
 * @param data the udf_data list head
 * @param skip the skip data length
 * @param length the summed data length
 * @return the tag
 */
tag udf_query_tag(udf_image *img, uint16_t Ident, uint16_t SerialNum, uint32_t Location, udf_data *data, uint32_t skip, uint32_t length)
{
    tag ret;
    int i;
    uint16_t crc = 0;
    uint32_t clength;
    uint32_t offset = sizeof(tag);

    ret.tagIdent = cpu_to_le16(Ident);
    if (img->udf_rev >= 0x0200)
        ret.descVersion = cpu_to_le16(3);
    else
        ret.descVersion = cpu_to_le16(2);
    ret.tagChecksum = 0;
    ret.reserved = 0;
    ret.tagSerialNum = cpu_to_le16(SerialNum);
    ret.descCRCLength = cpu_to_le16(length - sizeof(tag));
    while (data != NULL && length)
    {
        if ((clength = data->length) > length)
            clength = length;
        crc = udf_crc((uint8_t *)data->buffer + skip + offset, clength - offset, crc);
        length -= clength;
        offset = 0;
        skip = 0;
        data = data->next;
    }
    ret.descCRC = cpu_to_le16(crc);
    ret.tagLocation = cpu_to_le32(Location + img->next_sess_start);
    for (i=0; i<16; i++)
        if (i != 4)
            ret.tagChecksum += (uint8_t)(((char *)&ret)[i]);

    return ret;
}

/**
 * @brief allocate blocks on-disc
 * @param img the udf_image
 * @param start the starting block offset for the allocation search
 * @param blocks the number of blocks to allocate
 * @return the starting block number of the on-disc allocation
 */
int udf_alloc_blocks(udf_image *img, udf_extent *pspace, uint32_t start, uint32_t blocks)
{
    udf_desc *desc;
    struct partitionHeaderDesc *phd = (struct partitionHeaderDesc *)img->udf_pd[0]->partitionContentsUse;
    uint32_t value;

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

    if (img->flags & FLAG_FREED_BITMAP) {
        desc = find_desc(pspace, le32_to_cpu(phd->freedSpaceBitmap.extPosition));
        return udf_alloc_bitmap_blocks(img, desc, start, blocks);
    } else if (img->flags & FLAG_FREED_TABLE) {
        desc = find_desc(pspace, le32_to_cpu(phd->freedSpaceTable.extPosition));
        return udf_alloc_table_blocks(img, desc, start, blocks);
    } else if (img->flags & FLAG_UNALLOC_BITMAP)  {
        desc = find_desc(pspace, le32_to_cpu(phd->unallocSpaceBitmap.extPosition));
        return udf_alloc_bitmap_blocks(img, desc, start, blocks);
    } else if (img->flags & FLAG_UNALLOC_TABLE) {
        desc = find_desc(pspace, le32_to_cpu(phd->unallocSpaceTable.extPosition));
        return udf_alloc_table_blocks(img, desc, start, blocks);
    } else
        return 0;
}

/**
 * @brief create a file tag:FE/EFE udf_descriptor and add the file to a directory
 * @param disc the udf_disc
 * @param pspace the type:PSPACE udf_extent for on-disc allocations
 * @param name the file name in OSTA Compressed Unicode format (d-characters)
 * @param length the length of the file name in bytes
 * @param offset the starting block number to search for on-disc allocations
 * @param parent the directory tag:FE/EFE udf_descriptor
 * @param filechar file characteristics
 * @param filetype the file type
 * @param flags the file flags
 * @return the in-memory address of file tag:FE/EFE udf_descriptor
 */
udf_desc *udf_create(udf_image *img, udf_extent *pspace, const dchars *name, uint8_t length, uint32_t offset, udf_desc *parent, uint8_t filechar, uint8_t filetype, uint16_t flags)
{
    udf_desc *desc;

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

    struct fileEntry *fe;
    uint64_t uniqueID_le64;

    desc = set_desc(pspace, TAG_IDENT_FE, offset, sizeof(struct fileEntry), NULL);
    fe = (struct fileEntry *)desc->data->buffer;
    memcpy(fe, &default_fe, sizeof(struct fileEntry));
    memcpy(&fe->accessTime, &img->udf_pvd[0]->recordingDateAndTime, sizeof(timestamp));
    memcpy(&fe->modificationTime, &fe->accessTime, sizeof(timestamp));
    memcpy(&fe->attrTime, &fe->accessTime, sizeof(timestamp));
    if (filetype == ICBTAG_FILE_TYPE_STREAMDIR ||
            flags & ICBTAG_FLAG_STREAM)
        fe->uniqueID = cpu_to_le64(0);
    else {
        memcpy(&fe->uniqueID, img->udf_lvid->logicalVolContentsUse, sizeof(fe->uniqueID));
        if (!(le64_to_cpu(fe->uniqueID) & 0x00000000FFFFFFFFUL))
            uniqueID_le64 = cpu_to_le64(le64_to_cpu(fe->uniqueID) + 16);
        else
            uniqueID_le64 = cpu_to_le64(le64_to_cpu(fe->uniqueID) + 1);
        memcpy(img->udf_lvid->logicalVolContentsUse, &uniqueID_le64, sizeof(uniqueID_le64));
    }

    fe->icbTag.fileType = filetype;
    fe->icbTag.flags = ICBTAG_FLAG_AD_SHORT;
    if (parent) {
        //		fe->icbTag.parentICBLocation.logicalBlockNum = cpu_to_le32(parent->offset); // for strategy type != 4
        fe->icbTag.parentICBLocation.logicalBlockNum = cpu_to_le32(0);
        fe->icbTag.parentICBLocation.partitionReferenceNum = cpu_to_le16(0);
        insert_fid(img, pspace, desc, parent, name, length, filechar);
    } else {
        fe->icbTag.parentICBLocation.logicalBlockNum = cpu_to_le32(0);
        fe->icbTag.parentICBLocation.partitionReferenceNum = cpu_to_le16(0);
        if (filetype == ICBTAG_FILE_TYPE_DIRECTORY) {// root directory
            fe->uid = cpu_to_le32(img->uid);
            fe->gid = cpu_to_le32(img->gid);
            fe->permissions = cpu_to_le32(
                        ((img->mode & S_IRWXU) << 4) |
                        ((img->mode & S_IRWXG) << 2) |
                        ((img->mode & S_IRWXO) << 0) |
                        ((img->mode & S_IWUSR) ? FE_PERM_U_CHATTR : 0) |
                        ((img->mode & S_IWGRP) ? FE_PERM_G_CHATTR : 0) |
                        ((img->mode & S_IWOTH) ? FE_PERM_O_CHATTR : 0) |
                        0 // Do not allow deleting root directory
                        );
        }
    }
    if (filetype == ICBTAG_FILE_TYPE_DIRECTORY)
        query_lvidiu(img)->numDirs = cpu_to_le32(le32_to_cpu(query_lvidiu(img)->numDirs)+1);
    else if (filetype != ICBTAG_FILE_TYPE_STREAMDIR && filetype != ICBTAG_FILE_TYPE_VAT20 && filetype != ICBTAG_FILE_TYPE_VAT15 && !(flags & ICBTAG_FLAG_STREAM))
        query_lvidiu(img)->numFiles = cpu_to_le32(le32_to_cpu(query_lvidiu(img)->numFiles)+1);


    return desc;
}

/**
 * @brief create a directory tag:FE/EFE udf_descriptor and add the directory to
 *        a parent directory
 * @param disc the udf_disc
 * @param pspace the type:PSPACE udf_extent for on-disc allocations
 * @param name the file name in OSTA Compressed Unicode format (d-characters)
 * @param length the length of the file name in bytes
 * @param offset the starting block number to search for on-disc allocations
 * @param parent the parent directory tag:FE/EFE udf_descriptor
 * @return the in-memory address of the directory tag:FE/EFE udf_descriptor
 */
udf_desc *udf_mkdir(udf_image *img, udf_extent *pspace, const dchars *name, uint8_t length, uint32_t offset, udf_desc *parent)
{
    udf_desc *desc = udf_create(img, pspace, name, length, offset, parent, FID_FILE_CHAR_DIRECTORY, ICBTAG_FILE_TYPE_DIRECTORY, 0);

    if (!parent)
        parent = desc; // the root directory is it's own parent
    insert_fid(img, pspace, parent, desc, NULL, 0, FID_FILE_CHAR_DIRECTORY | FID_FILE_CHAR_PARENT); // directory parent back links are unnamed

    return desc;
}


/**
 * @brief create a FID and add it to a directory then increment the file
 *        link count
 * @param disc the udf_disc
 * @param pspace the type:PSPACE udf_extent for on-disc allocations
 * @param desc the file tag:FE/EFE udf_descriptor
 * @param parent the directory tag:FE/EFE udf_descriptor
 * @param name the file name in OSTA Compressed Unicode format (d-characters)
 * @param length the length of the file name in bytes
 * @param filechar the file characteristics
 * @return void
 */
void insert_fid(udf_image *img, udf_extent *pspace, udf_desc *desc, udf_desc *parent, const dchars *name, uint8_t length, uint8_t filechar)
{
    udf_data *data;
    struct fileIdentDesc *fid;
    struct allocDescImpUse *adiu;
    int ilength = compute_ident_length(sizeof(struct fileIdentDesc) + length);
    int offset;
    uint64_t uniqueID;
    uint32_t uniqueID_le32;

    data = alloc_data(NULL, ilength);
    fid = data->buffer;

    offset = insert_desc(img, pspace, desc, parent, data);
    fid->descTag.tagLocation = cpu_to_le32(offset);

    struct fileEntry *fe = (struct fileEntry *)desc->data->buffer;

    fe->fileLinkCount = cpu_to_le16(le16_to_cpu(fe->fileLinkCount) + 1);
    uniqueID = le64_to_cpu(fe->uniqueID);

    fe = (struct fileEntry *)parent->data->buffer;

    fid->icb.extLength = cpu_to_le32(img->blocksize);
    fid->icb.extLocation.logicalBlockNum = cpu_to_le32(desc->offset + img->next_sess_start);
    fid->icb.extLocation.partitionReferenceNum = cpu_to_le16(0);

    uniqueID_le32 = cpu_to_le32(uniqueID & 0x00000000FFFFFFFFUL);
    adiu = (struct allocDescImpUse *)fid->icb.impUse;
    memcpy(adiu->impUse, &uniqueID_le32, sizeof(uniqueID_le32));

    fid->fileVersionNum = cpu_to_le16(1);
    fid->fileCharacteristics = filechar;
    fid->lengthFileIdent = length;
    fid->lengthOfImpUse = cpu_to_le16(0);
    memcpy(fid->impUseAndFileIdent, name, length);
    fid->descTag = udf_query_tag(img, TAG_IDENT_FID, 1, le32_to_cpu(fid->descTag.tagLocation), data, 0, ilength); // later
    fe->informationLength = cpu_to_le64(le64_to_cpu(fe->informationLength) + ilength);

    *(tag *)desc->data->buffer = query_tag(img, pspace, desc, 1);
    *(tag *)parent->data->buffer = query_tag(img, pspace, parent, 1);
}

/**
 * @brief pre_insert_fid
 * @param img
 * @param pspace
 * @param parent
 * @param name
 * @param length
 * @param filechar
 */
void pre_insert_fid(udf_image *img, udf_extent *pspace, udf_desc *parent, udf_file *file, uint8_t filechar)
{
    udf_data *data;
    struct fileIdentDesc *fid;
    int offset;
    int ilength;

    ilength = compute_ident_length(sizeof(struct fileIdentDesc) + file->ulen);
    data = alloc_data(NULL, ilength);
    fid = data->buffer;
    offset = insert_desc(img, pspace, parent, parent, data);

    struct fileEntry *fe = (struct fileEntry *)parent->data->buffer;
    fid->descTag.tagLocation = cpu_to_le32(offset);
    fid->icb.extLength = cpu_to_le32(img->blocksize);
    fid->icb.extLocation.partitionReferenceNum = cpu_to_le16(0);
    fid->fileVersionNum = cpu_to_le16(1);
    fid->fileCharacteristics = filechar;
    fid->lengthFileIdent = file->ulen;
    fid->lengthOfImpUse = cpu_to_le16(0);
    memcpy(fid->impUseAndFileIdent + fid->lengthOfImpUse, file->uname, file->ulen);
    fe->informationLength = cpu_to_le64(le64_to_cpu(fe->informationLength) + ilength);
    file->fid_data = data;
    offset += (fe->informationLength / img->blocksize);
}

/**
 * @brief copyFile
 * @param img
 * @param pspace
 * @param pathname
 * @param fst
 * @param parent
 */
void copyFile(udf_image *img, udf_extent *pspace, udf_file *file, struct stat *fst, udf_desc *parent)
{
    assert(img);
    assert(pspace);
    assert(file);
    assert(file->fid_data);
    assert(fst);
    assert(parent);

    uint32_t offset;
    uint64_t uniqueID_le64;
    udf_desc *file_desc;
    udf_desc *sbd_desc;
    struct fileEntry *fe;
    struct spaceBitmapDesc *sbd;
    short_ad *ext;

    uint32_t ad_count = (uint32_t)(fst->st_size / MAX_SINGLE_FILE_SIZE);
    ad_count = (fst->st_size % MAX_SINGLE_FILE_SIZE == 0) ? ad_count : ad_count + 1;
    uint32_t fe_len = sizeof (struct fileEntry) + ad_count * sizeof (short_ad);
    // create desc and fe
    offset = udf_alloc_blocks(img, pspace, 0, 1); // Warning: offset 0
    file_desc = set_desc(pspace, TAG_IDENT_FE, offset, fe_len, NULL);
    file_desc->file = file;
    fe = (struct fileEntry *)file_desc->data->buffer;
    memcpy(fe, &default_fe, sizeof(struct fileEntry));
    memcpy(&fe->uniqueID, img->udf_lvid->logicalVolContentsUse, sizeof(fe->uniqueID));
    if (!(le64_to_cpu(fe->uniqueID) & 0x00000000FFFFFFFFUL))
        uniqueID_le64 = cpu_to_le64(le64_to_cpu(fe->uniqueID) + 16);
    else
        uniqueID_le64 = cpu_to_le64(le64_to_cpu(fe->uniqueID) + 1);
    memcpy(img->udf_lvid->logicalVolContentsUse, &uniqueID_le64, sizeof(uniqueID_le64));

    fe->icbTag.fileType = ICBTAG_FILE_TYPE_REGULAR;
    fe->icbTag.flags = 0;
    fe->icbTag.parentICBLocation.logicalBlockNum = cpu_to_le32(0);
    fe->icbTag.parentICBLocation.partitionReferenceNum = cpu_to_le16(0);

    fe->uid = fst->st_uid;
    fe->gid = fst->st_gid;

    if (fst->st_mode & S_IRUSR)
        fe->permissions |= FE_PERM_U_READ;
    if (fst->st_mode & S_IWUSR)
        fe->permissions |= FE_PERM_U_WRITE | FE_PERM_U_DELETE | FE_PERM_U_CHATTR;;
    if (fst->st_mode & S_IXUSR)
        fe->permissions |= FE_PERM_U_EXEC;
    if (fst->st_mode & S_IRGRP)
        fe->permissions |= FE_PERM_G_READ;
    if (fst->st_mode & S_IWGRP)
        fe->permissions |= FE_PERM_G_WRITE | FE_PERM_G_DELETE | FE_PERM_G_CHATTR;
    if (fst->st_mode & S_IXGRP)
        fe->permissions |= FE_PERM_G_EXEC;
    if (fst->st_mode & S_IROTH)
        fe->permissions |= FE_PERM_O_READ;
    if (fst->st_mode & S_IWOTH)
        fe->permissions |= FE_PERM_O_WRITE | FE_PERM_O_DELETE | FE_PERM_O_CHATTR;;
    if (fst->st_mode & S_IXOTH)
        fe->permissions |= FE_PERM_O_EXEC;
    if (fst->st_mode & S_ISUID)
        fe->icbTag.flags |= ICBTAG_FLAG_SETUID;
    if (fst->st_mode & S_ISGID)
        fe->icbTag.flags |= ICBTAG_FLAG_SETGID;

    fe->informationLength = (uint64_t)fst->st_size;
    fe->fileLinkCount = cpu_to_le16(le16_to_cpu(fe->fileLinkCount) + 1); // Warning: parent dir++

    timestamp tm;
    updateTimestamp(fst->st_atime, 0, &tm);
    fe->accessTime = tm;
    updateTimestamp(fst->st_atime, 0, &tm);
    fe->modificationTime = tm;
    updateTimestamp(fst->st_atime, 0, &tm);
    fe->attrTime = tm;

    /* check whether embedding of data is possible  */
    fe->logicalBlocksRecorded = ((fst->st_size + 2047) & ~2047) >> 11;

    /* extents for the file data */
    sbd_desc = next_desc(pspace->head, TAG_IDENT_SBD);
    sbd = (struct spaceBitmapDesc *)sbd_desc->data->buffer;
    ext = (short_ad*)fe->extendedAttrAndAllocDescs + fe->lengthExtendedAttr;
    // flags must is 0 (short_ad)
    if (fe->informationLength > MAX_SINGLE_FILE_SIZE) {
        int i;
        int count = (int)((fe->informationLength / MAX_SINGLE_FILE_SIZE));
        uint64_t req_len = fe->informationLength;
        for (i = 0; i < count; i++, ext++) {
            getExtents(MAX_SINGLE_FILE_SIZE, ext, sbd);
            ext->extPosition += img->next_sess_start;
            req_len -= MAX_SINGLE_FILE_SIZE;
            udf_alloc_blocks(img, pspace, offset, MAX_SINGLE_FILE_SIZE >> 11); // for data extent
        }
        if (req_len > 0) {
            getExtents(req_len, ext, sbd);
            ext->extPosition += img->next_sess_start;
            count += 1;
            udf_alloc_blocks(img, pspace, offset, ((req_len + 2047) & ~2047) >> 11); // for data extent
        } else {
            udf_fprintf(stdout, "Error: No space for file!");
            abort();
        }
        fe->lengthAllocDescs = (uint32_t)count * sizeof (short_ad);
    } else {
        getExtents(fe->informationLength, ext, sbd);
        ext->extPosition += img->next_sess_start;
        fe->lengthAllocDescs = sizeof (short_ad);
        udf_alloc_blocks(img, pspace, offset, fe->logicalBlocksRecorded); // for data extent
    }

    /* update parent fid */
    struct fileIdentDesc *fid = (struct fileIdentDesc *)file->fid_data->buffer;
    uint64_t uniqueID;
    uint32_t uniqueID_le32;
    uniqueID = le64_to_cpu(fe->uniqueID);
    uniqueID_le32 = cpu_to_le32(uniqueID & 0x00000000FFFFFFFFUL);
    fid->icb.extLocation.logicalBlockNum = cpu_to_le32(file_desc->offset + img->next_sess_start);
    struct allocDescImpUse *adiu = (struct allocDescImpUse *)fid->icb.impUse;
    memcpy(adiu->impUse, &uniqueID_le32, sizeof(uniqueID_le32));
    fid->descTag = udf_query_tag(img, TAG_IDENT_FID, 1,
                                       le32_to_cpu(fid->descTag.tagLocation), file->fid_data, 0, file->fid_data->length);

    /* TODO : update parent fe filecount */

    /* write tag */
    *(tag *)file_desc->data->buffer = query_tag(img, pspace, file_desc, 1);
    *(tag *)parent->data->buffer = query_tag(img, pspace, parent, 1);

    query_lvidiu(img)->numFiles = cpu_to_le32(le32_to_cpu(query_lvidiu(img)->numFiles) + 1);
    file->fe = fe;
}

/**
 * @brief copyDirectory
 * @param img
 * @param pspace
 * @param file
 * @param fst
 * @param parent
 */
void copyDirectory(udf_image *img, udf_extent *pspace, udf_file *file, udf_desc *parent)
{
    assert(img);
    assert(pspace);
    assert(file);
    assert(file->fid_data);
    assert(parent);

    udf_desc *desc;
    struct fileEntry *fe;
    glob_t globbuf;
    char dir_pat[PATH_MAX] = {0};

    /* mkdir and insert self fid */
    // Warning: offset 0
    desc = udf_create(img, pspace, file->uname, file->ulen, 0, NULL, FID_FILE_CHAR_DIRECTORY, ICBTAG_FILE_TYPE_DIRECTORY, 0);
    insert_fid(img, pspace, desc, desc, NULL, 0, FID_FILE_CHAR_DIRECTORY | FID_FILE_CHAR_PARENT); // directory parent back links are unnamed
    fe = (struct fileEntry *)desc->data->buffer;

    /* update parent fid and write parent tag */
    struct fileIdentDesc *fid = (struct fileIdentDesc *)file->fid_data->buffer;
    uint64_t uniqueID;
    uint32_t uniqueID_le32;
    uniqueID = le64_to_cpu(fe->uniqueID);
    uniqueID_le32 = cpu_to_le32(uniqueID & 0x00000000FFFFFFFFUL);
    fid->icb.extLocation.logicalBlockNum = cpu_to_le32(desc->offset + img->next_sess_start);
    struct allocDescImpUse *adiu = (struct allocDescImpUse *)fid->icb.impUse;
    memcpy(adiu->impUse, &uniqueID_le32, sizeof(uniqueID_le32));
    fid->descTag = udf_query_tag(img, TAG_IDENT_FID, 1,
                                       le32_to_cpu(fid->descTag.tagLocation), file->fid_data, 0, file->fid_data->length);

    *(tag *)parent->data->buffer = query_tag(img, pspace, parent, 1);
    file->fe = fe;

    /* Recursive Copy */
    strcpy(dir_pat, file->file_name);
    strcat(dir_pat, "/*");
    if (glob(dir_pat, 0, NULL, &globbuf) < 0) {
        perror("glob()");
        abort();
    }

    /* add hidden files */
    memset(dir_pat, 0, sizeof (dir_pat));
    strcpy(dir_pat, file->file_name);
    strcat(dir_pat, "/.*");
    if (glob(dir_pat, GLOB_APPEND, NULL, &globbuf) < 0) {
        perror("glob()");
        abort();
    }

    desc->file = (udf_file *)calloc(1, sizeof (udf_file) * globbuf.gl_pathc);
    desc->fcount = (uint32_t)globbuf.gl_pathc;
    cpdir(img, pspace, desc, globbuf.gl_pathc, globbuf.gl_pathv);
    globfree(&globbuf);
}

void linkFile(udf_image *img, udf_extent *pspace, udf_file *file, struct fileEntry *old_fe, udf_desc *parent)
{
    assert(img);
    assert(pspace);
    assert(file);
    assert(old_fe);
    assert(parent);

    uint32_t offset;
    uint64_t uniqueID_le64;
    udf_desc *file_desc;
    struct fileEntry *fe;

    uint32_t ad_count = (uint32_t)(old_fe->informationLength / MAX_SINGLE_FILE_SIZE);
    ad_count = (old_fe->informationLength % MAX_SINGLE_FILE_SIZE == 0) ? ad_count : ad_count + 1;
    uint32_t fe_len = sizeof (struct fileEntry) + ad_count * sizeof (short_ad);
    // create desc and fe
    offset = udf_alloc_blocks(img, pspace, 0, 1); // Warning: offset 0
    file_desc = set_desc(pspace, TAG_IDENT_FE, offset, fe_len, NULL);
    fe = (struct fileEntry *)file_desc->data->buffer;
    memcpy(fe, old_fe, fe_len);
    memcpy(&fe->uniqueID, img->udf_lvid->logicalVolContentsUse, sizeof(fe->uniqueID));
    if (!(le64_to_cpu(fe->uniqueID) & 0x00000000FFFFFFFFUL))
        uniqueID_le64 = cpu_to_le64(le64_to_cpu(fe->uniqueID) + 16);
    else
        uniqueID_le64 = cpu_to_le64(le64_to_cpu(fe->uniqueID) + 1);
    memcpy(img->udf_lvid->logicalVolContentsUse, &uniqueID_le64, sizeof(uniqueID_le64));

    fe->icbTag.fileType = ICBTAG_FILE_TYPE_REGULAR;
    fe->icbTag.flags = 0;
    fe->icbTag.parentICBLocation.logicalBlockNum = cpu_to_le32(0);
    fe->icbTag.parentICBLocation.partitionReferenceNum = cpu_to_le16(0);

    /* update parent fid */
    struct fileIdentDesc *fid = (struct fileIdentDesc *)file->fid_data->buffer;
    uint64_t uniqueID;
    uint32_t uniqueID_le32;
    uniqueID = le64_to_cpu(fe->uniqueID);
    uniqueID_le32 = cpu_to_le32(uniqueID & 0x00000000FFFFFFFFUL);
    fid->icb.extLocation.logicalBlockNum = cpu_to_le32(file_desc->offset + img->next_sess_start);
    struct allocDescImpUse *adiu = (struct allocDescImpUse *)fid->icb.impUse;
    memcpy(adiu->impUse, &uniqueID_le32, sizeof(uniqueID_le32));
    fid->descTag = udf_query_tag(img, TAG_IDENT_FID, 1,
                                       le32_to_cpu(fid->descTag.tagLocation), file->fid_data, 0, file->fid_data->length);

    /* write tag */
    *(tag *)file_desc->data->buffer = query_tag(img, pspace, file_desc, 1);
    *(tag *)parent->data->buffer = query_tag(img, pspace, parent, 1);

    query_lvidiu(img)->numFiles = cpu_to_le32(le32_to_cpu(query_lvidiu(img)->numFiles) + 1);
}

udf_desc *linkDirectory(udf_image *img, udf_extent *pspace, udf_file *file, udf_desc *parent)
{
    assert(img);
    assert(pspace);
    assert(file);
    assert(parent);

    udf_desc *desc = NULL;
    struct fileEntry *fe;

    desc = udf_create(img, pspace, file->uname, file->ulen, 0, NULL, FID_FILE_CHAR_DIRECTORY, ICBTAG_FILE_TYPE_DIRECTORY, 0);
    insert_fid(img, pspace, desc, desc, NULL, 0, FID_FILE_CHAR_DIRECTORY | FID_FILE_CHAR_PARENT); // directory parent back links are unnamed
    fe = (struct fileEntry *)desc->data->buffer;

    /* update parent fid and write parent tag */
    struct fileIdentDesc *fid = (struct fileIdentDesc *)file->fid_data->buffer;
    uint64_t uniqueID;
    uint32_t uniqueID_le32;
    uniqueID = le64_to_cpu(fe->uniqueID);
    uniqueID_le32 = cpu_to_le32(uniqueID & 0x00000000FFFFFFFFUL);
    fid->icb.extLocation.logicalBlockNum = cpu_to_le32(desc->offset + img->next_sess_start);
    struct allocDescImpUse *adiu = (struct allocDescImpUse *)fid->icb.impUse;
    memcpy(adiu->impUse, &uniqueID_le32, sizeof(uniqueID_le32));
    fid->descTag = udf_query_tag(img, TAG_IDENT_FID, 1,
                                       le32_to_cpu(fid->descTag.tagLocation), file->fid_data, 0, file->fid_data->length);

    *(tag *)parent->data->buffer = query_tag(img, pspace, parent, 1);

    return desc;
}

/**
 * @brief cpdir, import all files in a dir to `desc`
 * @param img
 * @param pspace
 * @param desc
 * @param count
 * @param names is null if from root dir, neither it from `globbuf`
 */
void cpdir(udf_image *img, udf_extent *pspace, udf_desc *desc, size_t count, char **names)
{
    size_t i;
    struct stat fst;
    uint64_t fid_len;

    // hold fid space
    for (i = 0; i < count; i++) {
        char *name = (names == NULL) ? img->inner_files[i].file_name : names[i];
        char *base = basename(name);
        if (strcmp(base, ".") == 0 || strcmp(base, "..") == 0)
            continue;
        desc->file[i].file_name = (char *)malloc(strlen(name) + 1);
        memset(desc->file[i].file_name, 0, strlen(name) + 1);
        strcpy(desc->file[i].file_name, name);
        path_to_uname(desc->file[i].file_name, desc->file[i].uname, &desc->file[i].ulen);

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

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

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

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

    // write fe and alloc data extent space
    for (i = 0; i < count; i++) {
        if (lstat(desc->file[i].file_name, &fst) < 0) {
            udf_fprintf(stdout, "Warning: Run stat() failed on '%s'\n", desc->file[i].file_name);
            continue;
        }

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

