/*
** Copyright 2003, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/


#include "RootFileSystem.h"

#include <boot/partitions.h>
#include <boot/vfs.h>
#include <boot/stdio.h>
#include <ddm_modules.h>
#include <util/kernel_cpp.h>

#include <unistd.h>
#include <string.h>

using namespace boot;

#define TRACE_PARTITIONS 0
#if TRACE_PARTITIONS
#	define TRACE(x) printf x
#else
#	define TRACE(x) ;
#endif


/* supported partition modules */

static partition_module_info *sPartitionModules[] = {
#ifdef BOOT_SUPPORT_PARTITION_AMIGA
	&gAmigaPartitionModule,
#endif
#ifdef BOOT_SUPPORT_PARTITION_INTEL
	&gIntelPartitionMapModule,
	&gIntelExtendedPartitionModule,
#endif
#ifdef BOOT_SUPPORT_PARTITION_APPLE
	&gApplePartitionModule,
#endif
};
static const int32 sNumPartitionModules = sizeof(sPartitionModules) / sizeof(partition_module_info *);

/* supported file system modules */

static file_system_module_info *sFileSystemModules[] = {
#ifdef BOOT_SUPPORT_FILE_SYSTEM_BFS
	&gBFSFileSystemModule,
#endif
#ifdef BOOT_SUPPORT_FILE_SYSTEM_AMIGA_FFS
	&gAmigaFFSFileSystemModule,
#endif
#ifdef BOOT_SUPPORT_FILE_SYSTEM_FAT
	&gFATFileSystemModule,
#endif
};
static const int32 sNumFileSystemModules = sizeof(sFileSystemModules) / sizeof(file_system_module_info *);

extern NodeList gPartitions;


namespace boot {

/** A convenience class to automatically close a
 *	file descriptor upon deconstruction.
 */

class NodeOpener {
	public:
		NodeOpener(Node *node, int mode)
		{
			fFD = open_node(node, mode);
		}

		~NodeOpener()
		{
			close(fFD);
		}

		int Descriptor() const { return fFD; }

	private:
		int		fFD;
};


//	#pragma mark -


Partition::Partition(int fd)
	:
	fParent(NULL),
	fIsFileSystem(false)
{
	memset((partition_data *)this, 0, sizeof(partition_data));
	id = (partition_id)this;

	// it's safe to close the file
	fFD = dup(fd);
}


Partition::~Partition()
{
	close(fFD);
}


ssize_t 
Partition::ReadAt(void *cookie, off_t position, void *buffer, size_t bufferSize)
{
	if (position > this->size)
		return 0;
	if (position < 0)
		return B_BAD_VALUE;

	if (position + bufferSize > this->size)
		bufferSize = this->size - position;

	return read_pos(fFD, this->offset + position, buffer, bufferSize);
}


ssize_t 
Partition::WriteAt(void *cookie, off_t position, const void *buffer, size_t bufferSize)
{
	if (position > this->size)
		return 0;
	if (position < 0)
		return B_BAD_VALUE;

	if (position + bufferSize > this->size)
		bufferSize = this->size - position;

	return write_pos(fFD, this->offset + position, buffer, bufferSize);
}


off_t
Partition::Size() const
{
	struct stat stat;
	if (fstat(fFD, &stat) == B_OK)
		return stat.st_size;

	return Node::Size();
}


int32 
Partition::Type() const
{
	struct stat stat;
	if (fstat(fFD, &stat) == B_OK)
		return stat.st_mode;

	return Node::Type();
}


Partition *
Partition::AddChild()
{
	Partition *child = new Partition(fFD);
	if (child == NULL)
		return NULL;

	child->SetParent(this);
	child_count++;
	fChildren.Add(child);

	return child;
}


status_t
Partition::Mount(Directory **_fileSystem)
{
	for (int32 i = 0; i < sNumFileSystemModules; i++) {
		file_system_module_info *module = sFileSystemModules[i];

		TRACE(("check for file_system: %s\n", module->pretty_name));

		Directory *fileSystem;
		if (module->get_file_system(this, &fileSystem) == B_OK) {
			gRoot->AddVolume(fileSystem);
			if (_fileSystem)
				*_fileSystem = fileSystem;

			fIsFileSystem = true;
			return B_OK;
		}
	}

	return B_ENTRY_NOT_FOUND;
}


status_t 
Partition::Scan(bool mountFileSystems)
{
	// scan for partitions first (recursively all eventual children as well)
	
	TRACE(("Partition::Scan()\n"));

	for (int32 i = 0; i < sNumPartitionModules; i++) {
		partition_module_info *module = sPartitionModules[i];
		void *cookie;
		NodeOpener opener(this, O_RDONLY);

		TRACE(("check for partitioning_system: %s\n", module->pretty_name));

		if (module->identify_partition(opener.Descriptor(), this, &cookie) <= 0.0)
			continue;

		status_t status = module->scan_partition(opener.Descriptor(), this, cookie);
		module->free_identify_partition_cookie(this, cookie);

		if (status == B_OK) {
			// now that we've found something, check our children
			// out as well!

			NodeIterator iterator = fChildren.Iterator();
			Partition *child = NULL;

			while ((child = (Partition *)iterator.Next()) != NULL) {
				TRACE(("*** scan child %p (start = %Ld, size = %Ld, parent = %p)!\n",
					child, child->offset, child->size, child->Parent()));

				child->Scan(mountFileSystems);

				if (!mountFileSystems || child->IsFileSystem()) {
					// move the partitions containing file systems to the partition list
					fChildren.Remove(child);
					gPartitions.Add(child);
				}
			}

			// remove all unused children (we keep only file systems)

			while ((child = (Partition *)fChildren.RemoveHead()) != NULL)
				delete child;

			return B_OK;
		}
	}

	// scan for file systems

	if (mountFileSystems)
		return Mount();

	return B_ENTRY_NOT_FOUND; 
}

}	// namespace boot


//	#pragma mark -


status_t
add_partitions_for(int fd, bool mountFileSystems)
{
	TRACE(("add_partitions_for(fd = %ld, mountFS = %s)\n", fd, mountFileSystems ? "yes" : "no"));

	Partition *partition = new Partition(fd);

	// set some magic/default values
	partition->block_size = 512;
	partition->size = partition->Size();

	// add this partition to the list of partitions, if it contains
	// or might contain a file system
	if ((partition->Scan(mountFileSystems) == B_OK && partition->IsFileSystem())
		|| (!partition->IsFileSystem() && !mountFileSystems)) {
		gPartitions.Add(partition);
		return B_OK;
	}

	delete partition;
	return B_OK;
}


status_t
add_partitions_for(Node *device, bool mountFileSystems)
{
	TRACE(("add_partitions_for(%p, mountFS = %s)\n", device, mountFileSystems ? "yes" : "no"));

	int fd = open_node(device, O_RDONLY);
	if (fd < B_OK)
		return fd;

	status_t status = add_partitions_for(fd, mountFileSystems);
	if (status < B_OK)
		printf("add_partitions_for(%d) failed: %ld\n", fd, status);

	close(fd);
	return B_OK;
}


partition_data *
create_child_partition(partition_id id, int32 index, partition_id childID)
{
	Partition &partition = *(Partition *)id;
	Partition *child = partition.AddChild();
	if (child == NULL) {
		printf("creating partition failed: no memory\n");
		return NULL;
	}

	// we cannot do anything with the child here, because it was not
	// yet initialized by the partition module.
	TRACE(("new child partition!\n"));

	return child;
}


partition_data *
get_child_partition(partition_id id, int32 index)
{
	//Partition &partition = *(Partition *)id;

	// ToDo: do we really have to implement this?
	//	The intel partition module doesn't really needs this for our mission...

	return NULL;
}


partition_data *
get_parent_partition(partition_id id)
{
	Partition &partition = *(Partition *)id;

	return partition.Parent();
}

