⛏️ index : haiku.git

/*
 * Copyright 2012, Jérôme Duval, korli@users.berlios.de.
 * Copyright 2010, Michael Lotz, mmlr@mlotz.ch.
 * Copyright 2003, Tyler Dauwalder, tyler@dauwalder.net.
 * Distributed under the terms of the MIT License.
 */

#include "Icb.h"

#include "time.h"

#include "AllocationDescriptorList.h"
#include "Utils.h"
#include "Volume.h"

#include <file_cache.h>


status_t
DirectoryIterator::GetNextEntry(char *name, uint32 *length, ino_t *id)
{
	if (!id || !name || !length)
		return B_BAD_VALUE;

	TRACE(("DirectoryIterator::GetNextEntry: name = %p, length = %" B_PRIu32
		", id = %p, position = %" B_PRIdOFF ", parent length = %" B_PRIu64
		"\n", name, *length, id, fPosition, Parent()->Length()));

	status_t status = B_OK;
	if (fAtBeginning) {
		TRACE(("DirectoryIterator::GetNextEntry: .\n"));
		sprintf(name, ".");
		*length = 1;
		*id = Parent()->Id();
		fAtBeginning = false;
	} else {
		if (uint64(fPosition) >= Parent()->Length()) {
			TRACE(("DirectoryIterator::GetNextEntry: end of dir\n"));
			return B_ENTRY_NOT_FOUND;
		}

		uint8 data[kMaxFileIdSize];
		file_id_descriptor *entry = (file_id_descriptor *)data;

		uint32 block = 0;
		off_t offset = fPosition;

		size_t entryLength = kMaxFileIdSize;
		// First read in the static portion of the file id descriptor,
		// then, based on the information therein, read in the variable
		// length tail portion as well.
		status = Parent()->Read(offset, entry, &entryLength, &block);
		if (!status && entryLength >= sizeof(file_id_descriptor)
			&& entry->tag().init_check(block) == B_OK) {
			PDUMP(entry);
			offset += entry->total_length();

			if (entry->is_parent()) {
				TRACE(("DirectoryIterator::GetNextEntry: ..\n"));
				sprintf(name, "..");
				*length = 2;
			} else {
				UdfString string(entry->id(), entry->id_length());
				TRACE(("DirectoryIterator::GetNextEntry: UfdString id == `%s', "
					"length = %" B_PRIu32 "\n", string.Utf8(),
					string.Utf8Length()));
				DUMP(entry->icb());
				sprintf(name, "%s", string.Utf8());
				*length = string.Utf8Length();
			}
			*id = to_vnode_id(entry->icb());
		}

		if (!status)
			fPosition = offset;
	}

 	return status;
}


/*	\brief Rewinds the iterator to point to the first entry in the directory. */
void
DirectoryIterator::Rewind()
{
	fAtBeginning = true;
	fPosition = 0;
}


//	#pragma mark - Private methods


DirectoryIterator::DirectoryIterator(Icb *parent)
	:
	fAtBeginning(true),
	fParent(parent),
	fPosition(0)
{
}


Icb::Icb(Volume *volume, long_address address)
	:
	fVolume(volume),
	fData(volume),
	fInitStatus(B_NO_INIT),
	fId(to_vnode_id(address)),
	fPartition(address.partition()),
	fFileEntry(&fData),
	fExtendedEntry(&fData),
	fFileCache(NULL),
	fFileMap(NULL)
{
	TRACE(("Icb::Icb: volume = %p, address(block = %" B_PRIu32 ", partition = "
		"%d, length = %" B_PRIu32 ")\n", volume, address.block(),
		address.partition(), address.length()));

	if (volume == NULL) {
		fInitStatus = B_BAD_VALUE;
		return;
	}

	off_t block;
	status_t status = fVolume->MapBlock(address, &block);
	if (status == B_OK) {
		status = fData.SetTo(block);
		if (status == B_OK) {
			icb_header *header = (icb_header *)fData.Block();
			if (header->tag().id() == TAGID_FILE_ENTRY) {
				file_icb_entry *entry = (file_icb_entry *)header;
				PDUMP(entry);
				(void)entry;	// warning death
			} else if (header->tag().id() == TAGID_EXTENDED_FILE_ENTRY) {
				extended_file_icb_entry *entry
					= (extended_file_icb_entry *)header;
				PDUMP(entry);
				(void)entry;	// warning death
			} else {
				PDUMP(header);
			}
			status = header->tag().init_check(address.block());
		}
	}

	if (IsFile()) {
		fFileCache = file_cache_create(fVolume->ID(), fId, Length());
		fFileMap = file_map_create(fVolume->ID(), fId, Length());
	}

	fInitStatus = status;
	TRACE(("Icb::Icb: status = 0x%" B_PRIx32 ", `%s'\n", status,
		strerror(status)));
}


Icb::~Icb()
{
	if (fFileCache != NULL) {
		file_cache_delete(fFileCache);
		file_map_delete(fFileMap);
	}
}


status_t
Icb::GetDirectoryIterator(DirectoryIterator **iterator)
{
	status_t error = iterator ? B_OK : B_BAD_VALUE;

	if (!error) {
		*iterator = new(std::nothrow) DirectoryIterator(this);
		if (*iterator)
		 	fIteratorList.Add(*iterator);
		else
			error = B_NO_MEMORY;
	}

	return error;
}


status_t
Icb::InitCheck()
{
	return fInitStatus;
}


void
Icb::GetAccessTime(struct timespec &timespec) const
{
	timestamp ts;
	if (_Tag().id() == TAGID_EXTENDED_FILE_ENTRY)
		ts = _ExtendedEntry()->access_date_and_time();
	else
		ts = _FileEntry()->access_date_and_time();

	if (decode_time(ts, timespec) != B_OK) {
		decode_time(
			fVolume->PrimaryVolumeDescriptor()->recording_date_and_time(),
			timespec);
	}
}


void
Icb::GetModificationTime(struct timespec &timespec) const
{
	timestamp ts;
	if (_Tag().id() == TAGID_EXTENDED_FILE_ENTRY)
		ts = _ExtendedEntry()->modification_date_and_time();
	else
		ts = _FileEntry()->modification_date_and_time();

	if (decode_time(ts, timespec) != B_OK) {
		decode_time(
			fVolume->PrimaryVolumeDescriptor()->recording_date_and_time(),
			timespec);
	}
}


status_t
Icb::FindBlock(uint32 logicalBlock, off_t &block, bool &recorded)
{
	off_t pos = logicalBlock << fVolume->BlockShift();
	if (uint64(pos) >= Length()) {
		block = -1;
		return B_ERROR;
	}

	DEBUG_INIT_ETC("Icb", ("pos: %" B_PRIdOFF, pos));

	status_t status = B_OK;
	long_address extent;
	bool isEmpty = false;
	recorded = false;

	switch (_IcbTag().descriptor_flags()) {
		case ICB_DESCRIPTOR_TYPE_SHORT:
		{
			TRACE(("Icb::FindBlock: descriptor type -> short\n"));
			AllocationDescriptorList<ShortDescriptorAccessor> list(this,
				ShortDescriptorAccessor(fPartition));
			status = list.FindExtent(pos, &extent, &isEmpty);
			if (status != B_OK) {
				TRACE_ERROR(("Icb::FindBlock: error finding extent for offset "
					"%" B_PRIdOFF ". status = 0x%" B_PRIx32 " `%s'\n", pos, status,
					strerror(status)));
			}
			break;
		}

		case ICB_DESCRIPTOR_TYPE_LONG:
		{
			TRACE(("Icb::FindBlock: descriptor type -> long\n"));
			AllocationDescriptorList<LongDescriptorAccessor> list(this);
			status = list.FindExtent(pos, &extent, &isEmpty);
			if (status != B_OK) {
				TRACE_ERROR(("Icb::FindBlock: error finding extent for offset "
					"%" B_PRIdOFF ". status = 0x%" B_PRIx32 " `%s'\n", pos,
					status, strerror(status)));
			}
			break;
		}

		case ICB_DESCRIPTOR_TYPE_EXTENDED:
		{
			TRACE(("Icb::FindBlock: descriptor type -> extended\n"));
//			AllocationDescriptorList<ExtendedDescriptorAccessor> list(this, ExtendedDescriptorAccessor(0));
//			RETURN(_Read(list, pos, buffer, length, block));
			RETURN(B_ERROR);
			break;
		}

		case ICB_DESCRIPTOR_TYPE_EMBEDDED:
		{
			TRACE(("Icb::FindBlock: descriptor type: embedded\n"));
			RETURN(B_ERROR);
			break;
		}

		default:
			TRACE(("Icb::FindBlock: invalid icb descriptor flags! (flags = %d)\n",
				_IcbTag().descriptor_flags()));
			RETURN(B_BAD_VALUE);
			break;
	}

	if (status == B_OK) {
		block = extent.block();
		recorded = extent.type() == EXTENT_TYPE_RECORDED;
		TRACE(("Icb::FindBlock: block %" B_PRIdOFF "\n", block));
	}
	return status;
}


status_t
Icb::Read(off_t pos, void *buffer, size_t *length, uint32 *block)
{
	TRACE(("Icb::Read: pos = %" B_PRIdOFF ", buffer = %p, length = (%p)->%ld\n",
		pos, buffer, length, (length ? *length : 0)));

	DEBUG_INIT_ETC("Icb", ("pos: %" B_PRIdOFF " , length: %ld", pos, *length));

	if (fFileCache != NULL)
		return file_cache_read(fFileCache, NULL, pos, buffer, length);

	if (!buffer || !length || pos < 0)
		return B_BAD_VALUE;

	if (uint64(pos) >= Length()) {
		*length = 0;
		return B_OK;
	}

	switch (_IcbTag().descriptor_flags()) {
		case ICB_DESCRIPTOR_TYPE_SHORT:
		{
			TRACE(("Icb::Read: descriptor type -> short\n"));
			AllocationDescriptorList<ShortDescriptorAccessor> list(this,
				ShortDescriptorAccessor(fPartition));
			RETURN(_Read(list, pos, buffer, length, block));
			break;
		}

		case ICB_DESCRIPTOR_TYPE_LONG:
		{
			TRACE(("Icb::Read: descriptor type -> long\n"));
			AllocationDescriptorList<LongDescriptorAccessor> list(this);
			RETURN(_Read(list, pos, buffer, length, block));
			break;
		}

		case ICB_DESCRIPTOR_TYPE_EXTENDED:
		{
			TRACE(("Icb::Read: descriptor type -> extended\n"));
//			AllocationDescriptorList<ExtendedDescriptorAccessor> list(this, ExtendedDescriptorAccessor(0));
//			RETURN(_Read(list, pos, buffer, length, block));
			RETURN(B_ERROR);
			break;
		}

		case ICB_DESCRIPTOR_TYPE_EMBEDDED:
		{
			TRACE(("Icb::Read: descriptor type: embedded\n"));
			RETURN(B_ERROR);
			break;
		}

		default:
			TRACE(("Icb::Read: invalid icb descriptor flags! (flags = %d)\n",
				_IcbTag().descriptor_flags()));
			RETURN(B_BAD_VALUE);
			break;
	}
}


/*! \brief Does the dirty work of reading using the given DescriptorList object
	to access the allocation descriptors properly.
*/
template <class DescriptorList>
status_t
Icb::_Read(DescriptorList &list, off_t pos, void *_buffer, size_t *length, uint32 *block)
{
	TRACE(("Icb::_Read(): list = %p, pos = %" B_PRIdOFF ", buffer = %p, "
		"length = %ld\n", &list, pos, _buffer, (length ? *length : 0)));

	uint64 bytesLeftInFile = uint64(pos) > Length() ? 0 : Length() - pos;
	size_t bytesLeft = (*length >= bytesLeftInFile) ? bytesLeftInFile : *length;
	size_t bytesRead = 0;

	Volume *volume = GetVolume();
	status_t status = B_OK;
	uint8 *buffer = (uint8 *)_buffer;
	bool isFirstBlock = true;

	while (bytesLeft > 0) {

		TRACE(("Icb::_Read(): pos: %" B_PRIdOFF ", bytesLeft: %ld\n", pos,
			bytesLeft));
		long_address extent;
		bool isEmpty = false;
		status = list.FindExtent(pos, &extent, &isEmpty);
		if (status != B_OK) {
			TRACE_ERROR(("Icb::_Read: error finding extent for offset %"
				B_PRIdOFF ". status = 0x%" B_PRIx32 " `%s'\n", pos, status,
				strerror(status)));
			break;
		}

		TRACE(("Icb::_Read(): found extent for offset %" B_PRIdOFF ": (block: "
			"%" B_PRIu32 ", partition: %d, length: %" B_PRIu32 ", type: %d)\n",
			pos, extent.block(), extent.partition(), extent.length(),
			extent.type()));

		switch (extent.type()) {
			case EXTENT_TYPE_RECORDED:
				isEmpty = false;
				break;

			case EXTENT_TYPE_ALLOCATED:
			case EXTENT_TYPE_UNALLOCATED:
				isEmpty = true;
				break;

			default:
				TRACE_ERROR(("Icb::_Read(): Invalid extent type found: %d\n",
					extent.type()));
				status = B_ERROR;
				break;
		}

		if (status != B_OK)
			break;

		// Note the unmapped first block of the total read in
		// the block output parameter if provided
		if (isFirstBlock) {
			isFirstBlock = false;
			if (block)
				*block = extent.block();
		}

		off_t blockOffset
			= pos - off_t((pos >> volume->BlockShift()) << volume->BlockShift());

		size_t readLength = volume->BlockSize() - blockOffset;
		if (bytesLeft < readLength)
			readLength = bytesLeft;
		if (extent.length() < readLength)
			readLength = extent.length();

		TRACE(("Icb::_Read: reading block. offset = %" B_PRIdOFF
			", length: %ld\n", blockOffset, readLength));

		if (isEmpty) {
			TRACE(("Icb::_Read: reading %ld empty bytes as zeros\n",
				readLength));
			memset(buffer, 0, readLength);
		} else {
			off_t diskBlock;
			status = volume->MapBlock(extent, &diskBlock);
			if (status != B_OK) {
				TRACE_ERROR(("Icb::_Read: could not map extent\n"));
				break;
			}

			TRACE(("Icb::_Read: %ld bytes from disk block %" B_PRIdOFF " using"
				" block_cache_get_etc()\n", readLength, diskBlock));
			const uint8 *data;
			status = block_cache_get_etc(volume->BlockCache(),
				diskBlock, (const void**)&data);
			if (status != B_OK)
				break;
			memcpy(buffer, data + blockOffset, readLength);
			block_cache_put(volume->BlockCache(), diskBlock);
		}

		bytesLeft -= readLength;
		bytesRead += readLength;
		pos += readLength;
		buffer += readLength;
	}

	*length = bytesRead;

	return status;
}


status_t
Icb::GetFileMap(off_t offset, size_t size, file_io_vec *vecs, size_t *count)
{
	switch (_IcbTag().descriptor_flags()) {
		case ICB_DESCRIPTOR_TYPE_SHORT:
		{
			AllocationDescriptorList<ShortDescriptorAccessor> list(this,
				ShortDescriptorAccessor(0));
			return _GetFileMap(list, offset, size, vecs, count);
		}

		case ICB_DESCRIPTOR_TYPE_LONG:
		{
			AllocationDescriptorList<LongDescriptorAccessor> list(this);
			return _GetFileMap(list, offset, size, vecs, count);
		}

		case ICB_DESCRIPTOR_TYPE_EXTENDED:
		case ICB_DESCRIPTOR_TYPE_EMBEDDED:
		default:
		{
			// TODO: implement?
			return B_UNSUPPORTED;
		}
	}
}


template<class DescriptorList>
status_t
Icb::_GetFileMap(DescriptorList &list, off_t offset, size_t size,
	struct file_io_vec *vecs, size_t *count)
{
	size_t index = 0;
	size_t max = *count;

	while (true) {
		long_address extent;
		bool isEmpty = false;
		status_t status = list.FindExtent(offset, &extent, &isEmpty);
		if (status != B_OK)
			return status;

		switch (extent.type()) {
			case EXTENT_TYPE_RECORDED:
				isEmpty = false;
				break;

			case EXTENT_TYPE_ALLOCATED:
			case EXTENT_TYPE_UNALLOCATED:
				isEmpty = true;
				break;

			default:
				return B_ERROR;
		}

		if (isEmpty)
			vecs[index].offset = -1;
		else {
			off_t diskBlock;
			fVolume->MapBlock(extent, &diskBlock);
			vecs[index].offset = diskBlock << fVolume->BlockShift();
		}

		off_t length = extent.length();
		vecs[index].length = length;

		offset += length;
		size -= length;
		index++;

		if (index >= max || (off_t)size <= vecs[index - 1].length
			|| offset >= (off_t)Length()) {
			*count = index;
			return index >= max ? B_BUFFER_OVERFLOW : B_OK;
		}
	}

	// can never get here
	return B_ERROR;
}


status_t
Icb::Find(const char *filename, ino_t *id)
{
	TRACE(("Icb::Find: filename = `%s', id = %p\n", filename, id));

	if (!filename || !id)
		return B_BAD_VALUE;

	DirectoryIterator *i;
	status_t status = GetDirectoryIterator(&i);
	if (status != B_OK)
		return status;

	ino_t entryId;
	uint32 length = B_FILE_NAME_LENGTH;
	char name[B_FILE_NAME_LENGTH];

	bool foundIt = false;
	while (i->GetNextEntry(name, &length, &entryId) == B_OK) {
		if (strcmp(filename, name) == 0) {
			foundIt = true;
			break;
		}

		// reset overwritten length
		length = B_FILE_NAME_LENGTH;
	}

	if (foundIt)
		*id = entryId;
	else
		status = B_ENTRY_NOT_FOUND;

	return status;
}