/*
 * Copyright (c) 2012 Julia Medvedeva
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup udf
 * @{
 */
/**
 * @file udf_volume.c
 * @brief Implementation of volume recognition operations.
 */

#include <byteorder.h>
#include <block.h>
#include <libfs.h>
#include <errno.h>
#include <stdlib.h>
#include <str.h>
#include <mem.h>
#include <inttypes.h>
#include <io/log.h>
#include "udf.h"
#include "udf_volume.h"
#include "udf_osta.h"
#include "udf_cksum.h"
#include "udf_file.h"

/** Convert long_ad to absolute sector position
 *
 * Convert address sector concerning origin of partition to position
 * sector concerning origin of start of disk.
 *
 * @param instance UDF instance
 * @param long_ad  UDF long address
 *
 * @return Position of sector concerning origin of start of disk.
 *
 */
fs_index_t udf_long_ad_to_pos(udf_instance_t *instance, udf_long_ad_t *long_ad)
{
	log_msg(LOG_DEFAULT, LVL_DEBUG, "Long_Ad to Pos: "
	    "partition_num=%" PRIu16 ", partition_block=%" PRIu32,
	    FLE16(long_ad->location.partition_num),
	    FLE32(long_ad->location.lblock_num));

	return instance->partitions[
	    FLE16(long_ad->location.partition_num)].start +
	    FLE32(long_ad->location.lblock_num);
}

/** Check type and version of VRS
 *
 * Not exactly clear which values could have type and version.
 *
 * @param service_id
 * @param addr       Position sector with Volume Descriptor
 * @param vd         Returned value - Volume Descriptor.
 *
 * @return EOK on success or an error code.
 *
 */
static errno_t udf_volume_recongnition_structure_test(service_id_t service_id,
    aoff64_t addr, udf_vrs_descriptor_t *vd)
{
	return block_read_bytes_direct(service_id, addr,
	    sizeof(udf_vrs_descriptor_t), vd);
}

/** Read Volume Recognition Sequence
 *
 * It is a first udf data which we read.
 * It stars from fixed address VRS_ADDR = 32768 (bytes)
 *
 * @param service_id
 *
 * @return    EOK on success or an error code.
 */
errno_t udf_volume_recongnition(service_id_t service_id)
{
	aoff64_t addr = VRS_ADDR;
	bool nsr_found = false;

	udf_vrs_descriptor_t *vd = malloc(sizeof(udf_vrs_descriptor_t));
	if (!vd)
		return ENOMEM;

	errno_t rc = udf_volume_recongnition_structure_test(service_id, addr, vd);
	if (rc != EOK) {
		free(vd);
		return rc;
	}

	for (size_t i = 0; i < VRS_DEPTH; i++) {
		addr += sizeof(udf_vrs_descriptor_t);

		rc = udf_volume_recongnition_structure_test(service_id, addr, vd);
		if (rc != EOK) {
			free(vd);
			return rc;
		}

		/*
		 * UDF standard identifier. According to ECMA 167 2/9.1.2
		 */
		if ((str_lcmp(VRS_NSR2, (char *) vd->identifier, VRS_ID_LEN) == 0) ||
		    (str_lcmp(VRS_NSR3, (char *) vd->identifier, VRS_ID_LEN) == 0)) {
			nsr_found = true;
			log_msg(LOG_DEFAULT, LVL_DEBUG, "VRS: NSR found");
			continue;
		}

		if (str_lcmp(VRS_END, (char *) vd->identifier, VRS_ID_LEN) == 0) {
			log_msg(LOG_DEFAULT, LVL_DEBUG, "VRS: end found");
			break;
		}
	}

	free(vd);

	if (nsr_found)
		return EOK;
	else
		return EINVAL;
}

/** Convert descriptor tag fields from little-endian to current byte order
 *
 */
static void udf_prepare_tag(udf_descriptor_tag_t *tag)
{
	GET_LE16(tag->id);
	GET_LE16(tag->version);
	GET_LE16(tag->serial);
	GET_LE16(tag->descriptor_crc);
	GET_LE16(tag->descriptor_crc_length);
	GET_LE32(tag->location);
}

/** Read AVD by using one of default sector size from array
 *
 * @param service_id
 * @param avd         Returned value - Anchor Volume Descriptor
 * @param sector_size Expected sector size
 *
 * @return EOK on success or an error code.
 *
 */
static errno_t udf_get_anchor_volume_descriptor_by_ssize(service_id_t service_id,
    udf_anchor_volume_descriptor_t *avd, uint32_t sector_size)
{
	errno_t rc = block_read_bytes_direct(service_id,
	    UDF_AVDP_SECTOR * sector_size,
	    sizeof(udf_anchor_volume_descriptor_t), avd);
	if (rc != EOK)
		return rc;

	if (avd->tag.checksum != udf_tag_checksum((uint8_t *) &avd->tag))
		return EINVAL;

	// TODO: Should be tested in big-endian mode
	udf_prepare_tag(&avd->tag);

	if (avd->tag.id != UDF_TAG_AVDP)
		return EINVAL;

	GET_LE32(avd->main_extent.length);
	GET_LE32(avd->main_extent.location);
	GET_LE32(avd->reserve_extent.length);
	GET_LE32(avd->reserve_extent.location);

	return EOK;
}

/** Identification of the sector size
 *
 * We try to read Anchor Volume Descriptor by using one item from
 * sequence of default values. If we could read avd, we found sector size.
 *
 * @param service_id
 * @param avd        Returned value - Anchor Volume Descriptor
 *
 * @return EOK on success or an error code.
 *
 */
errno_t udf_get_anchor_volume_descriptor(service_id_t service_id,
    udf_anchor_volume_descriptor_t *avd)
{
	uint32_t default_sector_size[] = { 512, 1024, 2048, 4096, 8192, 0 };

	udf_instance_t *instance;
	errno_t rc = fs_instance_get(service_id, (void **) &instance);
	if (rc != EOK)
		return rc;

	if (instance->sector_size) {
		return udf_get_anchor_volume_descriptor_by_ssize(service_id, avd,
		    instance->sector_size);
	} else {
		size_t i = 0;
		while (default_sector_size[i] != 0) {
			rc = udf_get_anchor_volume_descriptor_by_ssize(service_id, avd,
			    default_sector_size[i]);
			if (rc == EOK) {
				instance->sector_size = default_sector_size[i];
				return EOK;
			}

			i++;
		}
	}

	return EINVAL;
}

/** Check on prevailing primary volume descriptor
 *
 * Some discs couldn't be rewritten and new information is identified
 * by descriptors with same data as one of already created descriptors.
 * We should find prevailing descriptor (descriptor with the highest number)
 * and delete old descriptor.
 *
 * @param pvd  Array of primary volumes descriptors
 * @param cnt  Count of items in array
 * @param desc Descriptor which could prevail over one
 *             of descriptors in array.
 *
 * @return True if desc prevails over some descriptor in array
 *
 */
static bool udf_check_prevailing_pvd(udf_primary_volume_descriptor_t *pvd,
    size_t cnt, udf_primary_volume_descriptor_t *desc)
{
	for (size_t i = 0; i < cnt; i++) {
		/*
		 * According to ECMA 167 3/8.4.3
		 * PVD, each of which has same contents of the corresponding
		 * Volume Identifier, Volume set identifier
		 * and Descriptor char set field.
		 */
		if ((memcmp((uint8_t *) pvd[i].volume_id,
		    (uint8_t *) desc->volume_id, 32) == 0) &&
		    (memcmp((uint8_t *) pvd[i].volume_set_id,
		    (uint8_t *) desc->volume_set_id, 128) == 0) &&
		    (memcmp((uint8_t *) &pvd[i].descriptor_charset,
		    (uint8_t *) &desc->descriptor_charset, 64) == 0) &&
		    (FLE32(desc->sequence_number) > FLE32(pvd[i].sequence_number))) {
			memcpy(&pvd[i], desc, sizeof(udf_primary_volume_descriptor_t));
			return true;
		}
	}

	return false;
}

/** Check on prevailing logic volume descriptor
 *
 * Some discs couldn't be rewritten and new information is identified
 * by descriptors with same data as one of already created descriptors.
 * We should find  prevailing descriptor (descriptor with the highest number)
 * and delete old descriptor.
 *
 * @param lvd  Array of logic volumes descriptors
 * @param cnt  Count of items in array
 * @param desc Descriptor which could prevail over one
 *             of descriptors in array.
 *
 * @return True if desc prevails over some descriptor in array
 *
 */
static bool udf_check_prevailing_lvd(udf_logical_volume_descriptor_t *lvd,
    size_t cnt, udf_logical_volume_descriptor_t *desc)
{
	for (size_t i = 0; i < cnt; i++) {
		/*
		 * According to ECMA 167 3/8.4.3
		 * LVD, each of which has same contents of the corresponding
		 * Logic Volume Identifier and Descriptor char set field.
		 */
		if ((memcmp((uint8_t *) lvd[i].logical_volume_id,
		    (uint8_t *) desc->logical_volume_id, 128) == 0) &&
		    (memcmp((uint8_t *) &lvd[i].charset,
		    (uint8_t *) &desc->charset, 64) == 0) &&
		    (FLE32(desc->sequence_number) > FLE32(lvd[i].sequence_number))) {
			memcpy(&lvd[i], desc, sizeof(udf_logical_volume_descriptor_t));
			return true;
		}
	}

	return false;
}

/** Check on prevailing partition descriptor
 *
 * Some discs couldn't be rewritten and new information is identified
 * by descriptors with same data as one of already created descriptors.
 * We should find  prevailing descriptor (descriptor with the highest number)
 * and delete old descriptor.
 *
 * @param pvd  Array of partition descriptors
 * @param cnt  Count of items in array
 * @param desc Descriptor which could prevail over one
 *             of descriptors in array.
 *
 * @return True if desc prevails over some descriptor in array
 *
 */
static bool udf_check_prevailing_pd(udf_partition_descriptor_t *pd, size_t cnt,
    udf_partition_descriptor_t *desc)
{
	for (size_t i = 0; i < cnt; i++) {
		/*
		 * According to ECMA 167 3/8.4.3
		 * Partition descriptors with identical Partition Number
		 */
		if ((FLE16(pd[i].number) == FLE16(desc->number)) &&
		    (FLE32(desc->sequence_number) > FLE32(pd[i].sequence_number))) {
			memcpy(&pd[i], desc, sizeof(udf_partition_descriptor_t));
			return true;
		}
	}

	return false;
}

/** Read information about virtual partition
 *
 * Fill start and length fields for partition. This function quite similar of
 * udf_read_icd. But in this we can meet only two descriptors and
 * we have to read only one allocator.
 *
 * @param instance UDF instance
 * @param pos      Position (Extended) File entry descriptor
 * @param id       Index of partition in instance::partitions array
 *
 * @return EOK on success or an error code.
 *
 */
static errno_t udf_read_virtual_partition(udf_instance_t *instance, uint32_t pos,
    uint32_t id)
{
	block_t *block = NULL;
	errno_t rc = block_get(&block, instance->service_id, pos,
	    BLOCK_FLAGS_NONE);
	if (rc != EOK)
		return rc;

	udf_descriptor_tag_t *desc = (udf_descriptor_tag_t *) (block->data);
	if (desc->checksum != udf_tag_checksum((uint8_t *) desc)) {
		block_put(block);
		return EINVAL;
	}

	/*
	 * We think that we have only one allocator. It is means that virtual
	 * partition, like physical, isn't fragmented.
	 * According to doc the type of allocator is short_ad.
	 */
	switch (FLE16(desc->id)) {
	case UDF_FILE_ENTRY:
		log_msg(LOG_DEFAULT, LVL_DEBUG, "ICB: File entry descriptor found");

		udf_file_entry_descriptor_t *fed =
		    (udf_file_entry_descriptor_t *) block->data;
		uint32_t start_alloc = FLE32(fed->ea_length) + UDF_FE_OFFSET;
		udf_short_ad_t *short_d =
		    (udf_short_ad_t *) ((uint8_t *) fed + start_alloc);
		instance->partitions[id].start = FLE32(short_d->position);
		instance->partitions[id].length = FLE32(short_d->length);
		break;

	case UDF_EFILE_ENTRY:
		log_msg(LOG_DEFAULT, LVL_DEBUG, "ICB: Extended file entry descriptor found");

		udf_extended_file_entry_descriptor_t *efed =
		    (udf_extended_file_entry_descriptor_t *) block->data;
		start_alloc = FLE32(efed->ea_length) + UDF_EFE_OFFSET;
		short_d = (udf_short_ad_t *) ((uint8_t *) efed + start_alloc);
		instance->partitions[id].start = FLE32(short_d->position);
		instance->partitions[id].length = FLE32(short_d->length);
		break;
	}

	return block_put(block);
}

/** Search partition in array of partitions
 *
 * Used only in function udf_fill_volume_info
 *
 * @param pd     Array of partitions
 * @param pd_cnt Count items in array
 * @param id     Number partition (not index) which we want to find
 *
 * @return Index of partition or (size_t) -1 if we didn't find anything
 *
 */
static size_t udf_find_partition(udf_partition_descriptor_t *pd, size_t pd_cnt,
    size_t id)
{
	for (size_t i = 0; i < pd_cnt; i++) {
		if (FLE16(pd[i].number) == id)
			return i;
	}

	return (size_t) -1;
}

/** Fill instance structures by information about partitions and logic
 *
 * @param lvd      Array of logic volumes descriptors
 * @param lvd_cnt  Count of items in lvd array
 * @param pd       Array of partition descriptors
 * @param pd_cnt   Count of items in pd array
 * @param instance UDF instance
 *
 * @return EOK on success or an error code.
 *
 */
static errno_t udf_fill_volume_info(udf_logical_volume_descriptor_t *lvd,
    size_t lvd_cnt, udf_partition_descriptor_t *pd, size_t pd_cnt,
    udf_instance_t *instance)
{
	instance->volumes = calloc(lvd_cnt, sizeof(udf_lvolume_t));
	if (instance->volumes == NULL)
		return ENOMEM;

	instance->partitions = calloc(pd_cnt, sizeof(udf_partition_t));
	if (instance->partitions == NULL) {
		free(instance->volumes);
		return ENOMEM;
	}

	instance->partition_cnt = pd_cnt;

	/*
	 * Fill information about logical volumes. We will save
	 * information about all partitions placed inside each volumes.
	 */

	size_t vir_pd_cnt = 0;
	for (size_t i = 0; i < lvd_cnt; i++) {
		instance->volumes[i].partitions =
		    calloc(FLE32(lvd[i].number_of_partitions_maps),
		    sizeof(udf_partition_t *));
		if (instance->volumes[i].partitions == NULL) {
			// FIXME: Memory leak, cleanup code missing
			return ENOMEM;
		}

		instance->volumes[i].partition_cnt = 0;
		instance->volumes[i].logical_block_size =
		    FLE32(lvd[i].logical_block_size);

		/*
		 * In theory we could have more than 1 logical volume. But now
		 * for current work of driver we will think that it single and all
		 * partitions from array pd belong to only first lvd
		 */

		uint8_t *idx = lvd[i].partition_map;
		for (size_t j = 0; j < FLE32(lvd[i].number_of_partitions_maps);
		    j++) {
			udf_type1_partition_map_t *pm1 =
			    (udf_type1_partition_map_t *) idx;

			if (pm1->partition_map_type == 1) {
				size_t pd_num = udf_find_partition(pd, pd_cnt,
				    FLE16(pm1->partition_number));
				if (pd_num == (size_t) -1) {
					// FIXME: Memory leak, cleanup code missing
					return ENOENT;
				}

				/*
				 * Fill information about physical partitions. We will save all
				 * partitions (physical and virtual) inside one array
				 * instance::partitions
				 */
				instance->partitions[j].access_type =
				    FLE32(pd[pd_num].access_type);
				instance->partitions[j].length =
				    FLE32(pd[pd_num].length);
				instance->partitions[j].number =
				    FLE16(pm1->partition_number);
				instance->partitions[j].start =
				    FLE32(pd[pd_num].starting_location);

				instance->volumes[i].partitions[
				    instance->volumes[i].partition_cnt] =
				    &instance->partitions[j];

				log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume[%" PRIun "]: partition [type %u] "
				    "found and filled", i, pm1->partition_map_type);

				instance->volumes[i].partition_cnt++;
				idx += pm1->partition_map_length;
				continue;
			}

			udf_type2_partition_map_t *pm2 =
			    (udf_type2_partition_map_t *) idx;

			if (pm2->partition_map_type == 2) {
				// TODO: check partition_ident for metadata_partition_map

				udf_metadata_partition_map_t *metadata =
				    (udf_metadata_partition_map_t *) idx;

				log_msg(LOG_DEFAULT, LVL_DEBUG, "Metadata file location=%u",
				    FLE32(metadata->metadata_fileloc));

				vir_pd_cnt++;
				instance->partitions = realloc(instance->partitions,
				    (pd_cnt + vir_pd_cnt) * sizeof(udf_partition_t));
				if (instance->partitions == NULL) {
					// FIXME: Memory leak, cleanup code missing
					return ENOMEM;
				}

				instance->partition_cnt++;

				size_t pd_num = udf_find_partition(pd, pd_cnt,
				    FLE16(metadata->partition_number));
				if (pd_num == (size_t) -1) {
					// FIXME: Memory leak, cleanup code missing
					return ENOENT;
				}

				instance->partitions[j].number =
				    FLE16(metadata->partition_number);
				errno_t rc = udf_read_virtual_partition(instance,
				    FLE32(metadata->metadata_fileloc) +
				    FLE32(pd[pd_num].starting_location), j);
				if (rc != EOK) {
					// FIXME: Memory leak, cleanup code missing
					return rc;
				}

				/* Virtual partition placed inside physical */
				instance->partitions[j].start +=
				    FLE32(pd[pd_num].starting_location);

				instance->volumes[i].partitions[
				    instance->volumes[i].partition_cnt] =
				    &instance->partitions[j];

				log_msg(LOG_DEFAULT, LVL_DEBUG, "Virtual partition: num=%d, start=%d",
				    instance->partitions[j].number,
				    instance->partitions[j].start);
				log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume[%" PRIun "]: partition [type %u] "
				    "found and filled", i, pm2->partition_map_type);

				instance->volumes[i].partition_cnt++;
				idx += metadata->partition_map_length;
				continue;
			}

			/* Not type 1 nor type 2 */
			udf_general_type_t *pm = (udf_general_type_t *) idx;

			log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume[%" PRIun "]: partition [type %u] "
			    "found and skipped", i, pm->partition_map_type);

			idx += pm->partition_map_length;
		}
	}

	return EOK;
}

/** Read volume descriptors sequence
 *
 * @param service_id
 * @param addr       UDF extent descriptor (ECMA 167 3/7.1)
 *
 * @return EOK on success or an error code.
 *
 */
errno_t udf_read_volume_descriptor_sequence(service_id_t service_id,
    udf_extent_t addr)
{
	udf_instance_t *instance;
	errno_t rc = fs_instance_get(service_id, (void **) &instance);
	if (rc != EOK)
		return rc;

	aoff64_t pos = addr.location;
	aoff64_t end = pos + (addr.length / instance->sector_size) - 1;

	if (pos == end)
		return EINVAL;

	size_t max_descriptors = ALL_UP(addr.length, instance->sector_size);

	udf_primary_volume_descriptor_t *pvd = calloc(max_descriptors,
	    sizeof(udf_primary_volume_descriptor_t));
	if (pvd == NULL)
		return ENOMEM;

	udf_logical_volume_descriptor_t *lvd = calloc(max_descriptors,
	    instance->sector_size);
	if (lvd == NULL) {
		free(pvd);
		return ENOMEM;
	}

	udf_partition_descriptor_t *pd = calloc(max_descriptors,
	    sizeof(udf_partition_descriptor_t));
	if (pd == NULL) {
		free(pvd);
		free(lvd);
		return ENOMEM;
	}

	size_t pvd_cnt = 0;
	size_t lvd_cnt = 0;
	size_t pd_cnt = 0;

	while (pos <= end) {
		block_t *block = NULL;
		rc = block_get(&block, service_id, pos, BLOCK_FLAGS_NONE);
		if (rc != EOK) {
			free(pvd);
			free(lvd);
			free(pd);
			return rc;
		}

		udf_volume_descriptor_t *vol =
		    (udf_volume_descriptor_t *) block->data;

		switch (FLE16(vol->common.tag.id)) {
			/*
			 * One sector size descriptors
			 */

		case UDF_TAG_PVD:
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume: Primary volume descriptor found");

			if (!udf_check_prevailing_pvd(pvd, pvd_cnt, &vol->volume)) {
				memcpy(&pvd[pvd_cnt], &vol->volume,
				    sizeof(udf_primary_volume_descriptor_t));
				pvd_cnt++;
			}

			pos++;
			break;

		case UDF_TAG_VDP:
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume: Volume descriptor pointer found");
			pos++;
			break;

		case UDF_TAG_IUVD:
			log_msg(LOG_DEFAULT, LVL_DEBUG,
			    "Volume: Implementation use volume descriptor found");
			pos++;
			break;

		case UDF_TAG_PD:
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume: Partition descriptor found");
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Partition number: %u, contents: '%.6s', "
			    "access type: %" PRIu32, FLE16(vol->partition.number),
			    vol->partition.contents.id, FLE32(vol->partition.access_type));
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Partition start: %" PRIu32 " (sector), "
			    "size: %" PRIu32 " (sectors)",
			    FLE32(vol->partition.starting_location),
			    FLE32(vol->partition.length));

			if (!udf_check_prevailing_pd(pd, pd_cnt, &vol->partition)) {
				memcpy(&pd[pd_cnt], &vol->partition,
				    sizeof(udf_partition_descriptor_t));
				pd_cnt++;
			}

			udf_partition_header_descriptor_t *phd =
			    (udf_partition_header_descriptor_t *) vol->partition.contents_use;
			if (FLE32(phd->unallocated_space_table.length)) {
				log_msg(LOG_DEFAULT, LVL_DEBUG,
				    "space table: length=%" PRIu32 ", pos=%" PRIu32,
				    FLE32(phd->unallocated_space_table.length),
				    FLE32(phd->unallocated_space_table.position));

				instance->space_type = SPACE_TABLE;
				instance->uaspace_start =
				    FLE32(vol->partition.starting_location) +
				    FLE32(phd->unallocated_space_table.position);
				instance->uaspace_length =
				    FLE32(phd->unallocated_space_table.length);
			}

			if (FLE32(phd->unallocated_space_bitmap.length)) {
				log_msg(LOG_DEFAULT, LVL_DEBUG,
				    "space bitmap: length=%" PRIu32 ", pos=%" PRIu32,
				    FLE32(phd->unallocated_space_bitmap.length),
				    FLE32(phd->unallocated_space_bitmap.position));

				instance->space_type = SPACE_BITMAP;
				instance->uaspace_start =
				    FLE32(vol->partition.starting_location) +
				    FLE32(phd->unallocated_space_bitmap.position);
				instance->uaspace_length =
				    FLE32(phd->unallocated_space_bitmap.length);
			}

			pos++;
			break;

			/*
			 * Relative size descriptors
			 */

		case UDF_TAG_LVD:
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume: Logical volume descriptor found");

			aoff64_t sct =
			    ALL_UP((sizeof(udf_logical_volume_descriptor_t) +
			    FLE32(vol->logical.map_table_length)),
			    sizeof(udf_common_descriptor_t));
			pos += sct;
			char tmp[130];

			udf_to_unix_name(tmp, 129,
			    (char *) vol->logical.logical_volume_id, 128,
			    &vol->logical.charset);

			log_msg(LOG_DEFAULT, LVL_DEBUG, "Logical Volume ID: '%s', "
			    "logical block size: %" PRIu32 " (bytes)", tmp,
			    FLE32(vol->logical.logical_block_size));
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Map table size: %" PRIu32 " (bytes), "
			    "number of partition maps: %" PRIu32,
			    FLE32(vol->logical.map_table_length),
			    FLE32(vol->logical.number_of_partitions_maps));

			if (!udf_check_prevailing_lvd(lvd, lvd_cnt, &vol->logical)) {
				memcpy(&lvd[lvd_cnt], &vol->logical,
				    sizeof(udf_logical_volume_descriptor_t) +
				    FLE32(vol->logical.map_table_length));
				lvd_cnt++;
			}

			break;

		case UDF_TAG_USD:
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume: Unallocated space descriptor found");

			sct = ALL_UP((sizeof(udf_unallocated_space_descriptor_t) +
			    FLE32(vol->unallocated.allocation_descriptors_num) *
			    sizeof(udf_extent_t)), sizeof(udf_common_descriptor_t));
			instance->uaspace_start = pos;
			instance->uaspace_length = sct;
			instance->uasd = (udf_unallocated_space_descriptor_t *)
			    malloc(sct * instance->sector_size);
			if (instance->uasd == NULL) {
				// FIXME: Memory leak, cleanup missing
				return ENOMEM;
			}

			memcpy(instance->uasd, block->data, instance->sector_size);
			pos += sct;
			break;

		case UDF_TAG_LVID:
			log_msg(LOG_DEFAULT, LVL_DEBUG,
			    "Volume: Logical volume integrity descriptor found");

			pos++;
			break;

		case UDF_TAG_TD:
			log_msg(LOG_DEFAULT, LVL_DEBUG, "Volume: Terminating descriptor found");

			/* Found terminating descriptor. Exiting */
			pos = end + 1;
			break;

		default:
			pos++;
		}

		rc = block_put(block);
		if (rc != EOK) {
			free(pvd);
			free(lvd);
			free(pd);
			return rc;
		}
	}

	/* Fill the instance */
	udf_fill_volume_info(lvd, lvd_cnt, pd, pd_cnt, instance);

	for (size_t i = 0; i < lvd_cnt; i++) {
		pos = udf_long_ad_to_pos(instance,
		    (udf_long_ad_t *) &lvd[i].logical_volume_conents_use);

		block_t *block = NULL;
		rc = block_get(&block, instance->service_id, pos,
		    BLOCK_FLAGS_NONE);
		if (rc != EOK) {
			// FIXME: Memory leak, cleanup missing
			return rc;
		}

		udf_descriptor_tag_t *desc = block->data;

		log_msg(LOG_DEFAULT, LVL_DEBUG, "First tag ID=%" PRIu16, desc->id);

		if (desc->checksum != udf_tag_checksum((uint8_t *) desc)) {
			// FIXME: Memory leak, cleanup missing
			return EINVAL;
		}

		udf_prepare_tag(desc);

		udf_fileset_descriptor_t *fd = block->data;
		memcpy((uint8_t *) &instance->charset,
		    (uint8_t *) &fd->fileset_charset, sizeof(fd->fileset_charset));

		instance->volumes[i].root_dir = udf_long_ad_to_pos(instance,
		    &fd->root_dir_icb);
	}

	free(pvd);
	free(lvd);
	free(pd);
	return EOK;
}

/**
 * @}
 */
