/*
 * 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>
 *             lanxuesong<lanxuesong@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 <stdlib.h>
#include <string.h>
#include <assert.h>

#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <inttypes.h>

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

#define INCREASE_CHILD_COUNT 10

static uint64_t get_size(int fd)
{
    struct stat st;
    uint64_t size;
    uint64_t offset;

    if (fstat(fd, &st) == 0) {
        if (S_ISBLK(st.st_mode) && ioctl(fd, BLKGETSIZE64, &size) == 0)
            return size;
        else if (S_ISREG(st.st_mode))
            return (uint64_t)st.st_size;
    }

    offset = (uint64_t)lseek(fd, 0, SEEK_END);
    if (offset == (uint64_t)-1) {
        udf_fprintf(stdout, "Error: Cannot detect size of disk: %s\n", strerror(errno));
        abort();
    }

    if (lseek(fd, 0, SEEK_SET) != 0) {
        udf_fprintf(stdout, "Error: Cannot seek to start of disk: %s\n", strerror(errno));
        abort();
    }

    return offset;
}

int read_offset(int fd, udf_image *disc, void *buf, loff_t offset, size_t count, int warn_beyond)
{
    loff_t off;
    ssize_t ret;

    if (offset + (loff_t)count > (loff_t)disc->blocks * disc->blocksize) {
        if (warn_beyond)
            udf_fprintf(stdout, "Warning: Trying to read beyond end of disk\n");
        return -1;
    }

    off = lseek(fd, offset, SEEK_SET);
    if (off != (loff_t)-1 && off != offset) {
        errno = EIO;
        off = (loff_t)-1;
    }
    if (off == (loff_t)-1) {
        udf_fprintf(stdout, "Warning: lseek failed: %s\n", strerror(errno));
        return -1;
    }

    ret = read_nointr(fd, buf, count);
    if (ret >= 0 && (size_t)ret != count) {
        errno = EIO;
        ret = -1;
    }
    if (ret < 0) {
        udf_fprintf(stdout, "Warning: read failed: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

static int read_anchor_i(int fd, udf_image *disc, int i, uint32_t location)
{
    struct anchorVolDescPtr avdp;

    if (read_offset(fd, disc, &avdp, (loff_t)location * disc->blocksize, sizeof(avdp), 1) < 0)
        return -2;

    if (le32_to_cpu(avdp.descTag.tagLocation) != location)
        return -2;
    if (le16_to_cpu(avdp.descTag.tagIdent) != TAG_IDENT_AVDP)
        return -2;

    disc->udf_anchor[i] = malloc(sizeof(avdp));
    if (!disc->udf_anchor[i])
    {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return -1;
    }

    memcpy(disc->udf_anchor[i], &avdp, sizeof(avdp));

//	ext = set_extent(disc, ANCHOR, location, 1);
//	set_desc(ext, TAG_IDENT_AVDP, 0, sizeof(avdp), alloc_data(disc->udf_anchor[i], sizeof(avdp)));

    return 0;
}

static int read_anchor_first(int fd, udf_image *disc)
{
    return read_anchor_i(fd, disc, 0, disc->last_sess_start + 256);
}

static int read_anchor_second(int fd, udf_image *disc)
{
    int ret1, ret2;
    uint32_t last_block;


    last_block = disc->blocks - 1;

    if (last_block > 256 && (loff_t)(last_block - 256) * disc->blocksize > (loff_t)32768 + disc->blocksize && last_block - 256 != 256)
        ret1 = read_anchor_i(fd, disc, 1, last_block - 256);
    else
        ret1 = -2;
    if (ret1 == -1)
        return -1;

    if ((loff_t)last_block * disc->blocksize > (loff_t)32768 + disc->blocksize && last_block != 256)
        ret2 = read_anchor_i(fd, disc, 2, last_block);
    else
        ret2 = -2;
    if (ret2 == -1)
        return -1;

    if (disc->udf_anchor[0] && disc->udf_anchor[1] && disc->udf_anchor[0]->mainVolDescSeqExt.extLocation != disc->udf_anchor[1]->mainVolDescSeqExt.extLocation)
        udf_fprintf(stdout, "Warning: First and second Anchor Volume Descriptor Pointer contain different location of Main Volume Descriptor Sequence, maybe wrong --startblock or --lastblock?\n");
    if (disc->udf_anchor[0] && disc->udf_anchor[2] && disc->udf_anchor[0]->mainVolDescSeqExt.extLocation != disc->udf_anchor[2]->mainVolDescSeqExt.extLocation)
        udf_fprintf(stdout, "Warning: First and third Anchor Volume Descriptor Pointer contain different location of Main Volume Descriptor Sequence, maybe wrong --startblock or --lastblock?\n");

    if (disc->udf_anchor[0])
        return 0;

    if (ret1 < 0 && ret2 < 0)
        return -2;

    if (ret2 < 0)
        udf_fprintf(stdout, "Warning: First and third Anchor Volume Descriptor Pointer not found, using second\n");
    else if (ret1 < 0)
        udf_fprintf(stdout, "Warning: First and second Anchor Volume Descriptor Pointer not found, using third\n");
    else
        udf_fprintf(stdout, "Warning: First Anchor Volume Descriptor Pointer not found, using second\n");

    return 0;
}

static int read_vrs(int fd, udf_image *disc, int *bea, int *nsr, int *tea)
{
    struct volStructDesc vsd;
    uint32_t vsd_len;
    int i;

    vsd_len = disc->blocksize > 2048 ? disc->blocksize : 2048;
    *nsr = -1;
    *bea = -1;
    *tea = -1;

    for (i = 0; i < 64; ++i) {
        if (32768 + i*vsd_len >= 256*disc->blocksize)
            break;
        if (read_offset(fd, disc, &vsd, (loff_t)disc->last_sess_start * disc->blocksize + 32768 + i * vsd_len, sizeof(vsd), 0) < 0)
            break;
        if (!vsd.stdIdent[0])
            break;
        else if (memcmp(vsd.stdIdent, VSD_STD_ID_BEA01, VSD_STD_ID_LEN) == 0) {
            if (*bea == -1) {
                *bea = i;
                disc->udf_vrs[0] = malloc(sizeof(vsd));
                if (!disc->udf_vrs[0]) {
                    udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
                    return -1;
                }
                memcpy(disc->udf_vrs[0], &vsd, sizeof(vsd));
            }
        } else if (memcmp(vsd.stdIdent, VSD_STD_ID_NSR02, VSD_STD_ID_LEN) == 0 ||
                 memcmp(vsd.stdIdent, VSD_STD_ID_NSR03, VSD_STD_ID_LEN) == 0) {
            if (*nsr == -1) {
                *nsr = i;
                disc->udf_vrs[1] = malloc(sizeof(vsd));
                if (!disc->udf_vrs[1]) {
                    udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
                    return -1;
                }
                memcpy(disc->udf_vrs[1], &vsd, sizeof(vsd));
            }
        } else if (memcmp(vsd.stdIdent, VSD_STD_ID_TEA01, VSD_STD_ID_LEN) == 0) {
            if (*tea == -1) {
                *tea = i;
                disc->udf_vrs[2] = malloc(sizeof(vsd));
                if (!disc->udf_vrs[2]) {
                    udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
                    return -1;
                }
                memcpy(disc->udf_vrs[2], &vsd, sizeof(vsd));
            }
        } else if (memcmp(vsd.stdIdent, VSD_STD_ID_BOOT2, VSD_STD_ID_LEN) != 0 &&
                 memcmp(vsd.stdIdent, VSD_STD_ID_CD001, VSD_STD_ID_LEN) != 0 &&
                 memcmp(vsd.stdIdent, VSD_STD_ID_CDW02, VSD_STD_ID_LEN) != 0)
            break;
    }

    if (i == 64)
        udf_fprintf(stdout, "Warning: Too many Volume Structure Descriptors in Volume Recognition Sequence, stopping scanning\n");

    if (*nsr == -1)
        return -2;

    return 0;
}

static int choose_anchor(udf_image *disc)
{
    if (disc->udf_anchor[0])
        return 0;
    else if (disc->udf_anchor[1])
        return 1;
    else if (disc->udf_anchor[2])
        return 2;
    else
        return -1;
}


static int scan_vds(int fd, udf_image *disc, enum udf_space_type vds_type)
{
    uint32_t location, length, count, i;
    uint32_t next_location, next_length, next_count;
    uint16_t type;
    size_t gd_length;
    struct genericDesc *gd_ptr;
    struct primaryVolDesc *pvd_ptr;
    struct partitionDesc *pd_ptr;
    struct impUseVolDesc *iuvd_ptr;
    struct volDescPtr *vdp;
    struct logicalVolDesc *lvd;
    struct unallocSpaceDesc *usd;
    struct domainIdentSuffix *dis;
    struct UDFIdentSuffix *uis;
    unsigned char buffer[512];
    int id, anchor;
    int nested;
    int done;

    anchor = choose_anchor(disc);
    if (anchor == -1)
        return -2;

    if (vds_type == MVDS)
    {
        next_location = le32_to_cpu(disc->udf_anchor[anchor]->mainVolDescSeqExt.extLocation);
        next_length = le32_to_cpu(disc->udf_anchor[anchor]->mainVolDescSeqExt.extLength) & EXT_LENGTH_MASK;
        id = 0;
    } else if (vds_type == RVDS) {
        next_location = le32_to_cpu(disc->udf_anchor[anchor]->reserveVolDescSeqExt.extLocation);
        next_length = le32_to_cpu(disc->udf_anchor[anchor]->reserveVolDescSeqExt.extLength) & EXT_LENGTH_MASK;
        id = 1;
        if (next_location == le32_to_cpu(disc->udf_anchor[anchor]->mainVolDescSeqExt.extLocation)) {
            udf_fprintf(stdout, "Warning: Reserve Volume Descriptor Sequence is on same location as Main\n");
            disc->udf_pvd[1] = disc->udf_pvd[0];
            disc->udf_lvd[1] = disc->udf_lvd[0];
            disc->udf_pd[1] = disc->udf_pd[0];
            disc->udf_usd[1] = disc->udf_usd[0];
            disc->udf_iuvd[1] = disc->udf_iuvd[0];
            disc->udf_td[1] = disc->udf_td[0];
            return 0;
        }
    } else {
        udf_fprintf(stdout, "Error: Wrong Volume Descriptor Sequence type\n");
        abort();
    }

    next_count = next_length / disc->blocksize;

    if (!next_location || !next_count)
        return -2;

    nested = 0;

    while (next_location && next_count) {
        if (++nested > 64) {
            udf_fprintf(stdout, "Warning: Too many nested Volume Descriptor Sequences, stopping scanning\n");
            break;
        }

        location = next_location;
        length = next_length;
        count = next_count;
        next_location = 0;
        next_length = 0;
        next_count = 0;

        if (count > 256)
            length = 256 * disc->blocksize;

        done = 0;

        for (i = 0; i < count; ++i) {
            if (count >= 256) {
                udf_fprintf(stdout, "Warning: Too many descriptors %d in Volume Descriptor Sequence, stopping scanning\n", count);
                break;
            }

            if (read_offset(fd, disc, &buffer, ((loff_t)location+i) * disc->blocksize, sizeof(buffer), 1) < 0)
                return -3;

            gd_ptr = (struct genericDesc *)&buffer;
            type = le16_to_cpu(gd_ptr->descTag.tagIdent);
            if (type == 0)
                break;

            if (le32_to_cpu(gd_ptr->descTag.tagLocation) != location+i) {
                udf_fprintf(stdout, "Warning: Incorrect Volume Descriptor\n");
                return -3;
            }

            switch (type) {
                case TAG_IDENT_PVD:
                case TAG_IDENT_PD:
                case TAG_IDENT_IUVD:
                case TAG_IDENT_TD:
                case TAG_IDENT_VDP:
                default:
                    gd_ptr = malloc(sizeof(buffer));
                    if (!gd_ptr)
                    {
                        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
                        return -1;
                    }
                    memcpy(gd_ptr, &buffer, sizeof(buffer));

                    switch (type) {
                        case TAG_IDENT_PVD:
                            pvd_ptr = (struct primaryVolDesc *)gd_ptr;
                            /* Take all PVDs with the smallest primaryVolDescNum and then choose first with the highest volDescSeqNum */
                            if (!disc->udf_pvd[id] || le32_to_cpu(disc->udf_pvd[id]->primaryVolDescNum) > le32_to_cpu(pvd_ptr->primaryVolDescNum) ||
                                (le32_to_cpu(disc->udf_pvd[id]->primaryVolDescNum) == le32_to_cpu(pvd_ptr->primaryVolDescNum) &&
                                 le32_to_cpu(disc->udf_pvd[id]->volDescSeqNum) < le32_to_cpu(pvd_ptr->volDescSeqNum)))
                                disc->udf_pvd[id] = pvd_ptr;
                            break;

                        case TAG_IDENT_PD:
                            pd_ptr = (struct partitionDesc *)gd_ptr;
                            if (!disc->udf_pd[id] || le16_to_cpu(disc->udf_pd[id]->partitionNumber) == le16_to_cpu(pd_ptr->partitionNumber)) {
                                if (!disc->udf_pd[id] || le32_to_cpu(disc->udf_pd[id]->volDescSeqNum) < le32_to_cpu(pd_ptr->volDescSeqNum))
                                    disc->udf_pd[id] = pd_ptr;
                            } else {
                                udf_fprintf(stdout, "Warning: More then two Partition Descriptors are present, ignoring others\n");
                            }
                            break;

                        case TAG_IDENT_IUVD:
                            iuvd_ptr = (struct impUseVolDesc *)gd_ptr;
                            if (strncmp((char *)iuvd_ptr->impIdent.ident, UDF_ID_LV_INFO, sizeof(iuvd_ptr->impIdent.ident)) == 0) {
                                if (!disc->udf_iuvd[id] || le32_to_cpu(disc->udf_iuvd[id]->volDescSeqNum) < le32_to_cpu(iuvd_ptr->volDescSeqNum)) {
                                    disc->udf_iuvd[id] = iuvd_ptr;
                                    uis = (struct UDFIdentSuffix *)disc->udf_iuvd[id]->impIdent.identSuffix;
                                    if (disc->udf_rev < le16_to_cpu(uis->UDFRevision))
                                        disc->udf_rev = le16_to_cpu(uis->UDFRevision);
                                }
                            }
                            break;

                        case TAG_IDENT_TD:
                            if (!disc->udf_td[id])
                                disc->udf_td[id] = (struct terminatingDesc *)gd_ptr;
                            done = 1;
                            break;

                        case TAG_IDENT_VDP:
                            vdp = (struct volDescPtr *)gd_ptr;
                            next_location = le32_to_cpu(vdp->nextVolDescSeqExt.extLocation);
                            if (next_location <= location) {
                                udf_fprintf(stdout, "Warning: Next descriptor in Volume Descriptor Sequence is not on higher block number, ignoring it\n");
                                next_location = 0;
                            } else {
                                next_length = le32_to_cpu(vdp->nextVolDescSeqExt.extLength) & EXT_LENGTH_MASK;
                                next_count = next_length / disc->blocksize;
                            }
                            done = 1;
                            break;
                        default:
                            udf_fprintf(stdout, "Warning: Unknown descriptor in Volume Descriptor Sequence\n");
                            break;
                    }
                    break;

                case TAG_IDENT_LVD:
                    lvd = (struct logicalVolDesc *)gd_ptr;

                    if (sizeof(*lvd) + (uint64_t)le32_to_cpu(lvd->mapTableLength) > 256*disc->blocksize) {
                        udf_fprintf(stdout, "Warning: Logical Volume Descriptor is too big %"PRIu64"\n", (uint64_t)(sizeof(*lvd) + (uint64_t)le32_to_cpu(lvd->mapTableLength)));
                        i = count-1;
                        break;
                    }

                    gd_length = sizeof(*lvd) + le32_to_cpu(lvd->mapTableLength);
                    if (i*disc->blocksize + gd_length > length) {
                        udf_fprintf(stdout, "Warning: Logical Volume Descriptor is beyond end of Volume Descriptor Sequence\n");
                        i = count-1;
                        break;
                    }

                    lvd = malloc(gd_length);
                    if (!lvd) {
                        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
                        return -1;
                    }

                    if (gd_length <= sizeof(buffer))
                        memcpy(lvd, &buffer, gd_length);
                    else {
                        memcpy(lvd, &buffer, sizeof(buffer));
                        if (read_nointr(fd, (uint8_t *)lvd + sizeof(buffer), gd_length - sizeof(buffer)) != (ssize_t)(gd_length - sizeof(buffer))) {
                            udf_fprintf(stdout, "Warning: read failed: %s\n", strerror(errno ? errno : EIO));
                            free(lvd);
                            return -3;
                        }
                    }


                    if (gd_length > disc->blocksize)
                        i += (gd_length + (disc->blocksize-1)) / disc->blocksize - 1;

                    if (strncmp((char *)lvd->domainIdent.ident, UDF_ID_COMPLIANT, sizeof(lvd->domainIdent.ident)) == 0) {
                        if (!disc->udf_lvd[id] || le32_to_cpu(disc->udf_lvd[id]->volDescSeqNum) < le32_to_cpu(lvd->volDescSeqNum)) {
                            disc->udf_lvd[id] = lvd;
                            dis = (struct domainIdentSuffix *)lvd->domainIdent.identSuffix;
                            if (disc->udf_rev < le16_to_cpu(dis->UDFRevision))
                                disc->udf_rev = le16_to_cpu(dis->UDFRevision);
                        }

                        if (le32_to_cpu(lvd->logicalBlockSize) != disc->blocksize)
                            udf_fprintf(stdout, "Warning: blocksize in Logical Volume Descriptor is different as expected\n");
                    }

                    break;

                case TAG_IDENT_USD:
                    usd = (struct unallocSpaceDesc *)&buffer;

                    if (sizeof(*usd) + (uint64_t)le32_to_cpu(usd->numAllocDescs) * sizeof(*usd->allocDescs) > 256*disc->blocksize) {
                        udf_fprintf(stdout, "Warning: Unallocated Space Descriptor is too big (%"PRIu64")\n", (uint64_t)(sizeof(*usd) + (uint64_t)le32_to_cpu(usd->numAllocDescs) * sizeof(*usd->allocDescs)));
                        i = count-1;
                        break;
                    }

                    gd_length = sizeof(*usd) + le32_to_cpu(usd->numAllocDescs) * sizeof(*usd->allocDescs);
                    if (i*disc->blocksize + gd_length > length) {
                        udf_fprintf(stdout, "Warning: Unallocated Space Descriptor is beyond end of Volume Descriptor Sequence\n");
                        i = count-1;
                        break;
                    }

                    usd = malloc(gd_length);
                    if (!usd) {
                        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
                        return -1;
                    }

                    if (gd_length <= sizeof(buffer))
                        memcpy(usd, &buffer, gd_length);
                    else {
                        memcpy(usd, &buffer, sizeof(buffer));
                        if (read_nointr(fd, (uint8_t *)usd + sizeof(buffer), gd_length - sizeof(buffer)) != (ssize_t)(gd_length - sizeof(buffer))) {
                            udf_fprintf(stdout, "Warning: read failed: %s\n", strerror(errno ? errno : EIO));
                            free(usd);
                            return -3;
                        }
                    }

                    if (gd_length > disc->blocksize)
                        i += (gd_length + (disc->blocksize-1)) / disc->blocksize - 1;

                    if (!disc->udf_usd[id] || le32_to_cpu(disc->udf_usd[id]->volDescSeqNum) < le32_to_cpu(usd->volDescSeqNum))
                        disc->udf_usd[id] = usd;

                    break;
            }

            if (done)
                break;
        }
    }

    return 0;
}

static void scan_mvds(int fd, udf_image *disc)
{
    int ret;

    ret = scan_vds(fd, disc, MVDS);
    if (ret == -2)
        udf_fprintf(stdout, "Warning: Main Volume Descriptor Sequence not found\n");
    else if (ret == -3)
        udf_fprintf(stdout, "Warning: Main Volume Descriptor Sequence is damaged\n");
}

static void scan_rvds(int fd, udf_image *disc)
{
    int ret;

    ret = scan_vds(fd, disc, RVDS);
    if (ret == -2)
        udf_fprintf(stdout, "Warning: Reserve Volume Descriptor Sequence not found\n");
    else if (ret == -3)
        udf_fprintf(stdout, "Warning: Reserve Volume Descriptor Sequence is damaged\n");
}

static int detect_udf(int fd, udf_image *disc)
{
    int ret, ret2;
    uint64_t start = 0;
    int bea, nsr, tea;
    struct stat st;
    struct cdrom_multisession multisession;

    // detect whether multisession
    memset(&multisession, 0, sizeof(multisession));
    multisession.addr_format = CDROM_LBA;
    if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode) && ioctl(fd, CDROMMULTISESSION, &multisession) == 0 && multisession.xa_flag) {
        if ((uint32_t)multisession.addr.lba != disc->last_sess_start)
            udf_fprintf(stdout, "Warning: `last_sess_start` [%d] not equal to real last session [%d]\n", disc->last_sess_start, multisession.addr.lba);
    }
    start = disc->last_sess_start * (disc->blocksize ? disc->blocksize : 2048);
    disc->blocks = get_size(fd) / disc->blocksize;

    // detect vrs
    ret = read_vrs(fd, disc, &bea, &nsr, &tea);
    if (ret < 0) {
        if (ret == -2)
            udf_fprintf(stdout, "Error: UDF Volume Recognition Sequence not found\n");
        return -1;
    }

    ret = read_anchor_first(fd, disc);
    ret2 = read_anchor_second(fd, disc);

    if (ret < 0 && ret2 < 0) {
        udf_fprintf(stdout, "Error: UDF Volume Recognition Sequence found but not Anchor Volume Descriptor Pointer\n");
        return -1;
    }

    return 0;
}

static struct genericPartitionMap *find_partition(udf_image *disc, uint8_t type, const char *ident, uint16_t partition, uint16_t *partition_map_num)
{
    uint32_t i, offset;
    struct genericPartitionMap *pmap;
    struct genericPartitionMap1 *pm1;
    struct udfPartitionMap2 *upm2;
    int id;

    if (disc->udf_lvd[0])
        id = 0;
    else if (disc->udf_lvd[1])
        id = 1;
    else
        return NULL;

    for (i = 0, offset = 0; i < le32_to_cpu(disc->udf_lvd[id]->numPartitionMaps) && offset < le32_to_cpu(disc->udf_lvd[id]->mapTableLength); ++i, offset += pmap->partitionMapLength)
    {
        pmap = (struct genericPartitionMap *)&disc->udf_lvd[id]->partitionMaps[offset];

        if (type != (uint8_t)-1 && pmap->partitionMapType != type)
            continue;

        if (partition_map_num && *partition_map_num != (uint16_t)-1 && i != *partition_map_num)
            continue;

        if (partition != (uint16_t)-1)
        {
            if (pmap->partitionMapType == GP_PARTITION_MAP_TYPE_1)
            {
                pm1 = (struct genericPartitionMap1 *)pmap;
                if (le16_to_cpu(pm1->partitionNum) != partition)
                    continue;
            }
            else if (pmap->partitionMapType == GP_PARTITION_MAP_TYPE_2)
            {
                upm2 = (struct udfPartitionMap2 *)pmap;
                if (le16_to_cpu(upm2->partitionNum) != partition)
                    continue;
            }
            else
                continue;
        }

        if (ident)
        {
            if (pmap->partitionMapType != GP_PARTITION_MAP_TYPE_2)
                continue;
            upm2 = (struct udfPartitionMap2 *)pmap;
            if (strncmp((char *)upm2->partIdent.ident, ident, sizeof(upm2->partIdent.ident)) != 0)
                continue;
        }

        if (partition_map_num && *partition_map_num == (uint16_t)-1)
            *partition_map_num = i;
        return pmap;
    }

    return NULL;
}

static uint32_t find_block_position(struct genericPartitionMap *pmap, uint32_t block, uint16_t *partition)
{
    struct genericPartitionMap1 *pm1;

    if (pmap->partitionMapType == GP_PARTITION_MAP_TYPE_1) {
        pm1 = (struct genericPartitionMap1 *)pmap;
        *partition = le16_to_cpu(pm1->partitionNum);
        return block;
    } else {
        udf_fprintf(stdout, "Warning: Unknown Partition Map\n");
        return UINT32_MAX;
    }
}

static struct partitionDesc *find_partition_descriptor(udf_image *disc, uint16_t partition)
{
    int id;

    if (disc->udf_pd[0])
        id = 0;
    else if (disc->udf_pd[1])
        id = 1;
    else
        return NULL;

    if (le16_to_cpu(disc->udf_pd[id]->partitionNumber) == partition)
        return disc->udf_pd[id];

    return NULL;
}

static void read_fsd(int fd, udf_image *disc)
{
    long_ad *ad;
    uint16_t partition;
    uint32_t block, location, position, length;
    struct genericPartitionMap *pmap;
    struct partitionDesc *pd;
    int id;

    if (disc->udf_lvd[0])
        id = 0;
    else if (disc->udf_lvd[1])
        id = 1;
    else
        return;

    ad = (long_ad *)disc->udf_lvd[id]->logicalVolContentsUse;
    block = le32_to_cpu(ad->extLocation.logicalBlockNum);
    partition = le16_to_cpu(ad->extLocation.partitionReferenceNum);
    length = le32_to_cpu(ad->extLength) & EXT_LENGTH_MASK;

    pmap = find_partition(disc, -1, NULL, -1, &partition);
    if (!pmap) {
        udf_fprintf(stdout, "Warning: Incorrect Logical Volume Descriptor\n");
        return;
    }

    position = find_block_position(pmap, block, &partition);
    if (position == UINT32_MAX) {
        udf_fprintf(stdout, "Warning: File Set Descriptor cannot be read\n");
        return;
    }

    pd = find_partition_descriptor(disc, partition);
    if (!pd) {
        udf_fprintf(stdout, "Warning: File Set Descriptor cannot be read\n");
        return;
    }

    location = le32_to_cpu(pd->partitionStartingLocation) + position;

    if (sizeof(*disc->udf_fsd) > length) {
        udf_fprintf(stdout, "Warning: Incorrect File Set Descriptor\n");
        return;
    }

    disc->udf_fsd = malloc(length);
    if (!disc->udf_fsd) {
        udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
        return;
    }

    if (read_offset(fd, disc, disc->udf_fsd, (loff_t)location * disc->blocksize, length, 1) < 0) {
        free(disc->udf_fsd);
        disc->udf_fsd = NULL;
        return;
    }

    if (le32_to_cpu(disc->udf_fsd->descTag.tagLocation) != block) {
        udf_fprintf(stdout, "Warning: Incorrect Logical Volume Integrity Descriptor\n");
        free(disc->udf_fsd);
        disc->udf_fsd = NULL;
        return;
    }

    if (le16_to_cpu(disc->udf_fsd->descTag.tagIdent) != TAG_IDENT_FSD) {
        udf_fprintf(stdout, "Warning: Incorrect File Set Descriptor\n");
        free(disc->udf_fsd);
        disc->udf_fsd = NULL;
        return;
    }

    if (strncmp((char *)disc->udf_fsd->domainIdent.ident, UDF_ID_COMPLIANT, sizeof(disc->udf_fsd->domainIdent.ident)) != 0) {
        udf_fprintf(stdout, "Warning: Unsupported File Set Descriptor\n");
        free(disc->udf_fsd);
        disc->udf_fsd = NULL;
        return;
    }
}

static void read_fid_by_fe(int fd, udf_image *disc, Directory *dir, struct fileEntry *fe)
{
    uint32_t location, length;
    uint fid_blkno, fid_blklen;
    short_ad *sh = NULL;
    long_ad *lo = NULL;
    struct fileIdentDesc *fid = NULL;

    if ((fe->icbTag.flags & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_IN_ICB) {
        dir->fid_data = calloc(1, fe->lengthAllocDescs);
        fid = (struct fileIdentDesc *)dir->fid_data;
        memcpy(fid, fe->extendedAttrAndAllocDescs + fe->lengthExtendedAttr, fe->lengthAllocDescs);
    } else {
        length = (fe->informationLength + 2047) & ~2047;
        dir->fid_data = calloc(1, length);
        fid = (struct fileIdentDesc *)dir->fid_data;
        dir->fid_len = length;
        if ((fe->icbTag.flags & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_SHORT) {
            sh = (short_ad*)(fe->extendedAttrAndAllocDescs + fe->lengthExtendedAttr);
            fid_blkno = sh->extPosition;
            fid_blklen = sh->extLength;
        } else if ((fe->icbTag.flags & ICBTAG_FLAG_AD_MASK) == ICBTAG_FLAG_AD_LONG) {
            lo = (long_ad*)(fe->extendedAttrAndAllocDescs + fe->lengthExtendedAttr);
            fid_blkno = lo->extLocation.logicalBlockNum;
            fid_blklen = lo->extLength;
        } else {
            udf_fprintf(stdout, "Warning: Root FE falgs not correct: %d\n", fe->icbTag.flags);
            return;
        }

        // Warning: test a lot of fid
        location = le32_to_cpu(disc->udf_pd[0]->partitionStartingLocation) + fid_blkno;
        if (read_offset(fd, disc, fid, (loff_t)location * disc->blocksize, fid_blklen, 1) < 0) {
            udf_fprintf(stdout, "Warning: Read root FID Failed\n");
            return;
        }
    }
}

static void traversal_dir(int fd, udf_image *disc, Directory *parent)
{
    struct fileIdentDesc *fid;
    struct fileEntry *curfe = (struct fileEntry *)parent->fe;
    uint64_t i;
    uint32_t count = 0;
    uint32_t location;

    if (!curfe)
        return;

    for (i = 0; i < curfe->informationLength;
         i += ((sizeof(struct fileIdentDesc) + fid->lengthOfImpUse + fid->lengthFileIdent + 3) & ~3)) {
        fid = (struct fileIdentDesc*)(parent->fid_data + i);
        if (fid->fileCharacteristics & FID_FILE_CHAR_DELETED)
            continue;
        if (fid->fileCharacteristics & FID_FILE_CHAR_PARENT)
            continue;
        count++;
        if (!(fid->fileCharacteristics & FID_FILE_CHAR_DIRECTORY))
            continue;

        Directory *dir = calloc(1, sizeof (Directory));
        dir->children = (Directory **)calloc(INCREASE_CHILD_COUNT, sizeof (Directory *));
        dir->c_capcity = INCREASE_CHILD_COUNT;
        decode_locale((dchars *)(fid->impUseAndFileIdent + fid->lengthOfImpUse), dir->name, fid->lengthFileIdent, sizeof(dir->name));
        strcpy(dir->path, parent->path);
        strcat(dir->path, dir->name);
        strcat(dir->path, "/");
        dir->parent = dir;
        if (parent->c_len >= parent->c_capcity) {
            parent->children = (Directory **)realloc(parent->children,
                                                    (parent->c_capcity + INCREASE_CHILD_COUNT) * sizeof (Directory *));

            parent->c_capcity += INCREASE_CHILD_COUNT;
        }
        parent->children[parent->c_len] = dir;
        parent->c_len++;

        // parse fe and fid
        struct fileEntry *fe = (struct fileEntry *)dir->fe;
        location = le32_to_cpu(disc->udf_pd[0]->partitionStartingLocation) + fid->icb.extLocation.logicalBlockNum;
        if (read_offset(fd, disc, fe, (loff_t)location * disc->blocksize, 2048, 1) < 0) {
            udf_fprintf(stdout, "Error: Read FE offset failed\n");
            abort();
        }
        read_fid_by_fe(fd, disc, dir, fe);
        fprintf(stdout, "Info: Current travel path: %s\n", dir->path);
        traversal_dir(fd, disc, dir);
    }
    parent->numfiles = count;
}

static void read_pspace(int fd, udf_image *disc, Directory *dir)
{
    int id;
    long_ad *ad;
    uint32_t location, length;
    struct fileEntry *fe = (struct fileEntry *)dir->fe;

    if (disc->udf_lvd[0])
        id = 0;
    else if (disc->udf_lvd[1])
        id = 1;
    else
        return;

    ad = (long_ad *)disc->udf_lvd[id]->logicalVolContentsUse;
    length = le32_to_cpu(ad->extLength) & EXT_LENGTH_MASK;
    // Warning: Why 4096 ???
    location = le32_to_cpu(disc->udf_pd[id]->partitionStartingLocation) + disc->udf_fsd->rootDirectoryICB.extLocation.logicalBlockNum;
    // find root fe and root fid
    if (read_offset(fd, disc, fe, (loff_t)location * disc->blocksize, 2048, 1) < 0) {
        free(disc->udf_fsd);
        disc->udf_fsd = NULL;
        return;
    }

    read_fid_by_fe(fd, disc, dir, fe);
    strcpy(dir->name, "/");
    strcpy(dir->path, "/");
    fprintf(stdout, "Info: Current travel path: %s\n", dir->path);
    dir->children = (Directory **)calloc(INCREASE_CHILD_COUNT, sizeof (Directory *));
    dir->c_capcity = INCREASE_CHILD_COUNT;
    traversal_dir(fd, disc, dir);
}

static void scan_lvis(int fd, udf_image *disc)
{
    uint32_t location, length;
    uint32_t next_location, next_length;
    uint16_t type;
    size_t lvid_length;
    unsigned char buffer[512];
    struct udf_extent *ext;
    struct logicalVolIntegrityDesc *lvid;
    tag *descTag;
    int id;
    int scanned;

    if (disc->udf_lvd[0])
        id = 0;
    else if (disc->udf_lvd[1])
        id = 1;
    else
        return;

    location = le32_to_cpu(disc->udf_lvd[id]->integritySeqExt.extLocation);
    length = le32_to_cpu(disc->udf_lvd[id]->integritySeqExt.extLength) & EXT_LENGTH_MASK;

    scanned = 0;

    while (location && length) {
        if (length > 256*disc->blocksize) {
            udf_fprintf(stdout, "Warning: Logical Volume Integrity Descriptor Sequence is too big (%"PRIu32")\n", length);
            break;
        }

        if (read_offset(fd, disc, &buffer, (loff_t)location * disc->blocksize, sizeof(buffer), 1) < 0)
            return;

        descTag = (tag *)&buffer;
        type = le16_to_cpu(descTag->tagIdent);
        if (type == 0)
            break;

        if (le32_to_cpu(descTag->tagLocation) != location) {
            udf_fprintf(stdout, "Warning: Incorrect Logical Volume Integrity Descriptor\n");
            break;
        }

        if (type == TAG_IDENT_TD)
            break;

        if (type != TAG_IDENT_LVID) {
            udf_fprintf(stdout, "Warning: Incorrect Logical Volume Integrity Descriptor\n");
            break;
        }

        lvid = (struct logicalVolIntegrityDesc *)buffer;
        if (le32_to_cpu(lvid->numOfPartitions) > 32) {
            udf_fprintf(stdout, "Warning: Too many partitions (%"PRIu32") in Logical Volume Integrity Descriptor, stopping scanning\n", le32_to_cpu(lvid->numOfPartitions));
            break;
        }
        if (le32_to_cpu(lvid->lengthOfImpUse) > 32*disc->blocksize) {
            udf_fprintf(stdout, "Warning: Logical Volume Integrity Descriptor Implementation Use is too big (%"PRIu32"), stopping scanning\n", le32_to_cpu(lvid->lengthOfImpUse));
            break;
        }

        lvid_length = sizeof(*lvid) + le32_to_cpu(lvid->numOfPartitions) * 2 * sizeof(uint32_t) + le32_to_cpu(lvid->lengthOfImpUse);
        if (lvid_length > length) {
            udf_fprintf(stdout, "Warning: Incorrect Logical Volume Integrity Descriptor\n");
            break;
        }

        lvid = malloc(lvid_length);
        if (!lvid) {
            udf_fprintf(stdout, "Error: malloc failed: %s\n", strerror(errno));
            break;
        }

        if (lvid_length <= sizeof(buffer))
            memcpy(lvid, &buffer, lvid_length);
        else {
            memcpy(lvid, &buffer, sizeof(buffer));
            if (read_nointr(fd, (uint8_t *)lvid + sizeof(buffer), lvid_length - sizeof(buffer)) != (ssize_t)(lvid_length - sizeof(buffer))) {
                udf_fprintf(stdout, "Warning: read failed: %s\n", strerror(errno ? errno : EIO));
                free(lvid);
                break;
            }
        }

        disc->udf_lvid = lvid;

        next_location = le32_to_cpu(lvid->nextIntegrityExt.extLocation);
        next_length = le32_to_cpu(lvid->nextIntegrityExt.extLength) & EXT_LENGTH_MASK;

        if (next_length && next_location <= location) {
            udf_fprintf(stdout, "Warning: Next Logical Volume Integrity is not on higher block number, ignoring it\n");
            next_length = 0;
        }

        if (next_location && next_length) {
            location = next_location;
            length = next_length;
        } else if (length > disc->blocksize) {
            ++location;
            length -= disc->blocksize;
        } else {
            length = 0;
        }

        if (length > 0 && ++scanned >= 1000) {
            udf_fprintf(stdout, "Warning: Too many Logical Volume Integrity Descriptors, stopping scanning\n");
            break;
        }
    }
}

static void parse_lvidiu(udf_image *disc)
{
    struct logicalVolIntegrityDescImpUse *lvidiu;

    if (!disc->udf_lvid || disc->udf_lvid->lengthOfImpUse < sizeof(*lvidiu)) {
        udf_fprintf(stdout, "Warning: Logical Volume Integrity Descriptor Implementation Use not found\n");
        return;
    }

    lvidiu = (struct logicalVolIntegrityDescImpUse *)&(disc->udf_lvid->data[le32_to_cpu(disc->udf_lvid->numOfPartitions) * 2 * sizeof(uint32_t)]);
    disc->num_files = le32_to_cpu(lvidiu->numFiles);
    disc->num_dirs = le32_to_cpu(lvidiu->numDirs);

    /* Fields minUDFReadRev and minUDFWriteRev are defined since UDF 1.02 */
    if (disc->udf_rev >= 0x0102)
        disc->udf_rev = le16_to_cpu(lvidiu->minUDFReadRev);
}

int read_disc(int fd, udf_image *disc, Directory *dir)
{
    assert(disc);
    assert(dir);

    if (detect_udf(fd, disc) < 0)
        return -1;

    scan_mvds(fd, disc);
    scan_rvds(fd, disc);

    if (!disc->udf_anchor[1] && !disc->udf_anchor[2])
        udf_fprintf(stdout, "Warning: Second and third Anchor Volume Descriptor Pointer not found\n");

    if (!disc->udf_pvd[0] && !disc->udf_pvd[1])
        udf_fprintf(stdout, "Warning: Primary Volume Descriptor not found\n");
    if (!disc->udf_pd[0] && !disc->udf_pd[1])
        udf_fprintf(stdout, "Warning: Partition Descriptor not found\n");
    if (!disc->udf_lvd[0] && !disc->udf_lvd[1])
        udf_fprintf(stdout, "Warning: Logical Volume Descriptor not found\n");

    if (!disc->udf_usd[0] && !disc->udf_usd[1])
        udf_fprintf(stdout, "Warning: Unallocated Space Descriptor not found\n");
    if (!disc->udf_iuvd[0] && !disc->udf_iuvd[1])
        udf_fprintf(stdout, "Warning: Implementation Use Volume Descriptor not found\n");
    if (!disc->udf_td[0] && !disc->udf_td[1])
        udf_fprintf(stdout, "Warning: Terminating Descriptor not found\n");

    scan_lvis(fd, disc);

    if (!disc->udf_lvid)
        udf_fprintf(stdout, "Warning: Logical Volume Integrity Descriptor not found\n");

    parse_lvidiu(disc);
    read_fsd(fd, disc);

    // warning: just support UDF 1.02 now
    if (disc->udf_rev != 0x0102) {
        udf_fprintf(stdout, "Error: UDF version %x not equal 0x0102\n", disc->udf_rev);
        return -2;
    }

    /* read all fe and fid */
    read_pspace(fd, disc, dir);
    return 0;
}

void clear_dir(Directory *dir)
{
    assert(dir);

    uint32_t i;

    if (dir->fid_data) {
        free(dir->fid_data);
        dir->fid_data = NULL;
    }
    for (i = 0; i < dir->c_len; i++) {
        if (dir->children[i])
            clear_dir(dir->children[i]);
    }
    if (dir->children) {
        free(dir->children);
        dir->children = NULL;
    }
    if (dir) {
        free(dir);
        dir = NULL;
    }
}
