⛏️ index : haiku.git

/*
 * Copyright 1999, Be Incorporated.   All Rights Reserved.
 * This file may be used under the terms of the Be Sample Code License.
 *
 * Copyright 2001, pinc Software.  All Rights Reserved.
 *
 * iso9960/multi-session, 1.0.0
 */


#include <algorithm>
#include <ctype.h>

#ifndef FS_SHELL
#	include <dirent.h>
#	include <errno.h>
#	include <fcntl.h>
#	include <stdio.h>
#	include <stdlib.h>
#	include <string.h>
#	include <sys/stat.h>
#	include <time.h>
#	include <unistd.h>

#	include <KernelExport.h>
#	include <NodeMonitor.h>
#	include <fs_interface.h>
#	include <fs_cache.h>

#	include <fs_attr.h>
#	include <fs_info.h>
#	include <fs_index.h>
#	include <fs_query.h>
#	include <fs_volume.h>

#	include <util/kernel_cpp.h>

// TODO: temporary solution as long as there is no public I/O requests API
#	include <io_requests.h>
#endif

#include "iso9660.h"
#include "iso9660_identify.h"


//#define TRACE_ISO9660
#ifdef TRACE_ISO9660
#	define TRACE(x) dprintf x
#else
#	define TRACE(x) ;
#endif


struct identify_cookie {
	iso9660_info info;
};

extern fs_volume_ops gISO9660VolumeOps;
extern fs_vnode_ops gISO9660VnodeOps;


//!	fs_io() callback hook
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)
{
	iso9660_inode* node = (iso9660_inode*)cookie;

	vecs->offset = offset + ((off_t)node->startLBN[FS_DATA_FORMAT]
		* (off_t)node->volume->logicalBlkSize[FS_DATA_FORMAT]);
	vecs->length = size;

	*_count = 1;

	return B_OK;
}


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


//	#pragma mark - Scanning


static float
fs_identify_partition(int fd, partition_data* partition, void** _cookie)
{
	iso9660_info* info = new iso9660_info;

	status_t status = iso9660_fs_identify(fd, info);
	if (status != B_OK) {
		delete info;
		return -1;
	}

	*_cookie = info;
	return 0.6f;
}


static status_t
fs_scan_partition(int fd, partition_data* partition, void* _cookie)
{
	iso9660_info* info = (iso9660_info*)_cookie;

	partition->status = B_PARTITION_VALID;
	partition->flags |= B_PARTITION_FILE_SYSTEM | B_PARTITION_READ_ONLY ;
	partition->block_size = ISO_PVD_SIZE;
	partition->content_size = ISO_PVD_SIZE * info->max_blocks;
	partition->content_name = strdup(info->PreferredName());

	if (partition->content_name == NULL)
		return B_NO_MEMORY;

	return B_OK;
}


static void
fs_free_identify_partition_cookie(partition_data* partition, void* _cookie)
{
	delete (iso9660_info*)_cookie;
}


//	#pragma mark - FS hooks


static status_t
fs_mount(fs_volume* _volume, const char* device, uint32 flags,
	const char* args, ino_t* _rootID)
{
	bool allowJoliet = true;
	iso9660_volume* volume;

	// Check for a 'nojoliet' parm
	// all we check for is the existance of 'nojoliet' in the parms.
	if (args != NULL) {
		uint32 i;
		char* spot;
		char* buf = strdup(args);

		uint32 len = strlen(buf);
		// lower case the parms data
		for (i = 0; i < len + 1; i++)
			buf[i] = tolower(buf[i]);

		// look for nojoliet
		spot = strstr(buf, "nojoliet");
		if (spot != NULL)
			allowJoliet = false;

		free(buf);
	}

	// Try and mount volume as an ISO volume.
	status_t result = ISOMount(device, O_RDONLY, &volume, allowJoliet);
	if (result == B_OK) {
		*_rootID = ISO_ROOTNODE_ID;

		_volume->private_volume = volume;
		_volume->ops = &gISO9660VolumeOps;
		volume->volume = _volume;
		volume->id = _volume->id;

		result = publish_vnode(_volume, *_rootID, &volume->rootDirRec,
			&gISO9660VnodeOps,
			volume->rootDirRec.attr.stat[FS_DATA_FORMAT].st_mode, 0);
		if (result != B_OK) {
			block_cache_delete(volume->fBlockCache, false);
			free(volume);
			result = B_ERROR;
		}
	}
	return result;
}


static status_t
fs_unmount(fs_volume* _volume)
{
	status_t result = B_NO_ERROR;
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;

	TRACE(("fs_unmount - ENTER\n"));

	// Unlike in BeOS, we need to put the reference to our root node ourselves
	put_vnode(_volume, ISO_ROOTNODE_ID);

	block_cache_delete(volume->fBlockCache, false);
	close(volume->fdOfSession);
	result = close(volume->fd);

	free(volume);

	TRACE(("fs_unmount - EXIT, result is %s\n", strerror(result)));
	return result;
}


static status_t
fs_read_fs_stat(fs_volume* _volume, struct fs_info* info)
{
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;

	info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY;
	info->block_size = volume->logicalBlkSize[FS_DATA_FORMAT];
	info->io_size = 65536;
	info->total_blocks = volume->volSpaceSize[FS_DATA_FORMAT];
	info->free_blocks = 0;

	strlcpy(info->device_name, volume->devicePath, sizeof(info->device_name));
	strlcpy(info->volume_name, volume->volIDString, sizeof(info->volume_name));

	// strip trailing spaces
	int i;
	for (i = strlen(info->volume_name) - 1; i >=0 ; i--) {
		if (info->volume_name[i] != ' ')
			break;
	}

	if (i < 0)
		strcpy(info->volume_name, "UNKNOWN");
	else
		info->volume_name[i + 1] = 0;

	strcpy(info->fsh_name, "iso9660");
	return B_OK;
}


static status_t
fs_get_vnode_name(fs_volume* _volume, fs_vnode* _node, char* buffer,
	size_t bufferSize)
{
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

	strlcpy(buffer, node->name, bufferSize);
	return B_OK;
}


static status_t
fs_walk(fs_volume* _volume, fs_vnode* _base, const char* file, ino_t* _vnodeID)
{
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;
	iso9660_inode* baseNode = (iso9660_inode*)_base->private_node;
	iso9660_inode* newNode = NULL;

	TRACE(("fs_walk - looking for %s in dir file of length %d\n", file,
		(int)baseNode->dataLen[FS_DATA_FORMAT]));

	if (strcmp(file, ".") == 0)  {
		// base directory
		TRACE(("fs_walk - found \".\" file.\n"));
		*_vnodeID = baseNode->id;
		return get_vnode(_volume, *_vnodeID, NULL);
	} else if (strcmp(file, "..") == 0) {
		// parent directory
		TRACE(("fs_walk - found \"..\" file.\n"));
		*_vnodeID = baseNode->parID;
		return get_vnode(_volume, *_vnodeID, NULL);
	}

	// look up file in the directory
	uint32 dataLength = baseNode->dataLen[FS_DATA_FORMAT];
	status_t result = ENOENT;
	size_t totalRead = 0;
	off_t block = baseNode->startLBN[FS_DATA_FORMAT];
	bool done = false;

	while (totalRead < dataLength && !done) {
		off_t cachedBlock = block;
		char* blockData = (char*)block_cache_get(volume->fBlockCache, block);
		if (blockData == NULL)
			break;

		size_t bytesRead = 0;
		off_t blockBytesRead = 0;
		iso9660_inode node;
		int initResult;

		TRACE(("fs_walk - read buffer from disk at LBN %lld into buffer "
			"%p.\n", block, blockData));

		// Move to the next block if necessary
		// Don't go over end of buffer, if dir record sits on boundary.

		while (blockBytesRead < volume->logicalBlkSize[FS_DATA_FORMAT]
			&& totalRead + blockBytesRead < dataLength
			&& blockData[0] != 0
			&& !done) {
			initResult = InitNode(volume, &node, blockData, &bytesRead);
			TRACE(("fs_walk - InitNode returned %s, filename %s, %u bytes "
				"read\n", strerror(initResult), node.name, (unsigned)bytesRead));

			if (initResult == B_OK) {
				if ((node.flags & ISO_IS_ASSOCIATED_FILE) == 0
					&& !strcmp(node.name, file)) {
					TRACE(("fs_walk - success, found vnode at block %lld, pos "
						"%lld\n", block, blockBytesRead));

					*_vnodeID = (block << 30) + (blockBytesRead & 0xffffffff);
					TRACE(("fs_walk - New vnode id is %lld\n", *_vnodeID));

					result = get_vnode(_volume, *_vnodeID, (void**)&newNode);
					if (result == B_OK) {
						newNode->parID = baseNode->id;
						done = true;
					}
				} else {
					free(node.name);
					free(node.attr.slName);
				}
			} else {
				result = initResult;
				if (bytesRead == 0)
					done = true;
			}
			blockData += bytesRead;
			blockBytesRead += bytesRead;

			TRACE(("fs_walk - Adding %u bytes to blockBytes read (total "
				"%lld/%u).\n", (unsigned)bytesRead, blockBytesRead,
				(unsigned)baseNode->dataLen[FS_DATA_FORMAT]));
		}
		totalRead += volume->logicalBlkSize[FS_DATA_FORMAT];
		block++;

		TRACE(("fs_walk - moving to next block %lld, total read %u\n",
			block, (unsigned)totalRead));
		block_cache_put(volume->fBlockCache, cachedBlock);
	}

	TRACE(("fs_walk - EXIT, result is %s, vnid is %Lu\n",
		strerror(result), *_vnodeID));
	return result;
}


static status_t
fs_read_vnode(fs_volume* _volume, ino_t vnodeID, fs_vnode* _node,
	int* _type, uint32* _flags, bool reenter)
{
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;

	iso9660_inode* newNode = (iso9660_inode*)calloc(sizeof(iso9660_inode), 1);
	if (newNode == NULL)
		return B_NO_MEMORY;

	uint32 pos = vnodeID & 0x3fffffff;
	uint32 block = vnodeID >> 30;

	TRACE(("fs_read_vnode - block = %u, pos = %u, raw = %Lu node %p\n",
		(unsigned)block, (unsigned) pos, vnodeID, newNode));

	if (pos > volume->logicalBlkSize[FS_DATA_FORMAT]) {
		free(newNode);
		return B_BAD_VALUE;
	}

	char* data = (char*)block_cache_get(volume->fBlockCache, block);
	if (data == NULL) {
		free(newNode);
		return B_IO_ERROR;
	}

	status_t result = InitNode(volume, newNode, data + pos, NULL);
	block_cache_put(volume->fBlockCache, block);

	if (result < B_OK) {
		free(newNode);
		return result;
	}

	newNode->volume = volume;
	newNode->id = vnodeID;

	_node->private_node = newNode;
	_node->ops = &gISO9660VnodeOps;
	*_type = newNode->attr.stat[FS_DATA_FORMAT].st_mode
		& ~(S_IWUSR | S_IWGRP | S_IWOTH);
	*_flags = 0;

	if ((newNode->flags & ISO_IS_DIR) == 0) {
		newNode->cache = file_cache_create(volume->id, vnodeID,
			newNode->dataLen[FS_DATA_FORMAT]);
	}

	return B_OK;
}


static status_t
fs_release_vnode(fs_volume* /*_volume*/, fs_vnode* _node, bool /*reenter*/)
{
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

	TRACE(("fs_release_vnode - ENTER (%p)\n", node));

	if (node->id != ISO_ROOTNODE_ID) {
		free(node->name);
		free(node->attr.slName);

		if (node->cache != NULL)
			file_cache_delete(node->cache);

		free(node);
	}

	TRACE(("fs_release_vnode - EXIT\n"));
	return B_OK;
}


static status_t
fs_read_pages(fs_volume* _volume, fs_vnode* _node, void*  _cookie, off_t pos,
	const iovec* vecs, size_t count, size_t* _numBytes)
{
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

	uint32 fileSize = node->dataLen[FS_DATA_FORMAT];
	size_t bytesLeft = *_numBytes;

	if (pos >= fileSize) {
		*_numBytes = 0;
		return B_OK;
	}
	if (pos + bytesLeft > fileSize) {
		bytesLeft = fileSize - pos;
		*_numBytes = bytesLeft;
	}

	file_io_vec fileVec;
	fileVec.offset = pos + ((off_t)node->startLBN[FS_DATA_FORMAT]
		* (off_t)volume->logicalBlkSize[FS_DATA_FORMAT]);
	fileVec.length = bytesLeft;

	uint32 vecIndex = 0;
	size_t vecOffset = 0;
	return read_file_io_vec_pages(volume->fd, &fileVec, 1, vecs, count,
		&vecIndex, &vecOffset, &bytesLeft);
}


static status_t
fs_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request)
{
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

#ifndef FS_SHELL
	if (io_request_is_write(request)) {
		notify_io_request(request, B_READ_ONLY_DEVICE);
		return B_READ_ONLY_DEVICE;
	}
#endif

	if ((node->flags & ISO_IS_DIR) != 0) {
#ifndef FS_SHELL
		notify_io_request(request, B_IS_A_DIRECTORY);
#endif
		return B_IS_A_DIRECTORY;
	}

	return do_iterative_fd_io(volume->fd, request, iterative_io_get_vecs_hook,
		iterative_io_finished_hook, node);
}


static status_t
fs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* st)
{
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;
	iso9660_inode* node = (iso9660_inode*)_node->private_node;
	status_t result = B_NO_ERROR;
	time_t time;

	TRACE(("fs_read_stat - ENTER\n"));

	st->st_dev = volume->id;
	st->st_ino = node->id;
	st->st_nlink = node->attr.stat[FS_DATA_FORMAT].st_nlink;
	st->st_uid = node->attr.stat[FS_DATA_FORMAT].st_uid;
	st->st_gid = node->attr.stat[FS_DATA_FORMAT].st_gid;
	st->st_blksize = 65536;
	st->st_mode = node->attr.stat[FS_DATA_FORMAT].st_mode;

	// Same for file/dir in ISO9660
	st->st_size = node->dataLen[FS_DATA_FORMAT];
	st->st_blocks = (st->st_size + 511) / 512;
	if (ConvertRecDate(&(node->recordDate), &time) == B_NO_ERROR) {
		st->st_ctim.tv_sec = st->st_mtim.tv_sec = st->st_atim.tv_sec = time;
		st->st_ctim.tv_nsec = st->st_mtim.tv_nsec = st->st_atim.tv_nsec = 0;
	}

	TRACE(("fs_read_stat - EXIT, result is %s\n", strerror(result)));

	return result;
}


static status_t
fs_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, void** /*cookie*/)
{
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

	// Do not allow any of the write-like open modes to get by
	if ((openMode & O_RWMASK) == O_WRONLY || (openMode & O_RWMASK) == O_RDWR
		|| (openMode & O_TRUNC) != 0 || (openMode & O_CREAT) != 0)
		return EROFS;

	if ((openMode & O_DIRECTORY) != 0 && (node->flags & ISO_IS_DIR) == 0)
		return B_NOT_A_DIRECTORY;

	return B_OK;
}


static status_t
fs_read(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos,
	void* buffer, size_t* _length)
{
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

	if ((node->flags & ISO_IS_DIR) != 0)
		return EISDIR;

	return file_cache_read(node->cache, NULL, pos, buffer, _length);
}


static status_t
fs_close(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/)
{
	return B_OK;
}


static status_t
fs_free_cookie(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/)
{
	return B_OK;
}


static status_t
fs_access(fs_volume* /*_volume*/, fs_vnode* /*_node*/, int /*mode*/)
{
	return B_OK;
}


static status_t
fs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer,
	size_t* _bufferSize)
{
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

	if (!S_ISLNK(node->attr.stat[FS_DATA_FORMAT].st_mode))
		return B_BAD_VALUE;

	size_t length = strlen(node->attr.slName);

	size_t bytesToCopy = std::min(length, *_bufferSize);

	*_bufferSize = length;

	memcpy(buffer, node->attr.slName, bytesToCopy);
	return B_OK;
}


static status_t
fs_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie)
{
	iso9660_inode* node = (iso9660_inode*)_node->private_node;

	TRACE(("fs_open_dir - node is %p\n", node));

	if ((node->flags & ISO_IS_DIR) == 0)
		return B_NOT_A_DIRECTORY;

	dircookie* dirCookie = (dircookie*)malloc(sizeof(dircookie));
	if (dirCookie == NULL)
		return B_NO_MEMORY;

	dirCookie->startBlock = node->startLBN[FS_DATA_FORMAT];
	dirCookie->block = node->startLBN[FS_DATA_FORMAT];
	dirCookie->totalSize = node->dataLen[FS_DATA_FORMAT];
	dirCookie->pos = 0;
	dirCookie->id = node->id;
	*_cookie = (void*)dirCookie;

	return B_OK;
}


static status_t
fs_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie,
	struct dirent* buffer, size_t bufferSize, uint32* num)
{
	iso9660_volume* volume = (iso9660_volume*)_volume->private_volume;
	dircookie* dirCookie = (dircookie*)_cookie;

	TRACE(("fs_read_dir - ENTER\n"));

	status_t result = ISOReadDirEnt(volume, dirCookie, buffer, bufferSize);

	// If we succeeded, return 1, the number of dirents we read.
	if (result == B_OK)
		*num = 1;
	else
		*num = 0;

	// When you get to the end, don't return an error, just return
	// a zero in *num.

	if (result == B_ENTRY_NOT_FOUND)
		result = B_OK;

	TRACE(("fs_read_dir - EXIT, result is %s\n", strerror(result)));
	return result;
}


static status_t
fs_rewind_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
	dircookie* cookie = (dircookie*)_cookie;

	cookie->block = cookie->startBlock;
	cookie->pos = 0;
	return B_OK;
}


static status_t
fs_close_dir(fs_volume* _volume, fs_vnode* _node, void* cookie)
{
	return B_OK;
}


static status_t
fs_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie)
{
	free(cookie);
	return B_OK;
}


//	#pragma mark -


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


fs_volume_ops gISO9660VolumeOps = {
	&fs_unmount,
	&fs_read_fs_stat,
	NULL,
	NULL,
	&fs_read_vnode,

	/* index and index directory ops */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,

	/* query ops */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,

	/* FS layer ops */
	NULL,
	NULL,
};

fs_vnode_ops gISO9660VnodeOps = {
	&fs_walk,
	&fs_get_vnode_name,
	&fs_release_vnode,
	NULL,

	/* vm-related ops */
	NULL,
	&fs_read_pages,
	NULL,

	&fs_io,
	NULL,	// cancel_io()

	/* cache file access */
	NULL,

	/* common */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	&fs_read_link,
	NULL,
	NULL,
	NULL,
	NULL,
	&fs_access,
	&fs_read_stat,
	NULL,
	NULL,

	/* file */
	NULL,
	&fs_open,
	&fs_close,
	&fs_free_cookie,
	&fs_read,
	NULL,

	/* dir */
	NULL,
	NULL,
	&fs_open_dir,
	&fs_close_dir,
	&fs_free_dir_cookie,
	&fs_read_dir,
	&fs_rewind_dir,

	/* attribute directory ops */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,

	/* attribute ops */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,

	/* node and FS layer support */
	NULL,
	NULL,
};

static file_system_module_info sISO660FileSystem = {
	{
		"file_systems/iso9660" B_CURRENT_FS_API_VERSION,
		0,
		iso_std_ops,
	},

	"iso9660",					// short_name
	"ISO9660 File System",		// pretty_name
	0,							// DDM flags

	// scanning
	fs_identify_partition,
	fs_scan_partition,
	fs_free_identify_partition_cookie,
	NULL,	// free_partition_content_cookie()

	&fs_mount,

	/* capability querying */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,

	/* shadow partition modifications */
	NULL,

	/* writing */
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
};

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