⛏️ index : haiku.git

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


/*! \file kernel_interface.cpp */


#include <Drivers.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <KernelExport.h>
#include <util/kernel_cpp.h>

#include <io_requests.h>

#include "Icb.h"
#include "Recognition.h"
#include "Utils.h"
#include "Volume.h"


#undef TRACE
#undef TRACE_ERROR
//#define UDF_KERNEL_INTERFACE_DEBUG
#ifdef UDF_KERNEL_INTERFACE_DEBUG
#	define TRACE(x)			dprintf x
#	define TRACE_ERROR(x)	dprintf x
#else
#	define TRACE(x)			/* nothing */
#	define TRACE_ERROR(x)	dprintf x
#endif

extern fs_volume_ops gUDFVolumeOps;
extern fs_vnode_ops gUDFVnodeOps;


struct identify_cookie {
	struct logical_volume_descriptor logical_volume_descriptor;
};


//	#pragma mark - io callbacks


static status_t
iterative_io_get_vecs_hook(void *cookie, io_request *request, off_t offset,
	size_t size, struct file_io_vec *vecs, size_t *count)
{
	Icb *icb = (Icb *)cookie;
	return file_map_translate(icb->FileMap(), offset, size, vecs, count,
		icb->GetVolume()->BlockSize());
}


static status_t
iterative_io_finished_hook(void *cookie, io_request *request, status_t status,
	bool partialTransfer, size_t bytesTransferred)
{
	// nothing to do
	return B_OK;
}


//	#pragma mark - fs_volume_ops fuctions


static float
udf_identify_partition(int fd, partition_data *partition, void **_cookie)
{
	TRACE(("udf_identify_partition: fd = %d, id = %ld, offset = %lld, size = %lld "
		"content_size = %lld, block_size = %lu\n", fd, partition->id,
		partition->offset, partition->size, partition->content_size,
		partition->block_size));

	primary_volume_descriptor primaryVolumeDescriptor;
	logical_volume_descriptor logicalVolumeDescriptor;
	partition_descriptor partitionDescriptors[kMaxPartitionDescriptors];
	uint8 descriptorCount = kMaxPartitionDescriptors;
	uint32 blockShift;
	status_t error = udf_recognize(fd, partition->offset, partition->size,
		partition->block_size, blockShift, primaryVolumeDescriptor,
		logicalVolumeDescriptor, partitionDescriptors, descriptorCount);
	if (error != B_OK)
		return -1;

	identify_cookie *cookie = new(std::nothrow) identify_cookie;
	if (cookie == NULL)
		return -1;

	cookie->logical_volume_descriptor = logicalVolumeDescriptor;
	*_cookie = cookie;
	return 0.8f;
}


static status_t
udf_scan_partition(int fd, partition_data *partition, void *_cookie)
{
	TRACE(("udf_scan_partition: fd = %d\n", fd));
	identify_cookie *cookie = (identify_cookie *)_cookie;
	logical_volume_descriptor &volumeDescriptor
		= cookie->logical_volume_descriptor;

	partition->status = B_PARTITION_VALID;
	partition->flags |= B_PARTITION_FILE_SYSTEM;
	partition->content_size = partition->size;
		// TODO: not actually correct
	partition->block_size = volumeDescriptor.logical_block_size();

	UdfString name(volumeDescriptor.logical_volume_identifier());
	partition->content_name = strdup(name.Utf8());
	if (partition->content_name == NULL)
		return B_NO_MEMORY;

	return B_OK;
}


static void
udf_free_identify_partition_cookie(partition_data *partition, void *cookie)
{
	delete (identify_cookie *)cookie;
}


static status_t
udf_unmount(fs_volume *_volume)
{
	TRACE(("udb_unmount: _volume = %p\n", _volume));
	Volume *volume = (Volume *)_volume->private_volume;
	delete volume;
	return B_OK;
}


static status_t
udf_read_fs_stat(fs_volume *_volume, struct fs_info *info)
{
	TRACE(("udf_read_fs_stat: _volume = %p, info = %p\n", _volume, info));

	Volume *volume = (Volume *)_volume->private_volume;

	// File system flags.
	info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY;

	info->io_size = 65536;
		// whatever is appropriate here? Just use the same value as BFS (and iso9660) for now

	info->block_size = volume->BlockSize();
	info->total_blocks = volume->Length();
	info->free_blocks = 0;

	// Volume name
	sprintf(info->volume_name, "%s", volume->Name());

	// File system name
	strcpy(info->fsh_name, "udf");

	return B_OK;
}


static status_t
udf_get_vnode(fs_volume *_volume, ino_t id, fs_vnode *node, int *_type,
	uint32 *_flags, bool reenter)
{
	TRACE(("udf_get_vnode: _volume = %p, _node = %p, reenter = %s\n",
		_volume, _node, (reenter ? "true" : "false")));

	Volume *volume = (Volume *)_volume->private_volume;

	// Convert the given vnode id to an address, and create
	// and return a corresponding Icb object for it.
	TRACE(("udf_get_vnode: id = %lld, blockSize = %lu\n", id,
		volume->BlockSize()));

	Icb *icb = new(std::nothrow) Icb(volume,
		to_long_address(id, volume->BlockSize()));
	if (icb == NULL)
		return B_NO_MEMORY;

	if (icb->InitCheck() == B_OK) {
		node->private_node = icb;
		node->ops = &gUDFVnodeOps;
		*_type = icb->Mode();
		*_flags = 0;
		return B_OK;
	}

	TRACE_ERROR(("udf_get_vnode: InitCheck failed\n"));
	delete icb;
	return B_ERROR;
}


//	#pragma mark - fs_vnode_ops functions


static status_t
udf_lookup(fs_volume *_volume, fs_vnode *_directory, const char *file,
	ino_t *vnodeID)
{
	TRACE(("udf_lookup: _directory = %p, filename = %s\n", _directory, file));

	Volume *volume = (Volume *)_volume->private_volume;
	Icb *dir = (Icb *)_directory->private_node;
	Icb *node = NULL;

	status_t status = B_OK;

	if (strcmp(file, ".") == 0) {
		TRACE(("udf_lookup: file = ./\n"));
		*vnodeID = dir->Id();
		status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&node);
		if (status != B_OK)
			return B_ENTRY_NOT_FOUND;
	} else {
		status = dir->Find(file, vnodeID);
		if (status != B_OK)
			return status;

		Icb *icb;
		status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&icb);
		if (status != B_OK)
			return B_ENTRY_NOT_FOUND;
	}
	TRACE(("udf_lookup: vnodeId = %lld found!\n", *vnodeID));

	return B_OK;
}


static status_t
udf_put_vnode(fs_volume *volume, fs_vnode *node, bool reenter)
{
	TRACE(("udf_put_vnode: volume = %p, node = %p\n", volume, node));
// No debug-to-file in release_vnode; can cause a deadlock in
// rare circumstances.
#if !DEBUG_TO_FILE
	DEBUG_INIT_ETC(NULL, ("node: %p", node));
#endif
	Icb *icb = (Icb *)node->private_node;
	delete icb;
#if !DEBUG_TO_FILE
	RETURN(B_OK);
#else
	return B_OK;
#endif
}


static status_t
udf_remove_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
{
	TRACE(("udf_remove_vnode: _volume = %p, _node = %p\n", _volume, _node));
	return B_ERROR;
}


static status_t
udf_read_stat(fs_volume *_volume, fs_vnode *node, struct stat *stat)
{
	TRACE(("udf_read_stat: _volume = %p, node = %p\n", _volume, node));

	if (!_volume || !node || !stat)
		return B_BAD_VALUE;

	Volume *volume = (Volume *)_volume->private_volume;
	Icb *icb = (Icb *)node->private_node;

	stat->st_dev = volume->ID();
	stat->st_ino = icb->Id();
	stat->st_nlink = icb->FileLinkCount();
	stat->st_blksize = volume->BlockSize();

	TRACE(("udf_read_stat: st_dev = %ld, st_ino = %lld, st_blksize = %d\n",
		stat->st_dev, stat->st_ino, stat->st_blksize));

	stat->st_uid = icb->Uid();
	stat->st_gid = icb->Gid();

	stat->st_mode = icb->Mode();
	stat->st_size = icb->Length();
	stat->st_blocks = (stat->st_size + 511) / 512;

	// File times. For now, treat the modification time as creation
	// time as well, since true creation time is an optional extended
	// attribute, and supporting EAs is going to be a PITA. ;-)
	icb->GetAccessTime(stat->st_atim);
	icb->GetModificationTime(stat->st_mtim);
	icb->GetModificationTime(stat->st_ctim);
	icb->GetModificationTime(stat->st_crtim);
	//icb->GetChangeTime(stat->st_ctim);
	//icb->GetCreationTime(stat->st_crtim);

	TRACE(("udf_read_stat: mode = 0x%x, st_ino: %lld\n", stat->st_mode,
		stat->st_ino));

	return B_OK;
}


static status_t
udf_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
{
	TRACE(("udf_open: _volume = %p, _node = %p\n", _volume, _node));
	return B_OK;
}


static status_t
udf_close(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
	TRACE(("udf_close: _volume = %p, _node = %p\n", _volume, _node));
	return B_OK;
}


static status_t
udf_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
	TRACE(("udf_free_cookie: _volume = %p, _node = %p\n", _volume, _node));
	return B_OK;
}


static status_t
udf_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
{
	TRACE(("udf_access: _volume = %p, _node = %p\n", _volume, _node));
	return B_OK;
}


static status_t
udf_read(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
	void *buffer, size_t *length)
{
	TRACE(("udf_read: ID = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", "
			"length = %lu\n",
		((Volume *)volume->private_volume)->ID(), pos, *length));

	Icb *icb = (Icb *)vnode->private_node;
	DEBUG_INIT_ETC("udf_read", ("ID = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", "
			"length = %lu",
		((Volume *)volume->private_volume)->ID(), pos, *length));

//	if (!inode->HasUserAccessableStream()) {
//		*_length = 0;
//		RETURN_ERROR(B_BAD_VALUE);
//	}

	RETURN(icb->Read(pos, buffer, length));
}


static status_t
udf_io(fs_volume *volume, fs_vnode *vnode, void *cookie, io_request *request)
{
	if (io_request_is_write(request)) {
		notify_io_request(request, B_READ_ONLY_DEVICE);
		return B_READ_ONLY_DEVICE;
	}

	Icb *icb = (Icb *)vnode->private_node;
	if (icb->FileCache() == NULL) {
		notify_io_request(request, B_BAD_VALUE);
		return B_BAD_VALUE;
	}

	return do_iterative_fd_io(((Volume *)volume->private_volume)->Device(),
		request, iterative_io_get_vecs_hook, iterative_io_finished_hook, icb);
}


static status_t
udf_get_file_map(fs_volume *_volume, fs_vnode *vnode, off_t offset, size_t size,
	struct file_io_vec *vecs, size_t *count)
{
	Icb *icb = (Icb *)vnode->private_node;
	return icb->GetFileMap(offset, size, vecs, count);
}


static status_t
udf_open_dir(fs_volume *volume, fs_vnode *vnode, void **cookie)
{
	TRACE(("udf_open_dir: volume = %p, vnode = %p\n", volume, vnode));
	DEBUG_INIT_ETC("udf_open_dir", ("ID = %" B_PRIdDEV,
		((Volume *)volume->private_volume)->ID()));

	if (!volume || !vnode || !cookie)
		RETURN(B_BAD_VALUE);

	Icb *dir = (Icb *)vnode->private_node;

	if (!dir->IsDirectory()) {
		TRACE_ERROR(("udf_open_dir: given Icb is not a directory (type: %d)\n",
			dir->Type()));
		return B_NOT_A_DIRECTORY;
	}

	DirectoryIterator *iterator = NULL;
	status_t status = dir->GetDirectoryIterator(&iterator);
	if (status != B_OK) {
		TRACE_ERROR(("udf_open_dir: error getting directory iterator: 0x%"
			B_PRIx32 ", `%s'\n", status, strerror(status)));
		return status;
	}
	*cookie = (void *)iterator;
	TRACE(("udf_open_dir: *cookie = %p\n", *cookie));

	return B_OK;
}


static status_t
udf_close_dir(fs_volume *_volume, fs_vnode *node, void *_cookie)
{
	TRACE(("udf_close_dir: _volume = %p, node = %p\n", _volume, node));
	return B_OK;
}


static status_t
udf_free_dir_cookie(fs_volume *_volume, fs_vnode *node, void *_cookie)
{
	TRACE(("udf_free_dir_cookie: _volume = %p, node = %p\n", _volume, node));
	return B_OK;
}


static status_t
udf_read_dir(fs_volume *_volume, fs_vnode *vnode, void *cookie,
	struct dirent *dirent, size_t bufferSize, uint32 *_num)
{
	TRACE(("udf_read_dir: _volume = %p, vnode = %p, bufferSize = %ld\n",
		_volume, vnode, bufferSize));

	if (!_volume || !vnode || !cookie || !_num
			|| bufferSize < sizeof(struct dirent)) {
		return B_BAD_VALUE;
	}

	Volume *volume = (Volume *)_volume->private_volume;
	Icb *dir = (Icb *)vnode->private_node;
	DirectoryIterator *iterator = (DirectoryIterator *)cookie;

	DEBUG_INIT_ETC("udf_read_dir", ("ID = %" B_PRIdDEV , volume->ID()));

	if (dir != iterator->Parent()) {
		TRACE_ERROR(("udf_read_dir: Icb does not match parent Icb of given "
			"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
		return B_BAD_VALUE;
	}

	uint32 nameLength = bufferSize - offsetof(struct dirent, d_name);
	ino_t id;
	status_t status = iterator->GetNextEntry(dirent->d_name, &nameLength, &id);
	if (!status) {
		TRACE(("udf_read_dir: dirent->d_name = %s, length = %ld\n", dirent->d_name, nameLength));
		*_num = 1;
		dirent->d_dev = volume->ID();
		dirent->d_ino = id;
		dirent->d_reclen = offsetof(struct dirent, d_name) + nameLength + 1;
	} else {
		*_num = 0;
		// Clear the status for end of directory
		if (status == B_ENTRY_NOT_FOUND)
			status = B_OK;
	}

	RETURN(status);
}


status_t
udf_rewind_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
	TRACE(("udf_rewind_dir: volume = %p, vnode = %p, cookie = %p\n",
		volume, vnode, cookie));
	DEBUG_INIT_ETC("udf_rewind_dir", ("ID = %" B_PRIdDEV,
		((Volume *)volume->private_volume)->ID()));

	if (!volume || !vnode || !cookie)
		RETURN(B_BAD_VALUE);

	Icb *dir = (Icb *)vnode->private_node;
	DirectoryIterator *iterator = (DirectoryIterator *)cookie;

	if (dir != iterator->Parent()) {
		PRINT(("udf_rewind_dir: icb does not match parent Icb of given "
			"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
		return B_BAD_VALUE;
	}

	iterator->Rewind();

	return B_OK;
}


//	#pragma mark -


/*! \brief mount

	\todo I'm using the B_GET_GEOMETRY ioctl() to find out where the end of the
	      partition is. This won't work for handling multi-session semantics correctly.
	      To support them correctly in R5 I need either:
	      - A way to get the proper info (best)
	      - To ignore trying to find anchor volume descriptor pointers at
	        locations N-256 and N. (acceptable, perhaps, but not really correct)
	      Either way we should address this problem properly for Haiku::R1.
	\todo Looks like B_GET_GEOMETRY doesn't work on non-device files (i.e.
	      disk images), so I need to use stat or something else for those
	      instances.
*/
static status_t
udf_mount(fs_volume *_volume, const char *_device, uint32 flags,
	const char *args, ino_t *_rootVnodeID)
{
	TRACE(("udf_mount: device = %s\n", _device));
	status_t status = B_OK;
	Volume *volume = NULL;
	off_t deviceOffset = 0;
	off_t numBlock = 0;
	partition_info info;
	device_geometry geometry;

	// Here we need to figure out the length of the device, and if we're
	// attempting to open a multisession volume, we need to figure out the
	// offset into the raw disk at which the volume begins, then open
	// the raw volume itself instead of the fake partition device the
	// kernel gives us, since multisession UDF volumes are allowed to access
	// the data in their own partition, as well as the data in any partitions
	// that precede them physically on the disc.
	int device = open(_device, O_RDONLY);
	status = device < B_OK ? device : B_OK;
	if (!status) {
		// First try to treat the device like a special partition device. If that's
		// what we have, then we can use the partition_info data to figure out the
		// name of the raw device (which we'll open instead), the offset into the
		// raw device at which the volume of interest will begin, and the total
		// length from the beginning of the raw device that we're allowed to access.
		//
		// If that fails, then we try to treat the device as an actual raw device,
		// and see if we can get the device size with B_GET_GEOMETRY syscall, since
		// stat()ing a raw device appears to not work.
		//
		// Finally, if that also fails, we're probably stuck with trying to mount
		// a regular file, so we just stat() it to get the device size.
		//
		// If that fails, you're just SOL.

		if (ioctl(device, B_GET_PARTITION_INFO, &info, sizeof(partition_info)) == 0) {
			TRACE(("partition_info:\n"));
			TRACE(("\toffset:             %lld\n", info.offset));
			TRACE(("\tsize:               %lld\n", info.size));
			TRACE(("\tlogical_block_size: %ld\n", info.logical_block_size));
			TRACE(("\tsession:            %ld\n", info.session));
			TRACE(("\tpartition:          %ld\n", info.partition));
			TRACE(("\tdevice:             `%s'\n", info.device));
			_device = info.device;
			deviceOffset = info.offset / info.logical_block_size;
			numBlock = deviceOffset + info.size / info.logical_block_size;
		} else if (ioctl(device, B_GET_GEOMETRY, &geometry, sizeof(device_geometry)) == 0) {
			TRACE(("geometry_info:\n"));
			TRACE(("\tsectors_per_track: %ld\n", geometry.sectors_per_track));
			TRACE(("\tcylinder_count:    %ld\n", geometry.cylinder_count));
			TRACE(("\thead_count:        %ld\n", geometry.head_count));
			deviceOffset = 0;
			numBlock = (off_t)geometry.sectors_per_track
				* geometry.cylinder_count * geometry.head_count;
		} else {
			struct stat stat;
			status = fstat(device, &stat) < 0 ? B_ERROR : B_OK;
			if (!status) {
				TRACE(("stat_info:\n"));
				TRACE(("\tst_size: %lld\n", stat.st_size));
				deviceOffset = 0;
				numBlock = stat.st_size / 2048;
			}
		}
		// Close the device
		close(device);
	}

	// Create and mount the volume
	volume = new(std::nothrow) Volume(_volume);
	status = volume->Mount(_device, deviceOffset, numBlock, 2048, flags);
	if (status != B_OK) {
		delete volume;
		return status;
	}

	_volume->private_volume = volume;
	_volume->ops = &gUDFVolumeOps;
	*_rootVnodeID = volume->RootIcb()->Id();

	TRACE(("udf_mount: succefully mounted the partition\n"));
	return B_OK;
}


//	#pragma mark -


static status_t
udf_std_ops(int32 op, ...)
{
	switch (op) {
		case B_MODULE_INIT:
			init_entities();
			return B_OK;
		case B_MODULE_UNINIT:
			return B_OK;
		default:
			return B_ERROR;
	}
}

fs_volume_ops gUDFVolumeOps = {
	&udf_unmount,
	&udf_read_fs_stat,
	NULL,	// write_fs_stat
	NULL,	// sync
	&udf_get_vnode,

	/* index directory & index operations */
	NULL,	// open_index_dir
	NULL,	// close_index_dir
	NULL,	// free_index_dir_cookie
	NULL,	// read_index_dir
	NULL,	// rewind_index_dir
	NULL,	// create_index
	NULL,	// remove_index
	NULL,	// read_index_stat

	/* query operations */
	NULL,	// open_query
	NULL,	// close_query
	NULL,	// free_query_cookie
	NULL,	// read_query
	NULL,	// rewind_query

	/* support for FS layers */
	NULL,	// create_sub_vnode
	NULL,	// delete_sub_vnode
};

fs_vnode_ops gUDFVnodeOps = {
	/* vnode operatoins */
	&udf_lookup,
	NULL,	// get_vnode_name
	&udf_put_vnode,
	&udf_remove_vnode,

	/* VM file access */
	NULL,	// can_page
	NULL,	// read_pages
	NULL,	// write_pages

	/* asynchronous I/O */
	&udf_io,
	NULL,	// cancel_io()

	/* cache file access */
	&udf_get_file_map,

	/* common operations */
	NULL,	// ioctl
	NULL,	// set_flags
	NULL,	// select
	NULL,	// deselect
	NULL,	// fsync
	NULL,	// read_symlink
	NULL,	// create_symlnk
	NULL,	// link
	NULL,	// unlink
	NULL,	// rename
	&udf_access,
	&udf_read_stat,
	NULL,	// write_stat
	NULL,	// preallocate

	/* file operations */
	NULL,	// create
	&udf_open,
	&udf_close,
	&udf_free_cookie,
	&udf_read,
	NULL,	// write

	/* directory operations */
	NULL,	// create_dir
	NULL,	// remove_dir
	&udf_open_dir,
	&udf_close_dir,
	&udf_free_dir_cookie,
	&udf_read_dir,
	&udf_rewind_dir,

	/* attribue directory operations */
	NULL,	// open_attr_dir
	NULL,	// close_attr_dir
	NULL,	// free_attr_dir_cookie
	NULL,	// read_attr_dir
	NULL,	// rewind_attr_dir

	/* attribute operations */
	NULL,	// create_attr
	NULL,	// open_attr
	NULL,	// close_attr
	NULL,	// free_attr_cookie
	NULL,	// read_attr
	NULL,	// write_attr
	NULL,	// read_attr_stat
	NULL,	// write_attr_stat
	NULL,	// rename_attr
	NULL,	// remove_attr

	/* support for node and FS layers */
	NULL,	// create_special_node
	NULL	// get_super_vnode

};

static file_system_module_info sUDFFileSystem = {
	{
		"file_systems/udf" B_CURRENT_FS_API_VERSION,
		0,
		udf_std_ops,
	},

	"udf",					// short_name
	"UDF File System",		// pretty_name
	0, // DDM flags

	&udf_identify_partition,
	&udf_scan_partition,
	&udf_free_identify_partition_cookie,
	NULL,	// free_partition_content_cookie()

	&udf_mount,

	NULL,
};

module_info *modules[] = {
	(module_info *)&sUDFFileSystem,
	NULL,
};