⛏️ index : haiku.git

/*
 * Copyright 1999-2001, Be Incorporated.   All Rights Reserved.
 * Copyright 2001-2020, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2024, Haiku, Inc. All rights reserved.
 * This file may be used under the terms of the Be Sample Code License.
 */

/*-
 * SPDX-License-Identifier: BSD-4-Clause
 *
 * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
 * Copyright (C) 1994, 1995, 1997 TooLs GmbH.
 * All rights reserved.
 * Original code by Paul Popelka (paulp@uts.amdahl.com) (see below).
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by TooLs GmbH.
 * 4. The name of TooLs GmbH may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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.
 */
/*-
 * Written by Paul Popelka (paulp@uts.amdahl.com)
 *
 * You can do anything you want with this software, just don't say you wrote
 * it, and don't remove this notice.
 *
 * This software is provided "as is".
 *
 * The author supplies this software to be publicly redistributed on the
 * understanding that the author is not responsible for the correct
 * functioning of this software in any circumstances and is not liable for
 * any damages caused by this software.
 *
 * October 1992
 */


#ifdef FS_SHELL
#include "sys/types.h"
#include "fssh_api_wrapper.h"
#else // !FS_SHELL
#include <dirent.h>
#include <malloc.h>
#include <new>
#include <stdlib.h>
#endif // !FS_SHELL

#ifndef FS_SHELL
#include <NodeMonitor.h>
#include <OS.h>
#include <TypeConstants.h>
#include <driver_settings.h>
#include <fs_info.h>
#include <fs_interface.h>
#include <fs_volume.h>
#include <io_requests.h>
#endif // !FS_SHELL

#if defined USER && __GNUC__ == 2
// required for fs_ops_support.h
#define alignof(type) __alignof__(type)
#endif // USER && __GNUC__ == 2
#include <fs_ops_support.h>
#ifdef FS_SHELL
#include "fssh_auto_deleter.h"
#include "syscalls.h"
#else // !FS_SHELL
#include <AutoDeleter.h>
#include <arch_vm.h>
#include <kernel.h>
#include <syscalls.h>
#include <util/AutoLock.h>
#include <vfs.h>
#endif // !FS_SHELL

// FreeBSD flag that turns on full implementation of ported code
#define _KERNEL

extern "C"
{
#include "sys/param.h"
#include "sys/buf.h"
#include "sys/clock.h"
#include "sys/conf.h"
#include "sys/iconv.h"
#include "sys/mount.h"
#include "sys/mutex.h"
#include "sys/namei.h"
#include "sys/vnode.h"

#include "fs/msdosfs/bootsect.h"
#include "fs/msdosfs/bpb.h"
#include "fs/msdosfs/denode.h"
#include "fs/msdosfs/direntry.h"
#include "fs/msdosfs/fat.h"
#include "fs/msdosfs/msdosfsmount.h"
}

#include "debug.h"
#include "dosfs.h"
#ifdef FS_SHELL
#include "fssh_defines.h"
#endif // FS_SHELL
#include "mkdos.h"
#include "support.h"
#include "vcache.h"


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);
static status_t iterative_io_finished_hook(void* cookie, io_request* request, status_t status,
	bool partialTransfer, size_t bytesTransferred);

static status_t _dosfs_sync(mount* volume, bool data = true);
static status_t _dosfs_fsync(vnode* bsdNode);
static status_t _dosfs_read_vnode(mount* bsdVolume, const ino_t id, vnode** newNode, bool createFileCache = true);

static status_t bsd_volume_init(fs_volume* fsVolume, const uint32 flags, mount** volume);
status_t bsd_volume_uninit(mount* volume);
static status_t bsd_device_init(mount* bsdVolume, const dev_t devID, const char* deviceFile,
	cdev** bsdDevice, bool* _readOnly);
status_t bsd_device_uninit(cdev* device);
static status_t dev_bsd_node_init(cdev* bsdDevice, vnode** devNode);
status_t dev_bsd_node_uninit(vnode* devNode);
static status_t fat_volume_init(vnode* devvp, mount* bsdVolume, const uint64_t fatFlags,
	const char* oemPref);
status_t fat_volume_uninit(msdosfsmount* volume);


typedef struct IdentifyCookie {
	uint32 fBytesPerSector;
	uint32 fTotalSectors;
	char fName[12];
} IdentifyCookie;

typedef struct FileCookie {
	uint32 fMode; // open mode
	u_long fLastSize; // file size at last notify_stat_changed call
	u_short fMtimeAtOpen; // inital modification time
	u_short fMdateAtOpen; // initial modification date
	bigtime_t fLastNotification; // time of last notify_stat_changed call
} FileCookie;

typedef struct DirCookie {
	uint32 fIndex; // read this entry next
} DirCookie;

typedef struct AttrCookie {
	uint32 fMode; // open mode
	int32 fType; // attribute type
#define FAT_ATTR_MIME 0x1234
} AttrCookie;


typedef CObjectDeleter<mount, status_t, &bsd_volume_uninit> StructMountDeleter;
typedef CObjectDeleter<cdev, status_t, &bsd_device_uninit> StructCdevDeleter;
typedef CObjectDeleter<vnode, status_t, &dev_bsd_node_uninit> DevVnodeDeleter;
typedef CObjectDeleter<msdosfsmount, status_t, &fat_volume_uninit> StructMsdosfsmountDeleter;


struct iconv_functions* msdosfs_iconv;


static status_t
dosfs_mount(fs_volume* volume, const char* device, uint32 flags, const char* args,
	ino_t* _rootVnodeID)
{
#ifdef FS_SHELL
	FUNCTION_START("device %" B_PRIdDEV "\n", volume->id);
#else
	FUNCTION_START("device %" B_PRIdDEV ", partition %" B_PRId32 "\n", volume->id,
		volume->partition);
#endif

	status_t status = B_OK;

	int opSyncMode = 0;
	char oemPref[11] = "";
	void* handle = load_driver_settings("dos");
	if (handle != NULL) {
		opSyncMode = strtoul(get_driver_parameter(handle, "op_sync_mode", "0", "0"), NULL, 0);
		if (opSyncMode < 0 || opSyncMode > 2)
			opSyncMode = 0;

		strlcpy(oemPref, get_driver_parameter(handle, "OEM_code_page", "", ""), 11);

		unload_driver_settings(handle);
	}

	uint64 fatFlags = 0;
	// libiconv support is implemented only for the userlandfs module
#ifdef USER
	fatFlags |= MSDOSFSMNT_KICONV;
	if (strcmp(oemPref, "") == 0)
		strlcpy(oemPref, "CP1252", 11);
#endif // USER

	// args is a command line option; dosfs doesn't use any so we can ignore it

	bool readOnly = (flags & B_MOUNT_READ_ONLY) != 0;
	if ((flags & ~B_MOUNT_READ_ONLY) != 0) {
		INFORM("unsupported mount flag(s) %" B_PRIx32 "\n", (flags & ~B_MOUNT_READ_ONLY));
		return B_UNSUPPORTED;
	}

	// Initialize the struct mount, which is an adapted FreeBSD VFS object. It is present in the
	// port because the ported BSD code relies on it.
	mount* bsdVolume;
	status = bsd_volume_init(volume, flags, &bsdVolume);
	if (status != B_OK)
		RETURN_ERROR(status);
	StructMountDeleter bsdVolumeDeleter(bsdVolume);

	// initialize a BSD-style device struct
	cdev* bsdDevice = NULL;
	status = bsd_device_init(bsdVolume, volume->id, device, &bsdDevice, &readOnly);
	if (status != B_OK)
		RETURN_ERROR(status);
	StructCdevDeleter bsdDeviceDeleter(bsdDevice);

	if (readOnly == true) {
		bsdVolume->mnt_flag |= MNT_RDONLY;
		fatFlags |= MSDOSFSMNT_RONLY;
	}

	// A shell/FUSE host system might not call dosfs_sync automatically at shutdown/reboot if the
	// user forgets to unmount a volume, so we always use op sync mode for those targets.
#ifdef FS_SHELL
	opSyncMode = 2;
#endif // FS_SHELL

	// see if we need to go into op sync mode
	switch (opSyncMode) {
		case 1:
			if (bsdDevice->si_geometry->removable == false) {
				// we're not removable, so skip op_sync
				break;
			}
			// supposed to fall through

		case 2:
			PRINT("mounted with op sync enabled\n");
			bsdVolume->mnt_flag |= MNT_SYNCHRONOUS;
			fatFlags |= MSDOSFSMNT_WAITONFAT;
			break;

		case 0:
		default:
			bsdVolume->mnt_flag |= MNT_ASYNC;
			break;
	}

	// The driver needs access to a BSD-format vnode representing the device file, which in BSD
	// would be a vnode on another volume. We manually generate a stand-in.
	vnode* devNode;
	status = dev_bsd_node_init(bsdDevice, &devNode);
	if (status != B_OK)
		RETURN_ERROR(status);
	DevVnodeDeleter devVnodeDeleter(devNode);

	// initialize the FAT private volume data
	status = fat_volume_init(devNode, bsdVolume, fatFlags, oemPref);
	if (status != B_OK)
		RETURN_ERROR(status);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	StructMsdosfsmountDeleter fatVolumeDeleter(fatVolume);

	// create caches of struct bufs for the driver to use in bread()
	rw_lock_write_lock(&devNode->v_bufobj.bo_lock.haikuRW);
	for (uint32 i = 0; i < BUF_CACHE_SIZE; ++i) {
		status = slist_insert_buf(devNode, fatVolume->pm_bpcluster);
		if (status != B_OK)
			RETURN_ERROR(status);
		status = slist_insert_buf(devNode, fatVolume->pm_fatblocksize);
		if (status != B_OK)
			RETURN_ERROR(status);
		status = slist_insert_buf(devNode, 0);
		if (status != B_OK)
			RETURN_ERROR(status);
	}
	rw_lock_write_unlock(&devNode->v_bufobj.bo_lock.haikuRW);

	volume->private_volume = bsdVolume;
	volume->ops = &gFATVolumeOps;

	// publish root vnode

	u_long dirClust = FAT32(fatVolume) == true ? fatVolume->pm_rootdirblk : MSDOSFSROOT;
	u_long dirOffset = MSDOSFSROOT_OFS;
	ino_t rootInode = DETOI(fatVolume, dirClust, dirOffset);

	status = add_to_vcache(bsdVolume, rootInode, rootInode);
	if (status != B_OK)
		RETURN_ERROR(status);

	vnode* bsdRootNode;
	status = _dosfs_read_vnode(bsdVolume, rootInode, &bsdRootNode);
	if (status != B_OK)
		RETURN_ERROR(status);
	denode* fatRootNode = reinterpret_cast<denode*>(bsdRootNode->v_data);
	ASSERT(fatRootNode->de_dirclust == dirClust && fatRootNode->de_diroffset == dirOffset);

	status = publish_vnode(volume, rootInode, bsdRootNode, &gFATVnodeOps, S_IFDIR, 0);
	if (status != B_OK)
		RETURN_ERROR(status);

	PRINT("root vnode id = %" B_PRIdINO ", @ %p\n", fatRootNode->de_inode, bsdRootNode);

	*_rootVnodeID = fatRootNode->de_inode;

#ifdef _KERNEL_MODE
	// initialize mnt_stat.f_mntonname, for use by msdosfs_integrity_error
	dev_t mountpt;
	ino_t mountino;
	vfs_get_mount_point(fatVolume->pm_dev->si_id, &mountpt, &mountino);
	vfs_entry_ref_to_path(mountpt, mountino, NULL, true, bsdVolume->mnt_stat.f_mntonname,
		B_PATH_NAME_LENGTH);
#endif // _KERNEL_MODE

	bsdVolumeDeleter.Detach();
	bsdDeviceDeleter.Detach();
	devVnodeDeleter.Detach();
	fatVolumeDeleter.Detach();

	return B_OK;
}


static float
dosfs_identify_partition(int fd, partition_data* partition, void** _cookie)
{
	FUNCTION_START("dosfs_identify_partition\n");

	// read in the boot sector
	uint8 buf[512];
	if (read_pos(fd, 0, buf, 512) != 512)
		return -1;

	FatType type;
	bool dos33;
	status_t status = check_bootsector(buf, type, dos33);
	if (status != B_OK)
		return status;

	// partially set up a msdosfsmount, enough to read the volume label from the root directory
	msdosfsmount dummyVolume;
	dummyVolume.pm_mountp = NULL;
	switch (type) {
		case fat12:
			dummyVolume.pm_fatmask = FAT12_MASK;
			break;
		case fat16:
			dummyVolume.pm_fatmask = FAT16_MASK;
			break;
		case fat32:
			dummyVolume.pm_fatmask = FAT32_MASK;
			break;
		default:
			return -1;
	}
	status = parse_bpb(&dummyVolume, reinterpret_cast<union bootsector*>(buf), dos33);
	if (status != B_OK)
		return status;
	dummyVolume.pm_BlkPerSec = dummyVolume.pm_BytesPerSec / DEV_BSIZE;
	dummyVolume.pm_rootdirsize = howmany(dummyVolume.pm_RootDirEnts * sizeof(direntry), DEV_BSIZE);
		// Will be 0 for a FAT32 volume.
	dummyVolume.pm_bpcluster
		= dummyVolume.pm_bpb.bpbSecPerClust * dummyVolume.pm_BlkPerSec * DEV_BSIZE;
	dummyVolume.pm_bnshift = ffs(DEV_BSIZE) - 1;
	dummyVolume.pm_fatblk = dummyVolume.pm_ResSectors * dummyVolume.pm_BlkPerSec;
	if (type == fat32) {
		// for FAT32, read_label depends on pm_firstcluster
		dummyVolume.pm_firstcluster
			= dummyVolume.pm_fatblk + dummyVolume.pm_FATs * dummyVolume.pm_FATsecs;
	} else {
		// for FAT12/16, parse_bpb doesn't initialize pm_rootdirblk
		dummyVolume.pm_rootdirblk
			= dummyVolume.pm_fatblk + dummyVolume.pm_FATs * dummyVolume.pm_FATsecs;
	}

	char name[LABEL_CSTRING];
	strcpy(name, "no name");
	read_label(&dummyVolume, fd, buf, name);

	IdentifyCookie* cookie = new(std::nothrow) IdentifyCookie;
	if (!cookie)
		return -1;
	cookie->fBytesPerSector = dummyVolume.pm_BytesPerSec;
	cookie->fTotalSectors = dummyVolume.pm_HugeSectors;
	strlcpy(cookie->fName, name, 12);

	*_cookie = cookie;

	return 0.8f;
}


static status_t
dosfs_scan_partition(int fd, partition_data* partition, void* _cookie)
{
	IdentifyCookie* cookie = reinterpret_cast<IdentifyCookie*>(_cookie);

	partition->status = B_PARTITION_VALID;
	partition->flags |= B_PARTITION_FILE_SYSTEM;
	partition->content_size = static_cast<off_t>(cookie->fTotalSectors) * cookie->fBytesPerSector;
	partition->block_size = cookie->fBytesPerSector;
	partition->content_name = strdup(cookie->fName);
	if (partition->content_name == NULL)
		return B_NO_MEMORY;

	return B_OK;
}


static void
dosfs_free_identify_partition_cookie(partition_data* partition, void* _cookie)
{
	delete reinterpret_cast<IdentifyCookie*>(_cookie);

	return;
}


static status_t
dosfs_unmount(fs_volume* volume)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	vnode* deviceNode = fatVolume->pm_devvp;
	cdev* bsdDevice = fatVolume->pm_dev;

#ifdef FS_SHELL
	FUNCTION_START("device %" B_PRIdDEV "\n", volume->id);
#else
	FUNCTION_START("device %" B_PRIdDEV ", partition %" B_PRId32 "\n", volume->id,
		volume->partition);
#endif

	status_t status = B_OK;
	status_t returnStatus = B_OK;

	put_vnode(volume, root_inode(fatVolume));

	MutexLocker locker(bsdVolume->mnt_mtx.haikuMutex);

	status = fat_volume_uninit(fatVolume);
	if (status != B_OK)
		returnStatus = status;

	// pseudo-BSD layer cleanup
	status = bsd_device_uninit(bsdDevice);
	if (status != B_OK)
		returnStatus = status;
	status = dev_bsd_node_uninit(deviceNode);
	if (status != B_OK)
		returnStatus = status;
	locker.Unlock();
	status = bsd_volume_uninit(bsdVolume);
	if (status != B_OK)
		returnStatus = status;

	RETURN_ERROR(returnStatus);
}


static status_t
dosfs_read_fs_stat(fs_volume* volume, struct fs_info* info)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	cdev* bsdDevice = fatVolume->pm_dev;

	FUNCTION();

	MutexLocker locker(bsdVolume->mnt_mtx.haikuMutex);

	info->flags = B_FS_IS_PERSISTENT | B_FS_HAS_MIME;
	if ((bsdVolume->mnt_flag & MNT_RDONLY) != 0)
		info->flags |= B_FS_IS_READONLY;

	if (bsdDevice->si_geometry->removable == true)
		info->flags |= B_FS_IS_REMOVABLE;

	info->block_size = fatVolume->pm_bpcluster;

	info->io_size = FAT_IO_SIZE;

	info->total_blocks = fatVolume->pm_maxcluster + 1 - 2;
		// convert from index to count and adjust for 2 reserved cluster numbers

	info->free_blocks = fatVolume->pm_freeclustercount;

	info->total_nodes = LONGLONG_MAX;

	info->free_nodes = LONGLONG_MAX;

	strlcpy(info->volume_name, fatVolume->pm_dev->si_name, sizeof(info->volume_name));

	strlcpy(info->device_name, fatVolume->pm_dev->si_device, sizeof(info->device_name));

	strlcpy(info->fsh_name, "fat", sizeof(info->fsh_name));

	return B_OK;
}


static status_t
dosfs_write_fs_stat(fs_volume* volume, const struct fs_info* info, uint32 mask)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);

	FUNCTION_START("with mask %" B_PRIx32 "\n", mask);

	MutexLocker locker(bsdVolume->mnt_mtx.haikuMutex);

	if ((mask & FS_WRITE_FSINFO_NAME) == 0)
		return B_OK;

	// if it's a r/o file system, then don't allow volume renaming
	if ((bsdVolume->mnt_flag & MNT_RDONLY) != 0)
		return B_READ_ONLY_DEVICE;

	PRINT("wfsstat: setting name to %s\n", info->volume_name);
	char name[LABEL_CSTRING];
	strlcpy(name, info->volume_name, LABEL_CSTRING);
	status_t status = label_to_fat(name);
	if (status != B_OK)
		return status;

	// update the BPB, unless the volume is too old to have a label field in the BPB
	void* blockCache = bsdVolume->mnt_cache;
	u_char* buffer;
	status
		= block_cache_get_writable_etc(blockCache, 0, -1, reinterpret_cast<void**>(&buffer));
	if (status != B_OK)
		return status;
	// check for the extended boot signature
	uint32 ebsOffset = FAT32(fatVolume) != 0 ? 0x42 : 0x26;
	uint32 labelOffset = ebsOffset + 5;
	char* memoryLabel = fatVolume->pm_dev->si_name;
	if (buffer[ebsOffset] == EXBOOTSIG) {
		// double check the position by verifying the name presently stored there
		char bpbLabel[LABEL_CSTRING];
		memcpy(bpbLabel, buffer + labelOffset, LABEL_LENGTH);
		label_from_fat(bpbLabel);
		if (strncmp(bpbLabel, memoryLabel, LABEL_LENGTH) == 0) {
			memcpy(buffer + labelOffset, name, LABEL_LENGTH);
		} else {
			INFORM("wfsstat: BPB position check failed\n");
			block_cache_set_dirty(blockCache, 0, false, -1);
			status = B_ERROR;
		}
	}
	block_cache_put(blockCache, 0);

	// update the label file if there is one
	if (bsdVolume->mnt_volentry >= 0) {
		uint8* rootDirBuffer;
		daddr_t rootDirSector = fatVolume->pm_rootdirblk;
		if (FAT32(fatVolume) == true)
			rootDirSector = cntobn(fatVolume, fatVolume->pm_rootdirblk);
		rootDirSector = BLOCK_TO_SECTOR(fatVolume, rootDirSector);
		daddr_t dirOffset = bsdVolume->mnt_volentry * sizeof(direntry);
		rootDirSector += dirOffset / fatVolume->pm_BytesPerSec;

		status = block_cache_get_writable_etc(blockCache, rootDirSector, -1,
			reinterpret_cast<void**>(&rootDirBuffer));
		if (status == B_OK) {
			direntry* label_direntry = reinterpret_cast<direntry*>(rootDirBuffer + dirOffset);

			char rootLabel[LABEL_CSTRING];
			memcpy(rootLabel, label_direntry->deName, LABEL_LENGTH);
			label_from_fat(rootLabel);
			if (strncmp(rootLabel, memoryLabel, LABEL_LENGTH) == 0) {
				memcpy(label_direntry->deName, name, LABEL_LENGTH);
			} else {
				INFORM("wfsstat: root directory position check failed\n");
				block_cache_set_dirty(blockCache, rootDirSector, false, -1);
				status = B_ERROR;
			}
			block_cache_put(blockCache, rootDirSector);
		}
	} else {
		// A future enhancement could be to create a label direntry if none exists already.
	}

	if (status == B_OK) {
		memcpy(memoryLabel, name, LABEL_LENGTH);
		label_from_fat(memoryLabel);
	}

	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
		_dosfs_sync(bsdVolume, false);

	RETURN_ERROR(status);
}


static status_t
dosfs_sync(fs_volume* volume)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);

	FUNCTION();

	MutexLocker volumeLocker(bsdVolume->mnt_mtx.haikuMutex);
	WriteLocker fatLocker(fatVolume->pm_fatlock.haikuRW);

	RETURN_ERROR(_dosfs_sync(bsdVolume));
}


/*! If data is true, include regular file data in the sync. Otherwise, only sync directories,
	the FAT, and, if applicable, the fsinfo sector.
*/
status_t
_dosfs_sync(struct mount* bsdVolume, bool data)
{
	status_t status = B_OK;
	status_t returnStatus = B_OK;

	status = write_fsinfo(reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data));
	if (status != B_OK) {
		REPORT_ERROR(status);
		returnStatus = status;
	}

	status = block_cache_sync(bsdVolume->mnt_cache);
	if (status != B_OK) {
		REPORT_ERROR(status);
		returnStatus = status;
	}

	if (data == true) {
		status = sync_all_files(bsdVolume);
		if (status != B_OK) {
			REPORT_ERROR(status);
			returnStatus = status;
		}
	}

	return returnStatus;
}


static status_t
dosfs_read_vnode(fs_volume* volume, ino_t id, fs_vnode* vnode, int* _type, uint32* _flags,
	bool reenter)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	struct vnode* bsdNode;

	FUNCTION_START("id %" B_PRIdINO ", type %d, flags %" B_PRIx32 "\n", id, *_type, *_flags);

	MutexLocker locker(bsdVolume->mnt_mtx.haikuMutex);

	// In case 2 threads are concurrently executing get_vnode() with the same ID, verify
	// after locking the volume that the node has not been constructed already.
	if (node_exists(bsdVolume, id) == true)
		return B_BAD_VALUE;

	status_t status = _dosfs_read_vnode(bsdVolume, id, &bsdNode);
	if (status != B_OK)
		RETURN_ERROR(status);

	ASSERT(static_cast<ino_t>(reinterpret_cast<denode*>(bsdNode->v_data)->de_inode) == id);

	vnode->private_node = bsdNode;
	vnode->ops = &gFATVnodeOps;
	if (bsdNode->v_type == VDIR)
		*_type = S_IFDIR;
	else if (bsdNode->v_type == VREG)
		*_type = S_IFREG;
	else
		panic("dosfs_read_vnode:  unknown type\n");

	*_flags = 0;

	return B_OK;
}


/*! Can be used internally by the FS to generate a private node.

 */
static status_t
_dosfs_read_vnode(mount* bsdVolume, const ino_t id, vnode** newNode, bool createFileCache)
{
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);

	status_t status = B_OK;
	u_long dirClust, dirOffset;
	if (id == root_inode(fatVolume)) {
		dirClust = FAT32(fatVolume) == true ? fatVolume->pm_rootdirblk : MSDOSFSROOT;
		dirOffset = MSDOSFSROOT_OFS;
	} else {
		status = get_location(bsdVolume, id, &dirClust, &dirOffset);
		if (status != B_OK)
			return status;
	}

	denode* fatNode;
	status = B_FROM_POSIX_ERROR(deget(fatVolume, dirClust, dirOffset, LK_EXCLUSIVE, &fatNode));
	if (status != B_OK)
		return status;

	vnode* bsdNode = fatNode->de_vnode;
	if (bsdNode->v_type == VREG) {
		status = set_mime_type(bsdNode, false);
		if (status != B_OK)
			REPORT_ERROR(status);

		if (createFileCache) {
			bsdNode->v_cache
				= file_cache_create(fatVolume->pm_dev->si_id, fatNode->de_inode, fatNode->de_FileSize);
			bsdNode->v_file_map
				= file_map_create(fatVolume->pm_dev->si_id, fatNode->de_inode, fatNode->de_FileSize);
		}
	}

	// identify the parent directory
	if (id == root_inode(fatVolume)) {
		bsdNode->v_parent = id;
	} else if (bsdNode->v_type == VREG) {
		bsdNode->v_parent = fatVolume->pm_bpcluster * dirClust;
		assign_inode(bsdVolume, &bsdNode->v_parent);
	}
	// For a directory other than the root directory, there is no easy way to
	// ID the parent. That Will be done in later (in dosfs_walk / dosfs_mkdir).

	bsdNode->v_state = VSTATE_CONSTRUCTED;

	status = vcache_set_constructed(bsdVolume, fatNode->de_inode);
	if (status != B_OK) {
		free(fatNode);
		free(bsdNode);
		return status;
	}

#ifdef DEBUG
	status = vcache_set_node(bsdVolume, fatNode->de_inode, bsdNode);
	if (status != B_OK)
		REPORT_ERROR(status);
#endif // DEBUG

	*newNode = bsdNode;

	rw_lock_write_unlock(&bsdNode->v_vnlock->haikuRW);

	return B_OK;
}


static status_t
dosfs_walk(fs_volume* volume, fs_vnode* dir, const char* name, ino_t* _id)
{
	vnode* bsdDir = reinterpret_cast<vnode*>(dir->private_node);
	denode* fatDir = reinterpret_cast<denode*>(bsdDir->v_data);
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);

	WriteLocker locker(bsdDir->v_vnlock->haikuRW);
		// msdosfs_lookup_ino will modify de_fndoffset, de_fndcnt

	if (bsdDir->v_type != VDIR)
		RETURN_ERROR(B_NOT_A_DIRECTORY);

	ComponentName bsdName((strcmp(name, "..") == 0 ? MAKEENTRY | ISDOTDOT : MAKEENTRY), NOCRED,
		LOOKUP, 0, name);

	daddr_t dirClust;
	u_long dirOffset;
	status_t status = B_FROM_POSIX_ERROR(
		msdosfs_lookup_ino(bsdDir, NULL, bsdName.Data(), &dirClust, &dirOffset));
	if (status != B_OK) {
		// we don't add a 'missing' entry to the entry cache because it would persist after a file
		// with this name is created, if the created entry is not in the same case as the entry
		// cache entry
		RETURN_ERROR(B_ENTRY_NOT_FOUND);
	}
	// msdosfs_lookup_ino will return 0 for cluster number if looking up .. in a directory
	// whose parent is the root directory, even on FAT32 volumes (which reflects the
	// value that is meant to be stored in the .. direntry, per the FAT spec)
	if (FAT32(fatVolume) == true && dirClust == MSDOSFSROOT)
		dirClust = fatVolume->pm_rootdirblk;
	vnode* bsdResult;
	status = assign_inode_and_get(bsdVolume, dirClust, dirOffset, &bsdResult);
	if (status != B_OK)
		RETURN_ERROR(status);
	denode* fatResult = reinterpret_cast<denode*>(bsdResult->v_data);

	if (bsdResult->v_type == VDIR) {
		// dosfs_read_vnode does not set this for directories because it does not know the
		// parent inode
		bsdResult->v_parent = fatDir->de_inode;
	}

	*_id = fatResult->de_inode;

	entry_cache_add(volume->id, fatDir->de_inode, name, fatResult->de_inode);

	return B_OK;
}


static status_t
dosfs_release_vnode(fs_volume* volume, fs_vnode* vnode, bool reenter)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	FUNCTION_START("inode %" B_PRIdINO " @ %p\n", fatNode->de_inode, bsdNode);

	status_t status = B_OK;

	if ((bsdNode->v_vflag & VV_ROOT) == 0) {
		WriteLocker locker(bsdNode->v_vnlock->haikuRW);
			// needed only in this block

		status = B_FROM_POSIX_ERROR(deupdat(fatNode, 0));
		if (status != B_OK)
			RETURN_ERROR(status);

		if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
			_dosfs_fsync(bsdNode);
	}

	if (bsdNode->v_type == VREG) {
		status = file_cache_sync(bsdNode->v_cache);
		file_cache_delete(bsdNode->v_cache);
		file_map_delete(bsdNode->v_file_map);
	} else {
		status = discard_clusters(bsdNode, 0);
	}

	vcache_set_constructed(bsdVolume, fatNode->de_inode, false);

	free(fatNode);

	rw_lock_destroy(&bsdNode->v_vnlock->haikuRW);

	free(bsdNode);

	RETURN_ERROR(status);
}


status_t
dosfs_remove_vnode(fs_volume* volume, fs_vnode* vnode, bool reenter)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	FUNCTION_START("%" B_PRIu64 " @ %p\n", fatNode->de_inode, bsdNode);

	WriteLocker locker(bsdNode->v_vnlock->haikuRW);

	if (MOUNTED_READ_ONLY(fatVolume) != 0)
		RETURN_ERROR(B_READ_ONLY_DEVICE);

	status_t status = B_OK;

	if (bsdNode->v_type == VREG) {
		file_cache_delete(bsdNode->v_cache);
		bsdNode->v_cache = NULL;
		file_map_delete(bsdNode->v_file_map);
		bsdNode->v_file_map = NULL;
	} else {
		status = discard_clusters(bsdNode, 0);
		if (status != B_OK)
			REPORT_ERROR(status);
	}

	// truncate the file
	if (fatNode->de_refcnt <= 0 && fatNode->de_StartCluster != root_start_cluster(fatVolume)) {
		rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
		status = B_FROM_POSIX_ERROR(detrunc(fatNode, static_cast<u_long>(0), 0, NOCRED));
		rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
		if (status != B_OK)
			REPORT_ERROR(status);
	}
	if (status == B_OK) {
		// remove vnode id from the cache
		if (find_vnid_in_vcache(bsdVolume, fatNode->de_inode) == B_OK)
			remove_from_vcache(bsdVolume, fatNode->de_inode);

		if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
			_dosfs_sync(bsdVolume, false);
	}

	free(fatNode);

	locker.Detach();
	rw_lock_destroy(&bsdNode->v_vnlock->haikuRW);

	free(bsdNode);

	RETURN_ERROR(status);
}


static bool
dosfs_can_page(fs_volume* vol, fs_vnode* vnode, void* cookie)
{
	// ToDo: we're obviously not even asked...
	return false;
}


static status_t
dosfs_read_pages(fs_volume* volume, fs_vnode* vnode, void* cookie, off_t pos, const iovec* vecs,
	size_t count, size_t* _numBytes)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	if (bsdNode->v_cache == NULL)
		return B_BAD_VALUE;

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	uint32 vecIndex = 0;
	size_t vecOffset = 0;
	size_t bytesLeft = *_numBytes;
	status_t status;

	while (true) {
		struct file_io_vec fileVecs[8];
		size_t fileVecCount = 8;
		bool bufferOverflow;
		size_t bytes = bytesLeft;

		status
			= file_map_translate(bsdNode->v_file_map, pos, bytesLeft, fileVecs, &fileVecCount, 0);
		if (status != B_OK && status != B_BUFFER_OVERFLOW)
			break;

		bufferOverflow = status == B_BUFFER_OVERFLOW;

		status = read_file_io_vec_pages(fatVolume->pm_dev->si_fd, fileVecs, fileVecCount, vecs,
			count, &vecIndex, &vecOffset, &bytes);
		if (status != B_OK || !bufferOverflow)
			break;

		pos += bytes;
		bytesLeft -= bytes;
	}

	RETURN_ERROR(status);
}


static status_t
dosfs_write_pages(fs_volume* volume, fs_vnode* vnode, void* cookie, off_t pos, const iovec* vecs,
	size_t count, size_t* _numBytes)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	uint32 vecIndex = 0;
	size_t vecOffset = 0;
	size_t bytesLeft = *_numBytes;
	status_t status;

	FUNCTION_START("%p\n", bsdNode);

	if (bsdNode->v_cache == NULL)
		return B_BAD_VALUE;

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	if (MOUNTED_READ_ONLY(fatVolume) != 0)
		return B_READ_ONLY_DEVICE;

	while (true) {
		struct file_io_vec fileVecs[8];
		size_t fileVecCount = 8;
		bool bufferOverflow;
		size_t bytes = bytesLeft;

		status
			= file_map_translate(bsdNode->v_file_map, pos, bytesLeft, fileVecs, &fileVecCount, 0);
		if (status != B_OK && status != B_BUFFER_OVERFLOW)
			break;

		bufferOverflow = status == B_BUFFER_OVERFLOW;

		status = write_file_io_vec_pages(fatVolume->pm_dev->si_fd, fileVecs, fileVecCount, vecs,
			count, &vecIndex, &vecOffset, &bytes);
		if (status != B_OK || !bufferOverflow)
			break;

		pos += bytes;
		bytesLeft -= bytes;
	}

	RETURN_ERROR(status);
}


static status_t
dosfs_io(fs_volume* volume, fs_vnode* vnode, void* cookie, io_request* request)
{
#if KDEBUG_RW_LOCK_DEBUG
	// dosfs_io depends on read-locks being implicitly transferrable across threads.
	return B_UNSUPPORTED;
#endif
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

#ifndef FS_SHELL
	if (io_request_is_write(request) && MOUNTED_READ_ONLY(fatVolume) != 0) {
		notify_io_request(request, B_READ_ONLY_DEVICE);
		return B_READ_ONLY_DEVICE;
	}
#endif

	if (bsdNode->v_cache == NULL) {
#ifndef FS_SHELL
		notify_io_request(request, B_BAD_VALUE);
#endif
		panic("dosfs_io:  no file cache\n");
		RETURN_ERROR(B_BAD_VALUE);
	}

	// divert to synchronous IO?
	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0 || bsdNode->v_sync == true)
		return B_UNSUPPORTED;

	rw_lock_read_lock(&bsdNode->v_vnlock->haikuRW);

	acquire_vnode(volume, fatNode->de_inode);

	RETURN_ERROR(do_iterative_fd_io(fatVolume->pm_dev->si_fd, request, iterative_io_get_vecs_hook,
		iterative_io_finished_hook, bsdNode));
}


static status_t
dosfs_get_file_map(fs_volume* volume, fs_vnode* vnode, off_t position, size_t length,
	struct file_io_vec* vecs, size_t* _count)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	FUNCTION_START("%" B_PRIuSIZE " bytes at %" B_PRIdOFF " (vnode id %" B_PRIdINO " at %p)\n",
		length, position, fatNode->de_inode, bsdNode);

	size_t max = *_count;
	*_count = 0;

	if ((bsdNode->v_type & VDIR) != 0)
		return B_IS_A_DIRECTORY;

	if (position < 0)
		position = 0;

	size_t fileSize = fatNode->de_FileSize;

	if (fileSize == 0 || length == 0 || static_cast<u_long>(position) >= fileSize)
		return B_OK;

	// truncate to file size, taking overflow into account
	if (static_cast<uint64>(position + length) >= fileSize
		|| static_cast<off_t>(position + length) < position) {
		length = fileSize - position;
	}

	csi iter;
	status_t status = init_csi(fatVolume, fatNode->de_StartCluster, 0, &iter);
	if (status != B_OK)
		RETURN_ERROR(B_IO_ERROR);

	size_t bytesPerSector = fatVolume->pm_BytesPerSec;

	// file-relative sector in which position lies
	uint32 positionSector = position / bytesPerSector;

	if (positionSector > 0) {
		status = iter_csi(&iter, positionSector);
		if (status != B_OK)
			RETURN_ERROR(status);
	}

	status = validate_cs(iter.fatVolume, iter.cluster, iter.sector);
	if (status != B_OK)
		RETURN_ERROR(status);

	int32 sectorOffset = position % bytesPerSector;
	size_t index = 0;

	// Each iteration populates one vec
	while (length > 0) {
		off_t initFsSector = fs_sector(&iter);
		uint32 sectors = 1;

		length -= min_c(length, bytesPerSector - sectorOffset);

		// Each iteration advances iter to the next sector of the file.
		// Break when iter reaches the first sector of a non-contiguous cluster.
		while (length > 0) {
			status = iter_csi(&iter, 1);
			ASSERT(status == B_OK);
			status = validate_cs(iter.fatVolume, iter.cluster, iter.sector);
			if (status != B_OK)
				RETURN_ERROR(status);

			if (initFsSector + sectors != fs_sector(&iter)) {
				// disjoint sectors, need to flush and begin a new vector
				break;
			}

			length -= min_c(length, bytesPerSector);
			sectors++;
		}

		vecs[index].offset = initFsSector * bytesPerSector + sectorOffset;
		vecs[index].length = sectors * bytesPerSector - sectorOffset;
		position += vecs[index].length;

		// for the last vector only, extend to the end of the last cluster
		if (length == 0) {
			if (IS_FIXED_ROOT(fatNode) == 0) {
				uint32 remainder = position % fatVolume->pm_bpcluster;
				if (remainder != 0)
					vecs[index].length += (fatVolume->pm_bpcluster - remainder);
			}
		}

		index++;

		if (index >= max) {
			// we're out of file_io_vecs; let's bail out
			*_count = index;
			return B_BUFFER_OVERFLOW;
		}

		sectorOffset = 0;
	}

	*_count = index;

	return B_OK;
}


static status_t
dosfs_fsync(fs_volume* volume, fs_vnode* vnode, bool dataOnly)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	return _dosfs_fsync(bsdNode);
}


static status_t
_dosfs_fsync(struct vnode* bsdNode)
{
	mount* bsdVolume = bsdNode->v_mount;
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	status_t status = B_OK;
	if (bsdNode->v_cache != NULL) {
		PRINT("fsync:  file_cache_sync\n");
		status = file_cache_sync(bsdNode->v_cache);
	} else {
		status = sync_clusters(bsdNode);
	}

	// If user chose op sync mode, flush the whole block cache. This will ensure that
	// the metadata that is external to the direntry (FAT chain for this file and all directory
	// files in the hierarchy above this file) is also synced. If not, just sync the FAT and the
	// node's direntry, if it has one (the root directory doesn't).
	status_t externStatus = B_OK;

	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0) {
		externStatus = block_cache_sync(bsdVolume->mnt_cache);
		if (externStatus != B_OK)
			REPORT_ERROR(externStatus);
	} else {
		off_t fatFirstSector = fatVolume->pm_fatblk / fatVolume->pm_BytesPerSec;
		size_t fatSectors = (fatVolume->pm_fatsize * fatVolume->pm_FATs)
			/ fatVolume->pm_BytesPerSec;
		status_t fatStatus
			= block_cache_sync_etc(bsdVolume->mnt_cache, fatFirstSector, fatSectors);
		if (fatStatus != B_OK) {
			externStatus = fatStatus;
			REPORT_ERROR(fatStatus);
		}
		if ((bsdNode->v_vflag & VV_ROOT) == 0) {
			status_t entryStatus = B_FROM_POSIX_ERROR(deupdat(fatNode, 1));
			if (entryStatus != B_OK) {
				externStatus = entryStatus;
				REPORT_ERROR(entryStatus);
			}
		}
	}

	if (status == B_OK)
		status = externStatus;

	RETURN_ERROR(status);
}


static status_t
dosfs_link(fs_volume* volume, fs_vnode* dir, const char* name, fs_vnode* vnode)
{
	FUNCTION_START("attempt to assign %s to %p in directory %p\n", name, vnode, dir);

	return B_UNSUPPORTED;
}


static status_t
dosfs_unlink(fs_volume* volume, fs_vnode* dir, const char* name)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	vnode* bsdDir = reinterpret_cast<vnode*>(dir->private_node);
	denode* fatDir = reinterpret_cast<denode*>(bsdDir->v_data);
	vnode* bsdNode = NULL;
	denode* fatNode = NULL;

	FUNCTION_START("%s in directory @ %p\n", name, bsdDir);

	if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
		return B_NOT_ALLOWED;

	ComponentName bsdName(ISLASTCN, NOCRED, DELETE, 0, name);

	// multiple unlinks of files in the same dir would interfere when msdosfs_lookup_ino sets
	// de_fndofset and de_fndcnt of the parent node
	WriteLocker dirLocker(bsdDir->v_vnlock->haikuRW);

	// set bsdNode to the file to be removed
	daddr_t cluster;
	u_long offset;
	status_t status
		= B_FROM_POSIX_ERROR(msdosfs_lookup_ino(bsdDir, NULL, bsdName.Data(), &cluster, &offset));
	if (status != B_OK)
		RETURN_ERROR(status);
	status = assign_inode_and_get(bsdVolume, cluster, offset, &bsdNode);
	if (status != B_OK)
		RETURN_ERROR(status);
	WriteLocker nodeLocker(bsdNode->v_vnlock->haikuRW);
	NodePutter nodePutter(bsdNode);
	fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	if (bsdNode->v_type == VDIR)
		return B_IS_A_DIRECTORY;

	status = _dosfs_access(bsdVolume, bsdNode, W_OK);
	if (status != B_OK)
		RETURN_ERROR(B_NOT_ALLOWED);

	status = B_FROM_POSIX_ERROR(removede(fatDir, fatNode));
	if (status != B_OK)
		RETURN_ERROR(status);

	// Set the loc to a unique value. This effectively removes it from the
	// vcache without releasing its vnid for reuse. It also nicely reserves
	// the vnid from use by other nodes. This is okay because the vnode is
	// locked in memory after this point and loc will not be referenced from
	// here on.
	ino_t ino = fatNode->de_inode;
	status = vcache_set_entry(bsdVolume, ino, generate_unique_vnid(bsdVolume));
	if (status != B_OK)
		RETURN_ERROR(status);

	status = remove_vnode(volume, ino);
	if (status != B_OK)
		RETURN_ERROR(status);

	status = entry_cache_remove(volume->id, fatDir->de_inode, name);
	if (status != B_OK)
		REPORT_ERROR(status);

	notify_entry_removed(volume->id, fatDir->de_inode, name, ino);

	nodeLocker.Unlock();

	if (status == B_OK && (bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0) {
		// sync the parent directory changes
		_dosfs_sync(bsdVolume, false);
	}

	RETURN_ERROR(status);
}


/*!
	What follows is the basic algorithm:

	if (file move) {
		if (dest file exists)
			remove dest file
		if (dest and src in same directory) {
			rewrite name in existing directory slot
		} else {
			write new entry in dest directory
			update offset and dirclust in denode
			clear old directory entry
		}
	} else {
		directory move
		if (dest directory exists) {
			if (dest is not empty)
				return ENOTEMPTY
			remove dest directory
		}
		if (dest and src in same directory)
			rewrite name in existing entry
		else {
			be sure dest is not a child of src directory
			write entry in dest directory
			update "." and ".." in moved directory
			clear old directory entry for moved directory
		}
	}
*/
status_t
dosfs_rename(fs_volume* volume, fs_vnode* fromDir, const char* fromName, fs_vnode* toDir,
	const char* toName)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	vnode* fromDirBsdNode = reinterpret_cast<vnode*>(fromDir->private_node);
	vnode* toDirBsdNode = reinterpret_cast<vnode*>(toDir->private_node);

	if (fromDir == toDir && !strcmp(fromName, toName))
		return B_OK;

	if (is_filename_legal(toName) == false) {
		INFORM("file name '%s' is not permitted in the FAT filesystem\n", toName);
		return B_BAD_VALUE;
	}

	ComponentName fromBsdName(ISLASTCN, NOCRED, RENAME, 0, fromName);
	ComponentName toBsdName(ISLASTCN, NOCRED, RENAME, 0, toName);

	// Eliminate the possibility that two move operations could deadlock, if they are
	// locking the same two directories in the reverse order.
	MutexLocker volumeLocker(bsdVolume->mnt_mtx.haikuMutex);

	WriteLocker fromDirLocker(fromDirBsdNode->v_vnlock->haikuRW);
	WriteLocker toDirLocker;
	if (fromDirBsdNode != toDirBsdNode)
		toDirLocker.SetTo(toDirBsdNode->v_vnlock->haikuRW, false);
	volumeLocker.Unlock();

	status_t status = _dosfs_access(bsdVolume, fromDirBsdNode, W_OK);
	if (status == B_OK && fromDirBsdNode != toDirBsdNode)
		status = _dosfs_access(bsdVolume, toDirBsdNode, W_OK);
	if (status != B_OK)
		RETURN_ERROR(status);

	// get the 'from' node
	daddr_t fromCluster;
	u_long fromOffset;
	status = B_FROM_POSIX_ERROR(
		msdosfs_lookup_ino(fromDirBsdNode, NULL, fromBsdName.Data(), &fromCluster, &fromOffset));
	if (status != B_OK)
		RETURN_ERROR(status);
	vnode* fromBsdNode;
	status = assign_inode_and_get(bsdVolume, fromCluster, fromOffset, &fromBsdNode);
	if (status != B_OK)
		RETURN_ERROR(status);
	NodePutter fromPutter(fromBsdNode);
	WriteLocker fromLocker(fromBsdNode->v_vnlock->haikuRW);

	// make sure the from entry wasn't deleted before we locked it
	status = B_FROM_POSIX_ERROR(
		msdosfs_lookup_ino(fromDirBsdNode, NULL, fromBsdName.Data(), &fromCluster, &fromOffset));
	if (status != B_OK) {
		INFORM("dosfs_rename:  file no longer present\n");
		RETURN_ERROR(status);
	}

	// get the "to" node, if the target name already exists
	daddr_t toCluster;
	u_long toOffset;
	status = B_FROM_POSIX_ERROR(
		msdosfs_lookup_ino(toDirBsdNode, NULL, toBsdName.Data(), &toCluster, &toOffset));
	if (status != B_OK && status != B_FROM_POSIX_ERROR(EJUSTRETURN))
		RETURN_ERROR(status);
	vnode* toBsdNode = NULL;
	if (status == B_OK) {
		// the target name does exist
		status = assign_inode_and_get(bsdVolume, toCluster, toOffset, &toBsdNode);
		if (status != B_OK)
			RETURN_ERROR(status);
	}

	// Is toName equivalent to fromName in the FAT filesystem?
	bool caseChange = false;
	if (fromBsdNode == toBsdNode) {
		// The names they must differ only in capitalization. Ignore the match that was found for
		// the "to" node.
		put_vnode(volume, reinterpret_cast<denode*>(toBsdNode->v_data)->de_inode);
		toBsdNode = NULL;
		caseChange = true;
	}

	NodePutter toPutter;
	WriteLocker toLocker;

	if (toBsdNode != NULL) {
		status = msdosfs_lookup_ino(toDirBsdNode, NULL, toBsdName.Data(), &toCluster, &toOffset);
		if (status != 0) {
			toBsdNode = NULL;
			status = B_OK;
		} else {
			toLocker.SetTo(toBsdNode->v_vnlock->haikuRW, false);
			toPutter.SetTo(toBsdNode);
		}
	}

	denode* fromDirFatNode = reinterpret_cast<denode*>(fromDirBsdNode->v_data);
	denode* fromFatNode = reinterpret_cast<denode*>(fromBsdNode->v_data);
	denode* toDirFatNode = reinterpret_cast<denode*>(toDirBsdNode->v_data);
	denode* toFatNode = toBsdNode != NULL ? reinterpret_cast<denode*>(toBsdNode->v_data) : NULL;

	PRINT("dosfs_rename: %" B_PRIu64 "/%s->%" B_PRIu64 "/%s\n", fromDirFatNode->de_inode, fromName,
		toDirFatNode->de_inode, toName);

	u_long toDirOffset = toDirFatNode->de_fndoffset;

	// Is fromName a directory?
	bool doingDirectory = false;
	// Be sure we are not renaming ".", "..", or an alias of ".". This leads to a
	// crippled directory tree. It's pretty tough to do a "ls" or "pwd" with the
	// "." directory entry missing, and "cd .."doesn't work if the ".." entry is missing.
	if ((fromFatNode->de_Attributes & ATTR_DIRECTORY) != 0) {
		// Avoid ".", "..", and aliases of "." for obvious reasons.
		if ((fromBsdName.Data()->cn_namelen == 1 && fromBsdName.Data()->cn_nameptr[0] == '.')
			|| fromDirFatNode == fromFatNode || (fromBsdName.Data()->cn_flags & ISDOTDOT) != 0
			|| (fromBsdName.Data()->cn_flags & ISDOTDOT) != 0) {
			RETURN_ERROR(B_BAD_VALUE);
		}
		doingDirectory = true;
	}

	// Is the target being moved to new parent directory?
	bool newParent = fromDirFatNode != toDirFatNode ? true : false;

	// If ".." must be changed (ie the directory gets a new parent) then the source
	// directory must not be in the directory hierarchy above the target, as this would
	// orphan everything below the source directory. Also the user must have write
	// permission in the source so as to be able to change "..".
	status = _dosfs_access(bsdVolume, fromBsdNode, W_OK);
	if (doingDirectory && newParent) {
		if (status != B_OK) // write access check above
			RETURN_ERROR(status);

		rw_lock_write_lock(&fatVolume->pm_checkpath_lock.haikuRW);

		// The BSD function doscheckpath requires a third argument to return the location of
		// any child directory of fromFatNode that is locked by another thread. In the port we
		// don't use make use of this information, we just wait for that node to be unlocked.
		daddr_t dummy;
		// Switch the 'to' directory from the WriteLocker to a simple lock. This is a workaround
		// for problems that occur when doscheckpath() works with the node lock, while that lock
		// is held by a WriteLocker.
		rw_lock_write_lock(&toDirBsdNode->v_vnlock->haikuRW);
		toDirLocker.Unlock();
		status = B_FROM_POSIX_ERROR(doscheckpath(fromFatNode, toDirFatNode, &dummy));
		toDirLocker.Lock();
		rw_lock_write_unlock(&toDirBsdNode->v_vnlock->haikuRW);

		rw_lock_write_unlock(&fatVolume->pm_checkpath_lock.haikuRW);
		if (status != B_OK)
			RETURN_ERROR(status);
	}

	if (toFatNode != NULL) {
		// Target must be empty if a directory and have no links to it. Also, ensure source and
		// target are compatible (both directories, or both not directories).
		if ((toFatNode->de_Attributes & ATTR_DIRECTORY) != 0) {
			if (!dosdirempty(toFatNode))
				RETURN_ERROR(B_DIRECTORY_NOT_EMPTY);
			if (!doingDirectory)
				RETURN_ERROR(B_NOT_A_DIRECTORY);
			entry_cache_remove(volume->id, toDirFatNode->de_inode, toBsdName.Data()->cn_nameptr);
		} else if (doingDirectory) {
			RETURN_ERROR(B_IS_A_DIRECTORY);
		}

		// delete the file/directory that we are overwriting
		daddr_t remCluster;
		u_long remOffset;
		status = msdosfs_lookup_ino(toDirBsdNode, NULL, toBsdName.Data(), &remCluster, &remOffset);
			// set de_fndoffset for use by removede
		status = B_FROM_POSIX_ERROR(removede(toDirFatNode, toFatNode));
		if (status != B_OK)
			RETURN_ERROR(status);

		// Set the loc to a unique value. This effectively removes it from the vcache without
		// releasing its vnid for reuse. It also nicely reserves the vnid from use by other
		// nodes. This is okay because the vnode is locked in memory after this point and loc
		// will not be referenced from here on.
		vcache_set_entry(bsdVolume, toFatNode->de_inode, generate_unique_vnid(bsdVolume));

		entry_cache_remove(volume->id, toDirFatNode->de_inode, toName);
		notify_entry_removed(volume->id, toDirFatNode->de_inode, toName, toFatNode->de_inode);

		remove_vnode(volume, toFatNode->de_inode);

		toLocker.Unlock();
		toPutter.Put();

		toBsdNode = NULL;
		toFatNode = NULL;
	}

	// Convert the filename in toBsdName into a dos filename. We copy this into the denode and
	// directory entry for the destination file/directory.
	u_char toShortName[SHORTNAME_CSTRING], oldShortNameArray[SHORTNAME_LENGTH];
	if (caseChange == false) {
		status = B_FROM_POSIX_ERROR(uniqdosname(toDirFatNode, toBsdName.Data(), toShortName));
		if (status != B_OK)
			RETURN_ERROR(status);
		if (is_shortname_legal(toShortName) == false)
			return B_NOT_ALLOWED;
	}
	// if only changing case, the dos filename (always all-caps) will remain the same

	// First write a new entry in the destination directory and mark the entry in the source
	// directory as deleted. If we moved a directory, then update its .. entry to point to
	// the new parent directory.
	if (caseChange == false) {
		memcpy(oldShortNameArray, fromFatNode->de_Name, SHORTNAME_LENGTH);
		memcpy(fromFatNode->de_Name, toShortName, SHORTNAME_LENGTH); // update denode
	} else {
		// We prefer to create the new dir entry before removing the old one, but if only
		// changing case, we remove the old dir entry first, so that msdosfs_lookup_ino call below
		// won't see it as a match for the to-name when it does its case-insensitive search,
		// which would cause it to return before it has found empty slots for the new dir entry.
		status = B_FROM_POSIX_ERROR(removede(fromDirFatNode, fromFatNode));
		if (status != B_OK) {
			INFORM("rename removede error:  %" B_PRIu64 "/%" B_PRIu64 ": %s\n",
				fromDirFatNode->de_inode, fromFatNode->de_inode, strerror(status));
			msdosfs_integrity_error(fatVolume);
			RETURN_ERROR(status);
		}
	}

	daddr_t createCluster;
	u_long createOffset;
	status
		= msdosfs_lookup_ino(toDirBsdNode, NULL, toBsdName.Data(), &createCluster, &createOffset);
	rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
		// the FAT will be updated if the directory needs to be extended to hold another dirent
	if (status == EJUSTRETURN) {
		toDirFatNode->de_fndoffset = toDirOffset;
			// if the to-name already existed, ensure that creatde will write the new
			// direntry to the space previously occupied by the (removed) to-name entry
		status = createde(fromFatNode, toDirFatNode, NULL, toBsdName.Data());
	}
	rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
	if (status != B_OK) {
		if (caseChange == true) {
			// We failed to create the new dir entry, and the old dir entry is already gone.
			// Try to restore the old entry.  Since the old name is a case variant of the new name
			// in the same directory, creating an entry with the old name will probably fail too.
			// Use the dos name instead of the long name, to simplify entry creation and try to
			// avoid the same mode of failure.
			ComponentName restoreName(ISLASTCN, NOCRED, CREATE, 0,
				reinterpret_cast<char*>(fromFatNode->de_Name));
			createde(fromFatNode, fromDirFatNode, NULL, restoreName.Data());
		} else {
			// we haven't removed the old dir entry yet
			memcpy(fromFatNode->de_Name, oldShortNameArray, SHORTNAME_LENGTH);
		}
		RETURN_ERROR(B_FROM_POSIX_ERROR(status));
	}

	// If fromFatNode is for a directory, then its name should always be "." since it is for the
	// directory entry in the directory itself (msdosfs_lookup() always translates to the "."
	// entry so as to get a unique denode, except for the root directory there are different
	// complications). However, we just corrupted its name to pass the correct name to
	// createde(). Undo this.
	if ((fromFatNode->de_Attributes & ATTR_DIRECTORY) != 0)
		memcpy(fromFatNode->de_Name, oldShortNameArray, SHORTNAME_LENGTH);
	fromFatNode->de_refcnt++;
		// offset the decrement that will occur in removede
	daddr_t remFromCluster;
	u_long remFromOffset;
	status = msdosfs_lookup_ino(fromDirBsdNode, NULL, fromBsdName.Data(), &remFromCluster,
		&remFromOffset);
	if (caseChange == false) {
		status = B_FROM_POSIX_ERROR(removede(fromDirFatNode, fromFatNode));
		if (status != B_OK) {
			INFORM("rename removede error:  %" B_PRIu64 "/%" B_PRIu64 ": %s\n",
				fromDirFatNode->de_inode, fromFatNode->de_inode, strerror(status));
			msdosfs_integrity_error(fatVolume);
			RETURN_ERROR(status);
		}
	}
	if (!doingDirectory) {
		status = B_FROM_POSIX_ERROR(pcbmap(toDirFatNode, de_cluster(fatVolume, toDirOffset), 0,
			&fromFatNode->de_dirclust, 0));
		if (status != B_OK) {
			msdosfs_integrity_error(fatVolume);
				// fs is corrupt
			RETURN_ERROR(status);
		}
		if (fromFatNode->de_dirclust == MSDOSFSROOT)
			fromFatNode->de_diroffset = toDirOffset;
		else
			fromFatNode->de_diroffset = toDirOffset & fatVolume->pm_crbomask;
	}

	fromBsdNode->v_parent = toDirFatNode->de_inode;

	ino_t newLocation = DETOI(fatVolume, fromFatNode->de_dirclust, fromFatNode->de_diroffset);
	vcache_set_entry(bsdVolume, fromFatNode->de_inode, newLocation);

	// If we moved a directory to a new parent directory, then we must fixup the ".." entry in
	// the moved directory.
	if (doingDirectory && newParent) {
		buf* dotDotBuf = NULL;
		u_long clustNumber = fromFatNode->de_StartCluster;
		ASSERT(clustNumber != MSDOSFSROOT);
			// this should never happen
		daddr_t blockNumber = cntobn(fatVolume, clustNumber);
		status = B_FROM_POSIX_ERROR(
			bread(fatVolume->pm_devvp, blockNumber, fatVolume->pm_bpcluster, NOCRED, &dotDotBuf));
		if (status != B_OK) {
			INFORM("rename read error:  %" B_PRIu64 "/%" B_PRIu64 ": %s\n",
				fromDirFatNode->de_inode, fromFatNode->de_inode, strerror(status));
			msdosfs_integrity_error(fatVolume);
			RETURN_ERROR(status);
		}
		direntry* dotDotEntry = reinterpret_cast<direntry*>(dotDotBuf->b_data) + 1;
		u_long parentClust = toDirFatNode->de_StartCluster;
		if (FAT32(fatVolume) == true && parentClust == fatVolume->pm_rootdirblk)
			parentClust = MSDOSFSROOT;
		putushort(dotDotEntry->deStartCluster, parentClust);
		if (FAT32(fatVolume) == true)
			putushort(dotDotEntry->deHighClust, parentClust >> 16);
		if (DOINGASYNC(fromBsdNode)) {
			bdwrite(dotDotBuf);
		} else if ((status = B_FROM_POSIX_ERROR(bwrite(dotDotBuf))) != B_OK) {
			INFORM("rename write error:  %" B_PRIu64 "/%" B_PRIu64 ":  %s\n",
				fromDirFatNode->de_inode, fromFatNode->de_inode, strerror(status));
			msdosfs_integrity_error(fatVolume);
			RETURN_ERROR(status);
		}
		entry_cache_add(volume->id, fromFatNode->de_inode, "..", toDirFatNode->de_inode);
	}

	status = entry_cache_remove(volume->id, fromDirFatNode->de_inode, fromName);
	if (status != B_OK)
		REPORT_ERROR(status);
	status = entry_cache_add(volume->id, toDirFatNode->de_inode, toName, fromFatNode->de_inode);
	if (status != B_OK)
		REPORT_ERROR(status);

	status = notify_entry_moved(volume->id, fromDirFatNode->de_inode, fromName,
		toDirFatNode->de_inode, toName, fromFatNode->de_inode);
	if (status != B_OK)
		REPORT_ERROR(status);

	set_mime_type(fromBsdNode, true);

	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0) {
		// sync the directory entry changes
		status = block_cache_sync(bsdVolume->mnt_cache);
	}

	RETURN_ERROR(status);
}


static status_t
dosfs_access(fs_volume* vol, fs_vnode* node, int mode)
{
	mount* bsdVolume = reinterpret_cast<mount*>(vol->private_volume);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(node->private_node);

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	RETURN_ERROR(_dosfs_access(bsdVolume, bsdNode, mode));
}


status_t
_dosfs_access(const mount* bsdVolume, const struct vnode* bsdNode, const int mode)
{
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);

	if ((mode & W_OK) != 0 && MOUNTED_READ_ONLY(fatVolume))
		RETURN_ERROR(B_READ_ONLY_DEVICE);

	mode_t fileMode = 0;
	mode_bits(bsdNode, &fileMode);

	// userlandfs does not provide check_access_permissions
#ifdef USER
	return check_access_permissions_internal(mode, fileMode, fatVolume->pm_gid, fatVolume->pm_uid);
#else
	return check_access_permissions(mode, fileMode, fatVolume->pm_gid, fatVolume->pm_uid);
#endif
}


static status_t
dosfs_rstat(fs_volume* volume, fs_vnode* vnode, struct stat* stat)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	// file mode bits
	mode_bits(bsdNode, &stat->st_mode);
	// file type bits
	status_t status = B_OK;
	if (bsdNode->v_type == VDIR)
		stat->st_mode |= S_IFDIR;
	else if (bsdNode->v_type == VREG)
		stat->st_mode |= S_IFREG;
	else
		status = B_BAD_VALUE;

	stat->st_nlink = 1;

	// The FAT filesystem does not keep track of ownership at the file level
	stat->st_uid = fatVolume->pm_uid;

	stat->st_gid = fatVolume->pm_gid;

	stat->st_size = fatNode->de_FileSize;

	stat->st_blksize = FAT_IO_SIZE;

	fattime2timespec(fatNode->de_MDate, fatNode->de_MTime, 0, 1, &stat->st_mtim);

	// FAT does not keep a record of last change time
	stat->st_ctim = stat->st_mtim;

	fattime2timespec(fatNode->de_ADate, 0, 0, 1, &stat->st_atim);

	fattime2timespec(fatNode->de_CDate, fatNode->de_CTime, fatNode->de_CHun, 1, &stat->st_crtim);

	stat->st_blocks = howmany(fatNode->de_FileSize, 512);

	RETURN_ERROR(status);
}


static status_t
dosfs_wstat(fs_volume* volume, fs_vnode* vnode, const struct stat* stat, uint32 statMask)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	FUNCTION_START("inode %" B_PRIu64 ", @ %p\n", fatNode->de_inode, bsdNode);

	WriteLocker locker(bsdNode->v_vnlock->haikuRW);

	bool hasWriteAccess = _dosfs_access(bsdVolume, bsdNode, W_OK) == B_OK;
	uid_t uid = geteuid();
	bool isOwnerOrRoot = uid == 0 || uid == fatVolume->pm_uid;
	;

	// We don't allow setting attributes on the root directory. The special case for the root
	// directory is because before FAT32, the root directory didn't have an entry for itself
	// (and was otherwise special). With FAT32, the root directory is not so special, but still
	// doesn't have an entry for itself.
	if (bsdNode->v_vflag & VV_ROOT)
		RETURN_ERROR(B_BAD_VALUE);

	off_t previousSize = fatNode->de_FileSize;
	status_t status = B_OK;

	if ((statMask & B_STAT_SIZE) != 0) {
		if (!hasWriteAccess)
			RETURN_ERROR(B_NOT_ALLOWED);

		switch (bsdNode->v_type) {
			case VDIR:
				return B_IS_A_DIRECTORY;
			case VREG:
				break;
			default:
				return B_BAD_VALUE;
				break;
		}

		if (stat->st_size >= MSDOSFS_FILESIZE_MAX)
			RETURN_ERROR(B_FILE_TOO_LARGE);

		bool shrinking = previousSize > stat->st_size;

		// If growing the file, detrunc will call deextend, which tries to zero out the new
		// clusters. We use the v_resizing flag to disable writes during detrunc to prevent that,
		// because using file_cache_write while the node is locked can cause a deadlock.
		// The new clusters will be cleared after return from detrunc instead.
		// We also disable writes in the case of shrinking the file because, unlike the detrunc
		// call in create or open, which always truncate to zero, this call will most likely pass
		// a size that is not a multiple of cluster size, so detrunc will want to zero out the end
		// of the last cluster.
		bsdNode->v_resizing = true;
		rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
		status = B_FROM_POSIX_ERROR(detrunc(fatNode, stat->st_size, 0, NOCRED));
		rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
		bsdNode->v_resizing = false;
		if (status != B_OK)
			RETURN_ERROR(status);

		PRINT("dosfs_wstat: inode %" B_PRIu64 ", @ %p size change from %" B_PRIdOFF " to %" B_PRIu64
			"\n", fatNode->de_inode, bsdNode, previousSize, stat->st_size);

		locker.Unlock();
			// avoid deadlock with dosfs_io
		file_cache_set_size(bsdNode->v_cache, fatNode->de_FileSize);
		if (shrinking == false && (statMask & B_STAT_SIZE_INSECURE) == 0) {
			status = fill_gap_with_zeros(bsdNode, previousSize, fatNode->de_FileSize);
			if (status != B_OK)
				RETURN_ERROR(status);
		}
		locker.Lock();

		if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
			_dosfs_fsync(bsdNode);

		fatNode->de_Attributes |= ATTR_ARCHIVE;
		fatNode->de_flag |= DE_MODIFIED;
	}

	// DOS files only have the ability to have their writability attribute set, so we use the
	// owner write bit to set the readonly attribute.
	if ((statMask & B_STAT_MODE) != 0) {
		if (!isOwnerOrRoot)
			RETURN_ERROR(B_NOT_ALLOWED);
		PRINT("setting file mode to %o\n", stat->st_mode);
		if (bsdNode->v_type != VDIR) {
			if ((stat->st_mode & S_IWUSR) == 0)
				fatNode->de_Attributes |= ATTR_READONLY;
			else
				fatNode->de_Attributes &= ~ATTR_READONLY;

			// We don't set the archive bit when modifying the time of
			// a directory to emulate the Windows/DOS behavior.
			fatNode->de_Attributes |= ATTR_ARCHIVE;
			fatNode->de_flag |= DE_MODIFIED;
		}
	}

	if ((statMask & B_STAT_UID) != 0) {
		PRINT("cannot set UID at file level\n");
		if (stat->st_uid != fatVolume->pm_uid)
			status = B_BAD_VALUE;
	}

	if ((statMask & B_STAT_GID) != 0) {
		PRINT("cannot set GID at file level\n");
		if (stat->st_gid != fatVolume->pm_gid)
			status = B_BAD_VALUE;
	}

	if ((statMask & B_STAT_ACCESS_TIME) != 0) {
		PRINT("setting access time\n");
		fatNode->de_flag &= ~DE_ACCESS;
		struct timespec atimGMT;
		local_to_GMT(&stat->st_atim, &atimGMT);
		timespec2fattime(&atimGMT, 0, &fatNode->de_ADate, NULL, NULL);
		if (bsdNode->v_type != VDIR)
			fatNode->de_Attributes |= ATTR_ARCHIVE;
		fatNode->de_flag |= DE_MODIFIED;
	}

	if ((statMask & B_STAT_MODIFICATION_TIME) != 0) {
		// the user or root can do that or any user with write access
		if (!isOwnerOrRoot && !hasWriteAccess)
			RETURN_ERROR(B_NOT_ALLOWED);
		PRINT("setting modification time\n");
		fatNode->de_flag &= ~DE_UPDATE;
		struct timespec mtimGMT;
		local_to_GMT(&stat->st_mtim, &mtimGMT);
		timespec2fattime(&mtimGMT, 0, &fatNode->de_MDate, &fatNode->de_MTime, NULL);
		if (bsdNode->v_type != VDIR)
			fatNode->de_Attributes |= ATTR_ARCHIVE;
		fatNode->de_flag |= DE_MODIFIED;
	}

	if ((statMask & B_STAT_CREATION_TIME) != 0) {
		// the user or root can do that or any user with write access
		if (!isOwnerOrRoot && !hasWriteAccess)
			RETURN_ERROR(B_NOT_ALLOWED);
		PRINT("setting creation time\n");
		struct timespec crtimGMT;
		local_to_GMT(&stat->st_crtim, &crtimGMT);
		timespec2fattime(&crtimGMT, 0, &fatNode->de_CDate, &fatNode->de_CTime, NULL);
		fatNode->de_flag |= DE_MODIFIED;
	}

	// node change time is not recorded in the FAT file system

	status = B_FROM_POSIX_ERROR(deupdat(fatNode, (bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0));

	notify_stat_changed(volume->id, bsdNode->v_parent, fatNode->de_inode, statMask);

	RETURN_ERROR(status);
}


static status_t
dosfs_create(fs_volume* volume, fs_vnode* dir, const char* name, int openMode, int perms,
	void** _cookie, ino_t* _newVnodeID)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	vnode* bsdDir = reinterpret_cast<vnode*>(dir->private_node);
	denode* fatDir = reinterpret_cast<denode*>(bsdDir->v_data);

	FUNCTION_START("create %s in %" B_PRIu64 ", perms = %o openMode =%o\n", name, fatDir->de_inode,
		perms, openMode);

	ComponentName bsdName(ISLASTCN | MAKEENTRY, NOCRED, CREATE, 0, name);

	WriteLocker locker(bsdDir->v_vnlock->haikuRW);

	if (_dosfs_access(bsdVolume, bsdDir, open_mode_to_access(openMode)) != B_OK)
		RETURN_ERROR(B_NOT_ALLOWED);

	if ((openMode & O_NOCACHE) != 0)
		RETURN_ERROR(B_UNSUPPORTED);

	if (is_filename_legal(name) != true) {
		INFORM("invalid FAT file name '%s'\n", name);
		RETURN_ERROR(B_UNSUPPORTED);
	}

	bool removed = false;
	status_t status = get_vnode_removed(volume, fatDir->de_inode, &removed);
	if (status == B_OK && removed == true)
		RETURN_ERROR(B_ENTRY_NOT_FOUND);

	if ((openMode & O_RWMASK) == O_RDONLY)
		RETURN_ERROR(B_NOT_ALLOWED);

	FileCookie* cookie = new(std::nothrow) FileCookie;
	if (cookie == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	ObjectDeleter<FileCookie> cookieDeleter(cookie);

	// In addition to checking for an existing file with this name, msdosfs_lookup_ino
	// will set de_fndoffset of the parent node to a vacant direntry slot if there is
	// no existing file, in preparation for createde.
	daddr_t cluster;
	u_long offset;
	status
		= B_FROM_POSIX_ERROR(msdosfs_lookup_ino(bsdDir, NULL, bsdName.Data(), &cluster, &offset));

	if (status == B_OK) {
		// there is already a file with this name
		vnode* existingBsdNode;
		status = assign_inode_and_get(bsdVolume, cluster, offset, &existingBsdNode);
		if (status != B_OK)
			RETURN_ERROR(status);
		WriteLocker existingLocker(existingBsdNode->v_vnlock->haikuRW);
		NodePutter existingPutter(existingBsdNode);
		denode* existingFatNode = reinterpret_cast<denode*>(existingBsdNode->v_data);

		if ((openMode & O_EXCL) != 0)
			RETURN_ERROR(B_FILE_EXISTS);
		if (existingBsdNode->v_type == VDIR)
			RETURN_ERROR(B_NOT_ALLOWED);
		if ((openMode & O_TRUNC) != 0) {
			status = _dosfs_access(bsdVolume, existingBsdNode, open_mode_to_access(openMode));
			if (status != B_OK)
				RETURN_ERROR(status);
			rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
			status = B_FROM_POSIX_ERROR(detrunc(existingFatNode, 0, 0, NOCRED));
			rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
			if (status != B_OK)
				RETURN_ERROR(status);

			existingLocker.Unlock();
				// avoid deadlock that can happen when reducing cache size
			file_cache_set_size(existingBsdNode->v_cache, 0);

			if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
				_dosfs_fsync(existingBsdNode);
		} else {
			status = _dosfs_access(bsdVolume, existingBsdNode, open_mode_to_access(openMode));
			if (status != B_OK)
				RETURN_ERROR(status);
		}

		*_newVnodeID = existingFatNode->de_inode;

		cookie->fMode = openMode;
		cookie->fLastSize = existingFatNode->de_FileSize;
		cookie->fMtimeAtOpen = existingFatNode->de_MTime;
		cookie->fMdateAtOpen = existingFatNode->de_MDate;
		cookie->fLastNotification = 0;
		*_cookie = cookie;
		cookieDeleter.Detach();

		return B_OK;
	}

	if (status != B_FROM_POSIX_ERROR(EJUSTRETURN))
		return status;

	// If this is the FAT12/16 root directory and there is no space left we can't do anything.
	// This is because the root directory can not change size.
	if (fatDir->de_StartCluster == MSDOSFSROOT && fatDir->de_fndoffset >= fatDir->de_FileSize) {
		INFORM("root directory is full and cannot be expanded\n");
		return B_UNSUPPORTED;
	}

	// set up a dummy node that will be converted into a direntry
	denode newDirentry;
	memset(&newDirentry, 0, sizeof(newDirentry));
	status = B_FROM_POSIX_ERROR(uniqdosname(fatDir, bsdName.Data(), newDirentry.de_Name));
	if (status != B_OK)
		return status;
	if (is_shortname_legal(newDirentry.de_Name) == false) {
		INFORM("invalid FAT short file name '%s'\n", name);
		RETURN_ERROR(B_UNSUPPORTED);
	}
	newDirentry.de_Attributes = ATTR_ARCHIVE;
	if ((perms & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0)
		newDirentry.de_Attributes |= ATTR_READONLY;
	newDirentry.de_LowerCase = 0;
	newDirentry.de_StartCluster = 0;
	newDirentry.de_FileSize = 0;
	newDirentry.de_pmp = fatDir->de_pmp;
	newDirentry.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
	timespec timeSpec;
	vfs_timestamp(&timeSpec);
	DETIMES(&newDirentry, &timeSpec, &timeSpec, &timeSpec);

	// write the direntry
	u_long fndoffset = fatDir->de_fndoffset;
		// remember this value, because fatDir->de_fndoffset is liable to change during createde
	rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
	status = B_FROM_POSIX_ERROR(createde(&newDirentry, fatDir, NULL, bsdName.Data()));
	rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
	if (status != B_OK)
		RETURN_ERROR(status);

	// determine the inode number
	u_long newCluster;
	status = B_FROM_POSIX_ERROR(
		pcbmap(fatDir, de_cluster(fatVolume, fndoffset), NULL, &newCluster, NULL));
	if (status != B_OK)
		RETURN_ERROR(status);
	uint32 newOffset = fndoffset;
	if (newCluster != MSDOSFSROOT)
		newOffset = fndoffset % fatVolume->pm_bpcluster;
	ino_t inode = DETOI(fatVolume, newCluster, newOffset);
	status = assign_inode(bsdVolume, &inode);
	if (status != B_OK)
		RETURN_ERROR(status);

	// set up the actual node
	vnode* bsdNode;
	status = _dosfs_read_vnode(bsdVolume, inode, &bsdNode, false);
	if (status != B_OK)
		RETURN_ERROR(status);
	mode_t nodeType = 0;
	if (bsdNode->v_type == VDIR)
		nodeType = S_IFDIR;
	else if (bsdNode->v_type == VREG)
		nodeType = S_IFREG;
	else
		panic("dosfs_create:  unknown node type\n");

	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	cookie->fMode = openMode;
	cookie->fLastSize = fatNode->de_FileSize;
	cookie->fMtimeAtOpen = fatNode->de_MTime;
	cookie->fMdateAtOpen = fatNode->de_MDate;
	cookie->fLastNotification = 0;
	*_cookie = cookie;

	status = publish_vnode(volume, inode, bsdNode, &gFATVnodeOps, nodeType, 0);

	// This is usually done in _dosfs_read_vnode. However, the node wasn't published yet,
	// so it would not have worked there.
	bsdNode->v_cache
		= file_cache_create(fatVolume->pm_dev->si_id, fatNode->de_inode, fatNode->de_FileSize);
	bsdNode->v_file_map
		= file_map_create(fatVolume->pm_dev->si_id, fatNode->de_inode, fatNode->de_FileSize);

	ASSERT(static_cast<ino_t>(fatNode->de_inode) == inode);
	*_newVnodeID = fatNode->de_inode;

	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
		_dosfs_fsync(bsdNode);

	entry_cache_add(volume->id, fatDir->de_inode, name, fatNode->de_inode);
	notify_entry_created(volume->id, fatDir->de_inode, name, fatNode->de_inode);

	cookieDeleter.Detach();

	return B_OK;
}


status_t
dosfs_open(fs_volume* volume, fs_vnode* vnode, int openMode, void** _cookie)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	FUNCTION_START("node %" B_PRIu64 " @ %p, omode %o\n", fatNode->de_inode, bsdNode, openMode);

	*_cookie = NULL;

	if ((openMode & O_NOCACHE) != 0)
		RETURN_ERROR(B_UNSUPPORTED);

	if ((openMode & O_CREAT) != 0) {
		PRINT("dosfs_open called with O_CREAT. call dosfs_create instead!\n");
		return B_BAD_VALUE;
	}

	ReadLocker readLocker;
	WriteLocker writeLocker;
	if ((openMode & O_TRUNC) != 0)
		writeLocker.SetTo(bsdNode->v_vnlock->haikuRW, false);
	else
		readLocker.SetTo(bsdNode->v_vnlock->haikuRW, false);

	// Opening a directory read-only is allowed, although you can't read
	// any data from it.
	if (bsdNode->v_type == VDIR && (openMode & O_RWMASK) != O_RDONLY)
		return B_IS_A_DIRECTORY;

	if ((bsdVolume->mnt_flag & MNT_RDONLY) != 0 || (fatNode->de_Attributes & ATTR_READONLY) != 0)
		openMode = (openMode & ~O_RWMASK) | O_RDONLY;

	status_t status = _dosfs_access(bsdVolume, bsdNode, open_mode_to_access(openMode));
	if (status != B_OK)
		RETURN_ERROR(status);

	FileCookie* cookie = new(std::nothrow) FileCookie;
	if (cookie == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	ObjectDeleter<FileCookie> cookieDeleter(cookie);
	cookie->fMode = openMode;
	cookie->fLastSize = fatNode->de_FileSize;
	cookie->fMtimeAtOpen = fatNode->de_MTime;
	cookie->fMdateAtOpen = fatNode->de_MDate;
	cookie->fLastNotification = 0;
	*_cookie = cookie;

	if ((openMode & O_TRUNC) != 0) {
		rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
		status = B_FROM_POSIX_ERROR(detrunc(fatNode, 0, 0, NOCRED));
		rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
		if (status != B_OK)
			RETURN_ERROR(status);

		writeLocker.Unlock();
		status = file_cache_set_size(bsdNode->v_cache, 0);
		if (status != B_OK)
			RETURN_ERROR(status);
	}

	cookieDeleter.Detach();

	return B_OK;
}


static status_t
dosfs_close(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	FUNCTION_START("%p\n", vnode->private_node);

	return B_OK;
}


static status_t
dosfs_free_cookie(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	FUNCTION_START("%s (inode %" B_PRIu64 " at %p)\n", fatNode->de_Name, fatNode->de_inode,
		bsdNode);

	ReadLocker readLocker;
	WriteLocker writeLocker;
	bool correctLock = false;
	while (correctLock == false) {
		if ((fatNode->de_flag & (DE_UPDATE | DE_ACCESS | DE_CREATE)) != 0) {
			writeLocker.SetTo(bsdNode->v_vnlock->haikuRW, false);
			if ((fatNode->de_flag & (DE_UPDATE | DE_ACCESS | DE_CREATE)) != 0)
				correctLock = true;
			else
				writeLocker.Unlock();
		} else {
			readLocker.SetTo(bsdNode->v_vnlock->haikuRW, false);
			if ((fatNode->de_flag & (DE_UPDATE | DE_ACCESS | DE_CREATE)) == 0)
				correctLock = true;
			else
				readLocker.Unlock();
		}
	}

	struct timespec timeSpec;
	vfs_timestamp(&timeSpec);
	DETIMES(fatNode, &timeSpec, &timeSpec, &timeSpec);

	FileCookie* fatCookie = reinterpret_cast<FileCookie*>(cookie);
	bool changedSize = fatCookie->fLastSize != fatNode->de_FileSize ? true : false;
	bool changedTime = false;
	if (fatCookie->fMtimeAtOpen != fatNode->de_MTime
		|| fatCookie->fMdateAtOpen != fatNode->de_MDate) {
		changedTime = true;
	}
	if (changedSize || changedTime) {
		notify_stat_changed(volume->id, bsdNode->v_parent, fatNode->de_inode,
			(changedTime ? B_STAT_MODIFICATION_TIME : 0) | (changedSize ? B_STAT_SIZE : 0));
	}

	if ((bsdNode->v_mount->mnt_flag & MNT_SYNCHRONOUS) != 0)
		deupdat(fatNode, 1);

	delete fatCookie;

	return B_OK;
}


status_t
dosfs_read(fs_volume* volume, fs_vnode* vnode, void* cookie, off_t pos, void* buffer,
	size_t* length)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FileCookie* fatCookie = reinterpret_cast<FileCookie*>(cookie);

	FUNCTION_START("%" B_PRIuSIZE " bytes at %" B_PRIdOFF " (node %" B_PRIu64 " @ %p)\n", *length,
		pos, reinterpret_cast<denode*>(bsdNode->v_data)->de_inode, bsdNode);

	if ((bsdNode->v_type & VDIR) != 0) {
		*length = 0;
		return B_IS_A_DIRECTORY;
	}

	if ((fatCookie->fMode & O_RWMASK) == O_WRONLY) {
		*length = 0;
		RETURN_ERROR(B_NOT_ALLOWED);
	}

	// The userlandfs implementation of file_cache_read seems to rely on the FS to decide
	// when to stop reading - it returns B_BAD_VALUE if called again after EOF has been reached.
#if USER
	if (static_cast<u_long>(pos) >= reinterpret_cast<denode*>(bsdNode->v_data)->de_FileSize) {
		*length = 0;
		return B_OK;
	}
#endif

	RETURN_ERROR(file_cache_read(bsdNode->v_cache, fatCookie, pos, buffer, length));
}


status_t
dosfs_write(fs_volume* volume, fs_vnode* vnode, void* cookie, off_t pos, const void* buffer,
	size_t* length)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	if (pos < 0)
		return B_BAD_VALUE;

	FileCookie* fatCookie = reinterpret_cast<FileCookie*>(cookie);

	if ((fatCookie->fMode & O_RWMASK) == O_RDONLY)
		RETURN_ERROR(B_NOT_ALLOWED);

	WriteLocker locker(bsdNode->v_vnlock->haikuRW);

	FUNCTION_START("%" B_PRIuSIZE " bytes at %" B_PRIdOFF " from buffer at %p (vnode id %" B_PRIu64
		")\n", *length, pos, buffer, fatNode->de_inode);

	size_t origSize = fatNode->de_FileSize;

	switch (bsdNode->v_type) {
		case VREG:
			if ((fatCookie->fMode & O_APPEND) != 0)
				pos = fatNode->de_FileSize;
			break;
		case VDIR:
			return B_IS_A_DIRECTORY;
		default:
			RETURN_ERROR(B_BAD_VALUE);
	}

	// if they've exceeded their filesize limit, tell them about it
	if (pos >= MSDOSFS_FILESIZE_MAX)
		RETURN_ERROR(B_FILE_TOO_LARGE);

	if ((pos + *length) >= MSDOSFS_FILESIZE_MAX)
		*length = static_cast<size_t>(MSDOSFS_FILESIZE_MAX - pos);

	// if we write beyond the end of the file, extend it
	status_t status = B_OK;
	if (pos + (*length) > fatNode->de_FileSize) {
		PRINT("dosfs_write:  extending %" B_PRIu64 " to %" B_PRIdOFF " > file size %lu\n",
			fatNode->de_inode, pos + *length, fatNode->de_FileSize);

		bsdNode->v_resizing = true;
		rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
		status = B_FROM_POSIX_ERROR(deextend(fatNode, static_cast<size_t>(pos) + *length, NOCRED));
		rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
		bsdNode->v_resizing = false;
		// if there is not enough free space to extend as requested, we return here
		if (status != B_OK)
			RETURN_ERROR(status);

		PRINT("setting file size to %lu (%lu clusters)\n", fatNode->de_FileSize,
			de_clcount(fatVolume, fatNode->de_FileSize));
		ASSERT(fatNode->de_FileSize == static_cast<unsigned long>(pos) + *length);
	}

	locker.Unlock();
	status = file_cache_set_size(bsdNode->v_cache, fatNode->de_FileSize);
	if (status == B_OK) {
		status = file_cache_write(bsdNode->v_cache, fatCookie, pos, buffer, length);
		if (status != B_OK) {
			REPORT_ERROR(status);
			status = B_OK;
		}
		if (*length == 0)
			status = B_IO_ERROR;
	}
	if (status != B_OK) {
		// complete write failure
		if (origSize < fatNode->de_FileSize) {
			// return file to its previous size
			int truncFlag = ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0) ? IO_SYNC : 0;
			locker.Lock();
			rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
			status_t undoStatus = B_FROM_POSIX_ERROR(detrunc(fatNode, origSize, truncFlag, NOCRED));
			if (undoStatus != 0)
				REPORT_ERROR(undoStatus);
			rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
			locker.Unlock();
			file_cache_set_size(bsdNode->v_cache, origSize);
		}
		RETURN_ERROR(status);
	}

	// do the zeroing that is disabled in deextend
	if (static_cast<u_long>(pos) > origSize) {
		status = fill_gap_with_zeros(bsdNode, origSize, pos);
		if (status != B_OK)
			REPORT_ERROR(status);
	}

	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0) {
		status = _dosfs_fsync(bsdNode);
		if (status != B_OK)
			REPORT_ERROR(status);
	}

	if (fatNode->de_FileSize > 0 && fatNode->de_FileSize > fatCookie->fLastSize
		&& system_time() > fatCookie->fLastNotification + INODE_NOTIFICATION_INTERVAL) {
		notify_stat_changed(volume->id, bsdNode->v_parent, fatNode->de_inode,
			B_STAT_MODIFICATION_TIME | B_STAT_SIZE | B_STAT_INTERIM_UPDATE);
		fatCookie->fLastSize = fatNode->de_FileSize;
		fatCookie->fLastNotification = system_time();
	}

	return B_OK;
}


static status_t
dosfs_mkdir(fs_volume* volume, fs_vnode* parent, const char* name, int perms)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	vnode* bsdParent = reinterpret_cast<vnode*>(parent->private_node);
	denode* fatParent = reinterpret_cast<denode*>(bsdParent->v_data);

	FUNCTION_START("%" B_PRIu64 "/%s (perm %o)\n", fatParent->de_inode, name, perms);

	if (is_filename_legal(name) == false)
		RETURN_ERROR(B_BAD_VALUE);

	ComponentName bsdName(ISLASTCN, NOCRED, CREATE, 0, name);

	WriteLocker locker(bsdParent->v_vnlock->haikuRW);

	status_t status = _dosfs_access(bsdVolume, bsdParent, W_OK);
	if (status != B_OK)
		RETURN_ERROR(status);

	if (bsdParent->v_type != VDIR)
		return B_BAD_TYPE;

	bool removed = false;
	status = get_vnode_removed(volume, fatParent->de_inode, &removed);
	if (status == B_OK && removed == true)
		RETURN_ERROR(B_ENTRY_NOT_FOUND);

	// add file type information to perms
	perms &= ~S_IFMT;
	perms |= S_IFDIR;

	// set fatParent::de_fndoffset and de_fndcnt in preparation for createde
	vnode* existingBsdNode;
	status = msdosfs_lookup_ino(bsdParent, &existingBsdNode, bsdName.Data(), NULL, NULL);
	if (status == 0) {
		// a directory with this name already exists
		rw_lock_write_unlock(&existingBsdNode->v_vnlock->haikuRW);
		put_vnode(volume, (reinterpret_cast<denode*>(existingBsdNode->v_data))->de_inode);
		return B_FILE_EXISTS;
	}
	if (status != EJUSTRETURN)
		RETURN_ERROR(B_FROM_POSIX_ERROR(status));

	// If this is the FAT12/16 root directory and there is no space left we can't do anything.
	// This is because the root directory can not change size.
	if (fatParent->de_StartCluster == MSDOSFSROOT
		&& fatParent->de_fndoffset >= fatParent->de_FileSize) {
		INFORM("root directory is full and cannot be expanded\n");
		return B_UNSUPPORTED;
	}

	// allocate a cluster to hold the about to be created directory
	u_long newCluster;
	rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
	status = B_FROM_POSIX_ERROR(clusteralloc(fatVolume, 0, 1, CLUST_EOFE, &newCluster, NULL));
	rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
	if (status != B_OK)
		RETURN_ERROR(status);

	// start setting up a dummy node to convert to the new direntry
	denode newEntry;
	memset(&newEntry, 0, sizeof(newEntry));
	newEntry.de_pmp = fatVolume;
	newEntry.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
	timespec timeSpec;
	vfs_timestamp(&timeSpec);
	DETIMES(&newEntry, &timeSpec, &timeSpec, &timeSpec);

	// Now fill the cluster with the "." and ".." entries. And write the cluster to disk. This
	// way it is there for the parent directory to be pointing at if there were a crash.
	off_t startBlock = cntobn(fatVolume, newCluster);
	buf* newData = getblk(fatVolume->pm_devvp, startBlock, fatVolume->pm_bpcluster, 0, 0, 0);
	if (newData == NULL) {
		clusterfree(fatVolume, newCluster);
		RETURN_ERROR(B_ERROR);
	}
	// clear out rest of cluster to keep scandisk happy
	memset(newData->b_data, 0, fatVolume->pm_bpcluster);
	memcpy(newData->b_data, &gDirTemplate, sizeof gDirTemplate);
	direntry* childEntries = reinterpret_cast<direntry*>(newData->b_data);
	putushort(childEntries[0].deStartCluster, newCluster);
	putushort(childEntries[0].deCDate, newEntry.de_CDate);
	putushort(childEntries[0].deCTime, newEntry.de_CTime);
	childEntries[0].deCHundredth = newEntry.de_CHun;
	putushort(childEntries[0].deADate, newEntry.de_ADate);
	putushort(childEntries[0].deMDate, newEntry.de_MDate);
	putushort(childEntries[0].deMTime, newEntry.de_MTime);
	u_long parentCluster = fatParent->de_StartCluster;
	// Although the root directory has a non-magic starting cluster number for FAT32, chkdsk and
	// fsck_msdosfs still require references to it in dotdot entries to be magic.
	if (FAT32(fatVolume) == true && parentCluster == fatVolume->pm_rootdirblk)
		parentCluster = MSDOSFSROOT;
	putushort(childEntries[1].deStartCluster, parentCluster);
	putushort(childEntries[1].deCDate, newEntry.de_CDate);
	putushort(childEntries[1].deCTime, newEntry.de_CTime);
	childEntries[1].deCHundredth = newEntry.de_CHun;
	putushort(childEntries[1].deADate, newEntry.de_ADate);
	putushort(childEntries[1].deMDate, newEntry.de_MDate);
	putushort(childEntries[1].deMTime, newEntry.de_MTime);
	if (FAT32(fatVolume) == true) {
		putushort(childEntries[0].deHighClust, newCluster >> 16);
		putushort(childEntries[1].deHighClust, parentCluster >> 16);
	}

	if (DOINGASYNC(bsdParent) == true) {
		bdwrite(newData);
	} else if ((status = B_FROM_POSIX_ERROR(bwrite(newData))) != B_OK) {
		clusterfree(fatVolume, newCluster);
		RETURN_ERROR(status);
	}

	// Now build up a directory entry pointing to the newly allocated cluster. This will be
	// written to an empty slot in the parent directory.
	status = B_FROM_POSIX_ERROR(uniqdosname(fatParent, bsdName.Data(), newEntry.de_Name));
	if (status == B_OK && is_shortname_legal(newEntry.de_Name) == false)
		status = B_UNSUPPORTED;
	if (status != B_OK) {
		clusterfree(fatVolume, newCluster);
		RETURN_ERROR(status);
	}

	newEntry.de_Attributes = ATTR_DIRECTORY;
	// The FAT on-disk direntry is limited in the permissions it can store
	if ((perms & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0)
		newEntry.de_Attributes |= ATTR_READONLY;
	newEntry.de_LowerCase = 0;
	newEntry.de_StartCluster = newCluster;
	newEntry.de_FileSize = 0;

	// convert newEntry into a new direntry and write it
	rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
		// lock FAT in case parent must be extended to hold another direntry
	status = B_FROM_POSIX_ERROR(createde(&newEntry, fatParent, NULL, bsdName.Data()));
	rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
	if (status != B_OK) {
		clusterfree(fatVolume, newCluster);
		RETURN_ERROR(status);
	}

	// set up the actual node
	ino_t inode = DETOI(fatVolume, newCluster, 0);
	assign_inode(bsdVolume, &inode);
	vnode* bsdNode;
	status = _dosfs_read_vnode(bsdVolume, inode, &bsdNode);
	if (status != B_OK) {
		clusterfree(fatVolume, newCluster);
		RETURN_ERROR(status);
	}
	// parent is not accessible in _dosfs_read_vnode when the node is a directory.
	bsdNode->v_parent = fatParent->de_inode;

	status = publish_vnode(volume, inode, bsdNode, &gFATVnodeOps, S_IFDIR, 0);
	if (status != B_OK) {
		clusterfree(fatVolume, newCluster);
		RETURN_ERROR(status);
	}

	put_vnode(volume, inode);

	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
		_dosfs_fsync(bsdNode);

	entry_cache_add(volume->id, fatParent->de_inode, name, inode);

	notify_entry_created(volume->id, fatParent->de_inode, name, inode);

	return B_OK;
}


static status_t
dosfs_rmdir(fs_volume* volume, fs_vnode* parent, const char* name)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	vnode* bsdParent = reinterpret_cast<vnode*>(parent->private_node);
	denode* fatParent = reinterpret_cast<denode*>(bsdParent->v_data);

	FUNCTION_START("%s in %" B_PRIu64 " at %p\n", name, fatParent->de_inode, bsdParent);

	if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
		return B_NOT_ALLOWED;

	ComponentName bsdName(ISLASTCN, NOCRED, DELETE, 0, name);

	WriteLocker parentLocker(bsdParent->v_vnlock->haikuRW);

	daddr_t cluster;
	u_long offset;
	status_t status = B_FROM_POSIX_ERROR(
		msdosfs_lookup_ino(bsdParent, NULL, bsdName.Data(), &cluster, &offset));
	if (status != B_OK)
		RETURN_ERROR(status);

	vnode* bsdTarget;
	status = assign_inode_and_get(bsdVolume, cluster, offset, &bsdTarget);
	if (status != B_OK)
		RETURN_ERROR(status);
	WriteLocker targetLocker(bsdTarget->v_vnlock->haikuRW);
	NodePutter targetPutter(bsdTarget);
	denode* fatTarget = reinterpret_cast<denode*>(bsdTarget->v_data);

	if (bsdTarget->v_type != VDIR)
		return B_NOT_A_DIRECTORY;
	if ((bsdTarget->v_vflag & VV_ROOT) != 0)
		return B_NOT_ALLOWED;
	if (dosdirempty(fatTarget) == false)
		return B_DIRECTORY_NOT_EMPTY;

	status = _dosfs_access(bsdVolume, bsdTarget, W_OK);
	if (status != B_OK)
		RETURN_ERROR(status);

	status = B_FROM_POSIX_ERROR(removede(fatParent, fatTarget));
	if (status != B_OK)
		RETURN_ERROR(status);

	// Set the loc to a unique value. This effectively removes it from the vcache without
	// releasing its vnid for reuse. It also nicely reserves the vnid from use by other nodes.
	// This is okay because the vnode is locked in memory after this point and loc will not
	// be referenced from here on.
	status = vcache_set_entry(bsdVolume, fatTarget->de_inode, generate_unique_vnid(bsdVolume));
	if (status != B_OK)
		RETURN_ERROR(status);

	status = remove_vnode(volume, fatTarget->de_inode);
	if (status != B_OK)
		RETURN_ERROR(status);

	targetLocker.Unlock();

	if ((bsdVolume->mnt_flag & MNT_SYNCHRONOUS) != 0)
		_dosfs_sync(bsdVolume, false);

	entry_cache_remove(volume->id, fatTarget->de_inode, "..");
	entry_cache_remove(volume->id, fatParent->de_inode, name);

	notify_entry_removed(volume->id, fatParent->de_inode, name, fatTarget->de_inode);

	return B_OK;
}


static status_t
dosfs_opendir(fs_volume* volume, fs_vnode* vnode, void** _cookie)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	*_cookie = NULL;

	if ((bsdNode->v_type & VDIR) == 0)
		return B_NOT_A_DIRECTORY;

	DirCookie* cookie = new(std::nothrow) DirCookie;
	if (cookie == NULL)
		RETURN_ERROR(B_NO_MEMORY);

	cookie->fIndex = 0;

	*_cookie = cookie;

	return B_OK;
}


status_t
dosfs_closedir(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	FUNCTION_START("%p\n", vnode->private_node);

	return B_OK;
}


status_t
dosfs_free_dircookie(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	delete reinterpret_cast<DirCookie*>(cookie);

	return B_OK;
}


static status_t
dosfs_readdir(fs_volume* volume, fs_vnode* vnode, void* cookie, struct dirent* buffer,
	size_t bufferSize, uint32* _num)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdVolume->mnt_data);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	FUNCTION_START("vp %p(%" B_PRIu64 "), bufferSize %lu, entries to be read %" B_PRIu32 "\n",
		bsdNode, fatNode->de_inode, bufferSize, *_num);

	WriteLocker locker(bsdNode->v_vnlock->haikuRW);

	if ((fatNode->de_Attributes & ATTR_DIRECTORY) == 0)
		RETURN_ERROR(B_NOT_A_DIRECTORY);

	uint32 entriesRequested = *_num;
	*_num = 0;

	// struct dirent is defined differently in Haiku and FreeBSD. In the ported driver,
	// Haiku's definition is the relevant one.
	dirent* dirBuf = reinterpret_cast<dirent*>(alloca(sizeof(struct dirent) + MAXNAMLEN + 1));
	memset(dirBuf, 0, sizeof(struct dirent) + MAXNAMLEN + 1);

	char* byteBuffer = reinterpret_cast<char*>(buffer);

	// If they are reading from the root directory then, we simulate the . and .. entries since
	// these don't exist in the root directory. We also set the offset bias to make up for having
	// to simulate these entries. By this I mean that at file offset 64 we read the first entry in
	// the root directory that lives on disk.

	// directory-relative index of the current direntry or winentry;
	// it is incremented for the simulated . and .. entries in the root directory too
	uint32* entryIndex = &reinterpret_cast<DirCookie*>(cookie)->fIndex;

	int32 bias = 0;
		// disk offset = virtual offset - bias
	if (static_cast<ino_t>(fatNode->de_inode) == root_inode(fatVolume))
		bias += 2 * sizeof(direntry);

	if (*entryIndex * sizeof(direntry) >= fatNode->de_FileSize + bias)
		return B_OK;

	if (static_cast<ino_t>(fatNode->de_inode) == root_inode(fatVolume)) {
		for (; *entryIndex < 2 && *_num < entriesRequested; ++*entryIndex, ++*_num) {
			dirBuf->d_ino = fatNode->de_inode;
			dirBuf->d_dev = volume->id;
			switch (*entryIndex) {
				case 0:
					dirBuf->d_name[0] = '.';
					dirBuf->d_name[1] = '\0';
					break;
				case 1:
					dirBuf->d_name[0] = '.';
					dirBuf->d_name[1] = '.';
					dirBuf->d_name[2] = '\0';
					break;
			}
			dirBuf->d_reclen = GENERIC_DIRSIZ(dirBuf);

			if (bufferSize < dirBuf->d_reclen) {
				if (*_num == 0)
					RETURN_ERROR(B_BUFFER_OVERFLOW)
				else
					return B_OK;
			}

			memcpy(byteBuffer, dirBuf, dirBuf->d_reclen);

			bufferSize -= dirBuf->d_reclen;
			byteBuffer += dirBuf->d_reclen;
		}
	}

	buf* entriesBuf;
		// disk entries being read from
	mbnambuf longName;
		// filename after extraction and conversion from winentries
	mbnambuf_init(&longName);
	int chkSum = -1;
		// checksum of the filename
	int32 winChain = 0;
		// number of consecutive winentries we have found before reaching the corresponding
		// direntry
	bool done = false;
	status_t status = B_OK;
	while (bufferSize > 0 && *_num < entriesRequested && done == false) {
		int32 logicalCluster = de_cluster(fatVolume, (*entryIndex * sizeof(direntry)) - bias);
			// file-relative cluster number containing the next entry to read
		int32 clusterOffset = ((*entryIndex * sizeof(direntry)) - bias) & fatVolume->pm_crbomask;
			// byte offset into buf::b_data at which the inner loop starts reading
		int32 fileDiff = fatNode->de_FileSize - (*entryIndex * sizeof(direntry) - bias);
			// remaining data in the directory file
		if (fileDiff <= 0)
			break;
		int32 bytesLeft
			= min_c(static_cast<int32>(fatVolume->pm_bpcluster) - clusterOffset, fileDiff);
			// remaining data in the struct buf, excluding any area past EOF

		int readSize;
			// how many bytes to read into the struct buf at a time; usually cluster size but
			// 512 bytes for the FAT12/16 root directory
		daddr_t readBlock;
			// volume-relative index of the readSize-sized block into entriesBuf
		u_long volumeCluster;
			// volume-relative cluster number containing the next entry to read
		status = B_FROM_POSIX_ERROR(
			pcbmap(fatNode, logicalCluster, &readBlock, &volumeCluster, &readSize));
		if (status != B_OK)
			break;

		status = B_FROM_POSIX_ERROR(
			bread(fatVolume->pm_devvp, readBlock, readSize, NOCRED, &entriesBuf));
		if (status != B_OK)
			break;

		bytesLeft = min_c(bytesLeft, readSize - entriesBuf->b_resid);
		if (bytesLeft == 0) {
			brelse(entriesBuf);
			status = B_IO_ERROR;
			break;
		}

		// convert from DOS directory entries to FS-independent directory entries
		direntry* fatEntry;
		for (fatEntry = reinterpret_cast<direntry*>(entriesBuf->b_data + clusterOffset);
			reinterpret_cast<char*>(fatEntry) < entriesBuf->b_data + clusterOffset + bytesLeft
			 && *_num < entriesRequested; fatEntry++, (*entryIndex)++) {
			// fatEntry is assumed to point to a struct direntry for now, but it may in fact
			// be a struct winentry; that case will handled below

			// ff this is an unused entry, we can stop
			if (fatEntry->deName[0] == SLOT_EMPTY) {
				done = true;
				break;
			}

			// skip deleted entries
			if (fatEntry->deName[0] == SLOT_DELETED) {
				chkSum = -1;
				mbnambuf_init(&longName);
				continue;
			}

			// handle Win95 long directory entries
			if (fatEntry->deAttributes == ATTR_WIN95) {
				chkSum = win2unixfn(&longName, reinterpret_cast<winentry*>(fatEntry), chkSum,
					fatVolume);
#ifdef DEBUG
				dprintf_winentry(fatVolume, reinterpret_cast<winentry*>(fatEntry), entryIndex);
#endif
				winChain++;
				continue;
			}

			// skip volume labels
			if (fatEntry->deAttributes & ATTR_VOLUME) {
				chkSum = -1;
				mbnambuf_init(&longName);
				continue;
			}

			// Found a direntry. First, populate d_ino.
			ino_t ino;
			if (fatEntry->deAttributes & ATTR_DIRECTORY) {
				u_long entryCluster = getushort(fatEntry->deStartCluster);
				if (FAT32(fatVolume) != 0)
					entryCluster |= getushort(fatEntry->deHighClust) << 16;
				if (entryCluster == MSDOSFSROOT)
					ino = root_inode(fatVolume);
				else
					ino = DETOI(fatVolume, entryCluster, 0);
			} else {
				u_long dirOffset = *entryIndex * sizeof(direntry) - bias;
				if (IS_FIXED_ROOT(fatNode) == 0) {
					// we want a cluster-relative offset
					dirOffset = (dirOffset % fatVolume->pm_bpcluster);
				}
				ino = DETOI(fatVolume, volumeCluster, dirOffset);
			}
			status = assign_inode(bsdVolume, &ino);
			if (status != B_OK)
				break;
			dirBuf->d_ino = ino;


			dirBuf->d_dev = volume->id;

			// Is this direntry associated with a chain of previous winentries?
			if (chkSum != winChksum(fatEntry->deName)) {
				// no, just read the short file name from this direntry
				dos2unixfn(fatEntry->deName, reinterpret_cast<u_char*>(dirBuf->d_name),
					fatEntry->deLowerCase, fatVolume);
				dirBuf->d_reclen = GENERIC_DIRSIZ(dirBuf);
				mbnambuf_init(&longName);
			} else {
				// yes, use the long file name that was assembled from the previous winentry/ies
				mbnambuf_flush(&longName, dirBuf);
			}
			chkSum = -1;

			if (bufferSize < dirBuf->d_reclen) {
				if (*_num == 0) {
					RETURN_ERROR(B_BUFFER_OVERFLOW);
				} else {
					done = true;
					// rewind to the start of the chain of winentries that precedes this direntry
					*entryIndex -= winChain;
					break;
				}
			}
			winChain = 0;

			memcpy(byteBuffer, dirBuf, dirBuf->d_reclen);

			// A single VFS dirent corresponds to 0 or more FAT winentries plus 1 FAT direntry.
			// Iteration code associated with direntries is placed here, instead of in the for
			// loop header, so it won't execute when the for loop continues early
			// after a winentry is found.
			bufferSize -= dirBuf->d_reclen;
			byteBuffer += dirBuf->d_reclen;
			++*_num;
		}
		brelse(entriesBuf);
	}

#ifdef DEBUG
	PRINT("dosfs_readdir returning %" B_PRIu32 " dirents:\n", *_num);
	uint8* printCursor = reinterpret_cast<uint8*>(buffer);
	for (uint32 i = 0; i < *_num; i++) {
		dirent* bufferSlot = reinterpret_cast<dirent*>(printCursor);
		PRINT("buffer offset: %ld, d_dev: %" B_PRIdDEV ", d_ino: %" B_PRIdINO
			", d_name: %s, d_reclen: %d\n", bufferSlot - buffer, bufferSlot->d_dev,
			bufferSlot->d_ino, bufferSlot->d_name, bufferSlot->d_reclen);
		printCursor += bufferSlot->d_reclen;
	}
#endif

	RETURN_ERROR(status);
}


static status_t
dosfs_rewinddir(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	DirCookie* fatCookie = reinterpret_cast<DirCookie*>(cookie);

	FUNCTION_START("%p\n", bsdNode);

	WriteLocker locker(bsdNode->v_vnlock->haikuRW);

	fatCookie->fIndex = 0;

	return B_OK;
}


static status_t
dosfs_open_attrdir(fs_volume* volume, fs_vnode* vnode, void** _cookie)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	if (_dosfs_access(bsdVolume, bsdNode, O_RDONLY) != B_OK)
		RETURN_ERROR(B_NOT_ALLOWED);

	if ((*_cookie = new(std::nothrow) int32) == NULL)
		RETURN_ERROR(B_NO_MEMORY);

	*reinterpret_cast<int32*>(*_cookie) = 0;

	return B_OK;
}


static status_t
dosfs_close_attrdir(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	FUNCTION_START("%p\n", vnode->private_node);

	*reinterpret_cast<int32*>(cookie) = 1;

	return B_OK;
}


static status_t
dosfs_free_attrdir_cookie(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	FUNCTION_START("%p\n", vnode->private_node);

	if (cookie == NULL)
		return B_BAD_VALUE;

	delete reinterpret_cast<int32*>(cookie);

	return B_OK;
}


static status_t
dosfs_read_attrdir(fs_volume* volume, fs_vnode* vnode, void* cookie, struct dirent* buffer,
	size_t bufferSize, uint32* _num)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);
	int32* fatCookie = reinterpret_cast<int32*>(cookie);

	FUNCTION_START("%p\n", bsdNode);

	*_num = 0;

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	if ((*fatCookie == 0) && (bsdNode->v_mime != NULL)) {
		*_num = 1;
		strcpy(buffer->d_name, "BEOS:TYPE");
		buffer->d_reclen = offsetof(struct dirent, d_name) + 10;
	}

	*fatCookie = 1;

	return B_OK;
}


static status_t
dosfs_rewind_attrdir(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	FUNCTION_START("%p\n", vnode->private_node);

	if (cookie == NULL)
		return B_BAD_VALUE;

	*reinterpret_cast<int32*>(cookie) = 0;

	return B_OK;
}


static status_t
dosfs_create_attr(fs_volume* volume, fs_vnode* vnode, const char* name, uint32 type, int openMode,
	void** _cookie)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	if (_dosfs_access(bsdVolume, bsdNode, open_mode_to_access(openMode)) != B_OK)
		RETURN_ERROR(B_NOT_ALLOWED);

	if (strcmp(name, "BEOS:TYPE") != 0)
		return B_UNSUPPORTED;

	if (bsdNode->v_mime == NULL)
		return B_BAD_VALUE;

	AttrCookie* cookie = new(std::nothrow) AttrCookie;
	cookie->fMode = openMode;
	cookie->fType = FAT_ATTR_MIME;
	*_cookie = cookie;

	return B_OK;
}


static status_t
dosfs_open_attr(fs_volume* volume, fs_vnode* vnode, const char* name, int openMode, void** _cookie)
{
	mount* bsdVolume = reinterpret_cast<mount*>(volume->private_volume);
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	if (_dosfs_access(bsdVolume, bsdNode, open_mode_to_access(openMode)) != B_OK)
		RETURN_ERROR(B_NOT_ALLOWED);

	if (strcmp(name, "BEOS:TYPE") != 0)
		return B_UNSUPPORTED;

	if (bsdNode->v_mime == NULL)
		return B_BAD_VALUE;

	AttrCookie* cookie = new(std::nothrow) AttrCookie;
	cookie->fMode = openMode;
	cookie->fType = FAT_ATTR_MIME;
	*_cookie = cookie;

	return B_OK;
}


static status_t
dosfs_close_attr(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	return B_OK;
}


static status_t
dosfs_free_attr_cookie(fs_volume* volume, fs_vnode* vnode, void* cookie)
{
	delete reinterpret_cast<AttrCookie*>(cookie);

	return B_OK;
}


static status_t
dosfs_read_attr(fs_volume* volume, fs_vnode* vnode, void* cookie, off_t pos, void* buffer,
	size_t* length)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	AttrCookie* fatCookie = reinterpret_cast<AttrCookie*>(cookie);
	if (fatCookie->fType != FAT_ATTR_MIME)
		return B_NOT_ALLOWED;
	if ((fatCookie->fMode & O_RWMASK) == O_WRONLY)
		return B_NOT_ALLOWED;

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	if (bsdNode->v_mime == NULL)
		return B_BAD_VALUE;

	if ((pos < 0) || (pos > static_cast<off_t>(strlen(bsdNode->v_mime))))
		return B_BAD_VALUE;

	ssize_t copied = user_strlcpy(reinterpret_cast<char*>(buffer),
		bsdNode->v_mime + pos, *length);
	if (copied < 0)
		return B_BAD_ADDRESS;

	if (static_cast<size_t>(copied) < *length)
		*length = copied + 1;

	return B_OK;
}


/*! suck up application attempts to set mime types; this hides an unsightly
	error message printed out by zip
*/
static status_t
dosfs_write_attr(fs_volume* volume, fs_vnode* vnode, void* cookie, off_t pos, const void* buffer,
	size_t* length)
{
	FUNCTION_START("%p\n", vnode->private_node);

	AttrCookie* fatCookie = reinterpret_cast<AttrCookie*>(cookie);
	if (fatCookie->fType != FAT_ATTR_MIME)
		return B_NOT_ALLOWED;
	if ((fatCookie->fMode & O_RWMASK) == O_RDONLY)
		return B_NOT_ALLOWED;

	return B_OK;
}


static status_t
dosfs_read_attr_stat(fs_volume* volume, fs_vnode* vnode, void* cookie, struct stat* stat)
{
	struct vnode* bsdNode = reinterpret_cast<struct vnode*>(vnode->private_node);

	FUNCTION_START("%p\n", bsdNode);

	AttrCookie* fatCookie = reinterpret_cast<AttrCookie*>(cookie);
	if (fatCookie->fType != FAT_ATTR_MIME)
		return B_NOT_ALLOWED;
	if ((fatCookie->fMode & O_RWMASK) == O_WRONLY)
		return B_NOT_ALLOWED;

	ReadLocker locker(bsdNode->v_vnlock->haikuRW);

	if (bsdNode->v_mime == NULL)
		return B_BAD_VALUE;

	stat->st_type = B_MIME_STRING_TYPE;
	stat->st_size = strlen(bsdNode->v_mime) + 1;

	return B_OK;
}


status_t
dosfs_initialize(int fd, partition_id partitionID, const char* name, const char* parameterString,
	off_t partitionSize, disk_job_id job)
{
	return _dosfs_initialize(fd, partitionID, name, parameterString, partitionSize, job);
}


status_t
dosfs_uninitialize(int fd, partition_id partitionID, off_t partitionSize, uint32 blockSize,
	disk_job_id job)
{
	return _dosfs_uninitialize(fd, partitionID, partitionSize, blockSize, job);
}


/*! Initialize a FreeBSD-style struct cdev.
	@param _readOnly As input, reflects the user-selected mount options; as output, will be set to
	true if the device is read-only or device parameters are outside the driver's scope of
	read-write support.
*/
static status_t
bsd_device_init(mount* bsdVolume, const dev_t devID, const char* deviceFile, cdev** bsdDevice,
	bool* _readOnly)
{
	cdev* device = new(std::nothrow) cdev;
	if (device == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	ObjectDeleter<cdev> deviceDeleter(device);

	device->si_fd = -1;
	device->si_refcount = 0;
	device->si_mountpt = bsdVolume;
	device->si_name[0] = '\0';
	strlcpy(device->si_device, deviceFile, B_DEV_NAME_LENGTH);
	device->si_mediasize = 0;
	device->si_id = devID;

	device->si_geometry = new(std::nothrow) device_geometry;
	if (device->si_geometry == NULL)
		return B_NO_MEMORY;
	ObjectDeleter<device_geometry> geomDeleter(device->si_geometry);

	// open read-only for now
	device->si_fd = open(deviceFile, O_RDONLY | O_NOCACHE);
	if (device->si_fd < 0) {
		if (errno == B_BUSY)
			INFORM("FAT driver does not permit multiple mount points at the same time\n");
		RETURN_ERROR(B_FROM_POSIX_ERROR(errno));
	}

	// get device characteristics
	device_geometry* geometry = device->si_geometry;
	if (ioctl(device->si_fd, B_GET_GEOMETRY, geometry, sizeof(device_geometry)) == -1) {
		// support mounting disk images
		struct stat imageStat;
		if (fstat(device->si_fd, &imageStat) >= 0 && S_ISREG(imageStat.st_mode)) {
			uint8 bootSector[512];
			if (read_pos(device->si_fd, 0, bootSector, 512) != 512) {
				INFORM("bsd_device_init: bootsector read failure\n");
				close(device->si_fd);
				return B_ERROR;
			}
			geometry->bytes_per_sector = read16(bootSector, 0xb);
			geometry->sectors_per_track = 0;
			geometry->cylinder_count = 0;
			geometry->head_count = 0;
			geometry->removable = true;
			geometry->read_only = !(imageStat.st_mode & S_IWUSR);
			geometry->write_once = false;
#ifndef FS_SHELL
			dev_t imageParentDev = dev_for_path(deviceFile);
			fs_info parentInfo;
			status_t status = fs_stat_dev(imageParentDev, &parentInfo);
			if (status != 0) {
				INFORM("bsd_device_init: fs_stat failure\n");
				close(device->si_fd);
				return B_FROM_POSIX_ERROR(status);
			}
			geometry->bytes_per_physical_sector = parentInfo.block_size;
#endif
			device->si_mediasize = imageStat.st_size;
		} else {
			close(device->si_fd);
			RETURN_ERROR(B_FROM_POSIX_ERROR(errno));
		}
	} else {
		device->si_mediasize = 1ULL * geometry->head_count * geometry->cylinder_count
			* geometry->sectors_per_track * geometry->bytes_per_sector;
	}

	if (geometry->read_only) {
		PRINT("%s is read-only\n", deviceFile);
		*_readOnly = true;
	}

	if (*_readOnly == false && static_cast<uint64>(device->si_mediasize) >
		2ULL * 1000 * 1000 * 1000 * 1000) {
		// the driver has not been tested on volumes > 2 TB
		INFORM("The FAT driver does not currently support write access to volumes larger than 2 "
			"TB.\n");
		*_readOnly = true;
	}

	if (*_readOnly == false) {
		// reopen it with read/write permissions
		close(device->si_fd);
		device->si_fd = open(deviceFile, O_RDWR | O_NOCACHE);
		if (device->si_fd < 0)
			RETURN_ERROR(B_FROM_POSIX_ERROR(errno));
	}

	deviceDeleter.Detach();
	geomDeleter.Detach();

	*bsdDevice = device;

	return B_OK;
}


status_t
bsd_device_uninit(cdev* device)
{
	if (device == NULL)
		return B_OK;

	if (device->si_fd >= 0) {
		if (close(device->si_fd) != 0)
			RETURN_ERROR(B_FROM_POSIX_ERROR(errno));
	} else {
		RETURN_ERROR(B_ERROR);
	}

	delete device->si_geometry;

#ifndef FS_SHELL
	_kern_unlock_node(device->si_fd);
#endif // FS_SHELL

	delete device;

	return B_OK;
}


/*! Create a FreeBSD-format vnode representing the device, to simulate a FreeBSD VFS environment
	for the ported driver code.
*/
static status_t
dev_bsd_node_init(cdev* bsdDevice, vnode** devNode)
{
	vnode* node;
	status_t status = B_FROM_POSIX_ERROR(getnewvnode(NULL, bsdDevice->si_mountpt, NULL, &node));
	if (status != B_OK)
		RETURN_ERROR(status);

	// Set up the device node members that are accessed by the driver.
	// We don't give this node any private data.
	node->v_type = VBLK;
	node->v_rdev = bsdDevice;
	node->v_mount = NULL;
	SLIST_INIT(&node->v_bufobj.bo_clusterbufs);
	SLIST_INIT(&node->v_bufobj.bo_fatbufs);
	SLIST_INIT(&node->v_bufobj.bo_emptybufs);
	node->v_bufobj.bo_clusters = 0;
	node->v_bufobj.bo_fatblocks = 0;
	node->v_bufobj.bo_empties = 0;
	rw_lock_init(&node->v_bufobj.bo_lock.haikuRW, "FAT v_bufobj");

	*devNode = node;

	return B_OK;
}


status_t
dev_bsd_node_uninit(vnode* devNode)
{
	if (devNode == NULL)
		return B_OK;

	rw_lock_write_lock(&devNode->v_bufobj.bo_lock.haikuRW);

	// free cluster-size struct bufs:  first b_data, and then the bufs themselves
	buf* listEntry;
	SLIST_FOREACH(listEntry, &devNode->v_bufobj.bo_clusterbufs, link)
	{
		free(listEntry->b_data);
	}
	while (!SLIST_EMPTY(&devNode->v_bufobj.bo_clusterbufs)) {
		listEntry = SLIST_FIRST(&devNode->v_bufobj.bo_clusterbufs);
		SLIST_REMOVE_HEAD(&devNode->v_bufobj.bo_clusterbufs, link);
		free(listEntry);
	}

	// free the FAT-block size bufs
	listEntry = NULL;
	SLIST_FOREACH(listEntry, &devNode->v_bufobj.bo_fatbufs, link)
	{
		free(listEntry->b_data);
	}
	while (!SLIST_EMPTY(&devNode->v_bufobj.bo_fatbufs)) {
		listEntry = SLIST_FIRST(&devNode->v_bufobj.bo_fatbufs);
		SLIST_REMOVE_HEAD(&devNode->v_bufobj.bo_fatbufs, link);
		free(listEntry);
	}

	// free the bufs that were just used as pointers to the block cache
	while (!SLIST_EMPTY(&devNode->v_bufobj.bo_emptybufs)) {
		listEntry = SLIST_FIRST(&devNode->v_bufobj.bo_emptybufs);
		SLIST_REMOVE_HEAD(&devNode->v_bufobj.bo_emptybufs, link);
		free(listEntry);
	}

	rw_lock_destroy(&devNode->v_bufobj.bo_lock.haikuRW);

	free(devNode);

	return B_OK;
}


/*! Further setup will be done later for mnt_data, mnt_stat.f_mntonname, mnt_volentry,
	and mnt_cache.
*/
static status_t
bsd_volume_init(fs_volume* fsVolume, const uint32 flags, mount** volume)
{
	mount* bsdVolume = new(std::nothrow) mount;
	if (bsdVolume == NULL)
		return B_NO_MEMORY;
	ObjectDeleter<mount> volDeleter(bsdVolume);

	bsdVolume->mnt_kern_flag = 0;
	bsdVolume->mnt_flag = 0;
	if ((flags & B_MOUNT_READ_ONLY) != 0)
		bsdVolume->mnt_flag |= MNT_RDONLY;

	bsdVolume->mnt_vfc = new(std::nothrow) vfsconf;
	if ((bsdVolume)->mnt_vfc == NULL)
		return B_NO_MEMORY;
	bsdVolume->mnt_vfc->vfc_typenum = 1;
		// For the port, 1 is arbitrarily assigned as the type of this file system.
		// The member is accessed by the ported FreeBSD code but never used for anything.

	bsdVolume->mnt_stat.f_iosize = FAT_IO_SIZE;

	bsdVolume->mnt_data = NULL;

	bsdVolume->mnt_iosize_max = FAT_IO_SIZE;

	mutex_init(&bsdVolume->mnt_mtx.haikuMutex, "FAT volume");

	bsdVolume->mnt_fsvolume = fsVolume;

	bsdVolume->mnt_volentry = -1;

	if (init_vcache(bsdVolume) != B_OK) {
		mutex_destroy(&(bsdVolume)->mnt_mtx.haikuMutex);
		delete bsdVolume->mnt_vfc;
		return B_ERROR;
	}

	bsdVolume->mnt_cache = NULL;

	*volume = bsdVolume;

	volDeleter.Detach();

	return B_OK;
}


status_t
bsd_volume_uninit(struct mount* volume)
{
	if (volume == NULL)
		return B_OK;

	delete volume->mnt_vfc;

	mutex_destroy(&volume->mnt_mtx.haikuMutex);

	uninit_vcache(volume);

	delete volume;

	return B_OK;
}


/*! Set up a msdosfsmount as mount::mnt_data and initialize the block cache.

*/
static status_t
fat_volume_init(vnode* devvp, mount* bsdVolume, const uint64_t fatFlags, const char* oemPref)
{
	// Read the boot sector of the filesystem, and then check the boot signature.
	// If not a dos boot sector then error out.
	cdev* dev = devvp->v_rdev;

	uint8* bootsectorBuffer = static_cast<uint8*>(calloc(512, sizeof(char)));
	if (bootsectorBuffer == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	MemoryDeleter bootsectorDeleter(bootsectorBuffer);
	if (read(dev->si_fd, bootsectorBuffer, 512) != 512)
		RETURN_ERROR(B_IO_ERROR);

	enum FatType fatType;
	bool dos33;
	status_t status = check_bootsector(bootsectorBuffer, fatType, dos33);
	if (status != B_OK)
		RETURN_ERROR(status);

	msdosfsmount* fatVolume = new(std::nothrow) msdosfsmount;
	if (fatVolume == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	ObjectDeleter<msdosfsmount> volumeDeleter(fatVolume);

	fatVolume->pm_cp = NULL;
		// Not implemented in port

	fatVolume->pm_fsinfo = 0;
	fatVolume->pm_curfat = 0;
	fatVolume->pm_rootdirsize = 0;
	fatVolume->pm_fmod = 0;

	fatVolume->pm_mountp = bsdVolume;
	fatVolume->pm_devvp = devvp;
	fatVolume->pm_odevvp = devvp;
	fatVolume->pm_bo = &devvp->v_bufobj;
	fatVolume->pm_dev = dev;

	fatVolume->pm_flags = 0;

	switch (fatType) {
		case fat12:
			fatVolume->pm_fatmask = FAT12_MASK;
			break;
		case fat16:
			fatVolume->pm_fatmask = FAT16_MASK;
			break;
		case fat32:
			fatVolume->pm_fatmask = FAT32_MASK;
			break;
		default:
			panic("invalid FAT type\n");
	}

	fatVolume->pm_uid = geteuid();
	fatVolume->pm_gid = getegid();

	fatVolume->pm_dirmask = S_IXUSR | S_IXGRP | S_IXOTH | S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR;
	fatVolume->pm_mask = S_IXUSR | S_IXGRP | S_IXOTH | S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR;

	// populate those msdosfsmount members that are pulled directly from the BPB
	status = parse_bpb(fatVolume, reinterpret_cast<bootsector*>(bootsectorBuffer), dos33);
	if (status != B_OK)
		RETURN_ERROR(status);

	fatVolume->pm_BlkPerSec = fatVolume->pm_BytesPerSec / DEV_BSIZE;
	if (static_cast<off_t>(fatVolume->pm_HugeSectors * fatVolume->pm_BlkPerSec) * DEV_BSIZE
		> dev->si_mediasize) {
		INFORM("sector count exceeds media size (%" B_PRIdOFF " > %" B_PRIdOFF ")\n",
			static_cast<off_t>(fatVolume->pm_HugeSectors) * fatVolume->pm_BlkPerSec * DEV_BSIZE,
			dev->si_mediasize);
		return B_BAD_VALUE;
	}
	uint8 SecPerClust = fatVolume->pm_bpb.bpbSecPerClust;

	// like FreeBSD, the port uses 512-byte blocks as the primary unit of disk data
	// rather then the device-dependent sector size
	fatVolume->pm_fsinfo *= fatVolume->pm_BlkPerSec;
	if (static_cast<uint64>(fatVolume->pm_HugeSectors) * fatVolume->pm_BlkPerSec > UINT_MAX) {
		INFORM("pm_HugeSectors overflows when converting from sectors to 512-byte blocks\n");
		return B_ERROR;
	}
	fatVolume->pm_HugeSectors *= fatVolume->pm_BlkPerSec;
	fatVolume->pm_HiddenSects *= fatVolume->pm_BlkPerSec;
	fatVolume->pm_FATsecs *= fatVolume->pm_BlkPerSec;
	SecPerClust *= fatVolume->pm_BlkPerSec;

	fatVolume->pm_fatblk = fatVolume->pm_ResSectors * fatVolume->pm_BlkPerSec;

	if (FAT32(fatVolume) == true) {
		fatVolume->pm_fatmult = 4;
		fatVolume->pm_fatdiv = 1;
		fatVolume->pm_firstcluster
			= fatVolume->pm_fatblk + fatVolume->pm_FATs * fatVolume->pm_FATsecs;
	} else {
		fatVolume->pm_curfat = 0;
		fatVolume->pm_rootdirblk
			= fatVolume->pm_fatblk + fatVolume->pm_FATs * fatVolume->pm_FATsecs;
		fatVolume->pm_rootdirsize = howmany(fatVolume->pm_RootDirEnts * sizeof(direntry),
			DEV_BSIZE); // in blocks
		fatVolume->pm_firstcluster = fatVolume->pm_rootdirblk + fatVolume->pm_rootdirsize;
	}

	if (fatVolume->pm_HugeSectors <= fatVolume->pm_firstcluster)
		RETURN_ERROR(B_BAD_VALUE);

	fatVolume->pm_maxcluster
		= (fatVolume->pm_HugeSectors - fatVolume->pm_firstcluster) / SecPerClust + 1;

	if (FAT32(fatVolume) == false) {
		if (fatVolume->pm_maxcluster <= ((CLUST_RSRVD - CLUST_FIRST) & FAT12_MASK)) {
			// This will usually be a floppy disk. This size makes sure that one FAT entry will
			// not be split across multiple blocks.
			fatVolume->pm_fatmult = 3;
			fatVolume->pm_fatdiv = 2;
		} else {
			fatVolume->pm_fatmult = 2;
			fatVolume->pm_fatdiv = 1;
		}
	}

	fatVolume->pm_fatsize = fatVolume->pm_FATsecs * DEV_BSIZE;

	uint32 fatCapacity = (fatVolume->pm_fatsize / fatVolume->pm_fatmult) * fatVolume->pm_fatdiv;
	if (fatVolume->pm_maxcluster >= fatCapacity) {
		INFORM("number of clusters (%ld) exceeds FAT capacity (%" B_PRIu32 ") "
			"(some clusters are inaccessible)\n", fatVolume->pm_maxcluster + 1, fatCapacity);
		fatVolume->pm_maxcluster = fatCapacity - 1;
	}

	if (FAT12(fatVolume) != 0)
		fatVolume->pm_fatblocksize = 3 * 512;
	else
		fatVolume->pm_fatblocksize = DEV_BSIZE;
	fatVolume->pm_fatblocksize = roundup(fatVolume->pm_fatblocksize, fatVolume->pm_BytesPerSec);
	fatVolume->pm_fatblocksec = fatVolume->pm_fatblocksize / DEV_BSIZE;
	fatVolume->pm_bnshift = ffs(DEV_BSIZE) - 1;

	// compute mask and shift value for isolating cluster relative byte offsets and cluster
	// numbers from a file offset
	fatVolume->pm_bpcluster = SecPerClust * DEV_BSIZE;
	fatVolume->pm_crbomask = fatVolume->pm_bpcluster - 1;
	fatVolume->pm_cnshift = ffs(fatVolume->pm_bpcluster) - 1;

	// this will be updated later if fsinfo exists
	fatVolume->pm_nxtfree = 3;

	// check for valid cluster size - must be a power of 2
	if ((fatVolume->pm_bpcluster ^ (1 << fatVolume->pm_cnshift)) != 0)
		RETURN_ERROR(B_BAD_VALUE);

	status = check_fat(fatVolume);
	if (status != B_OK)
		RETURN_ERROR(status);

	bool readOnly = (fatFlags & MSDOSFSMNT_RONLY) != 0;

	// check that the partition is large enough to contain the file system
	if (dev->si_geometry == NULL) {
		INFORM("si_geometry not initialized\n");
		return B_ERROR;
	}

	// mkfs.fat sets the BytesPerSec value in the BPB based on the -S command line parameter,
	// not on the properties of the underlying device.
	if (dev->si_geometry->bytes_per_sector > fatVolume->pm_BytesPerSec) {
		INFORM("Volume was formatted with %u-byte sectors, but device can support %" B_PRIu32
			"-byte sectors.\n", fatVolume->pm_BytesPerSec, dev->si_geometry->bytes_per_sector);
	}

	uint32 fsSectors = fatVolume->pm_HugeSectors / fatVolume->pm_BlkPerSec;
		// convert back from 512-byte blocks to sectors
	if (fsSectors > dev->si_mediasize / fatVolume->pm_BytesPerSec) {
		INFORM("dosfs: volume extends past end of partition, mounting read-only\n");
		readOnly = true;
	}

	// check for sector count overflow in the BPB, which is only possible for FAT32 volumes
	if (FAT32(fatVolume) != 0) {
		// given the size of the FAT table, how many sectors do we expect to have in the volume?
		uint32 fatSectors = fatVolume->pm_FATsecs / fatVolume->pm_BlkPerSec;
			// convert back from 512-byte blocks to sectors
		uint32 minUsedFatSectors = fatSectors - (fatSectors / 64)
			- (SECTORS_PER_CLUSTER(fatVolume) - 1);
			// The math recommended by Microsoft to estimate required FAT sectors at initialization
			// may overestimate the requirement; mkfs.fat also aligns the FAT to cluster size
		uint32 fatEntriesPerSector = fatVolume->pm_BytesPerSec / 4;
		uint32 minFatEntries = minUsedFatSectors * fatEntriesPerSector - (fatEntriesPerSector - 1);
			// the last utilized sector of a FAT contains at least one entry
		uint64 minDataSectors = (minFatEntries - 2) * SECTORS_PER_CLUSTER(fatVolume);
			// 2 reserved cluster numbers
		uint64 minTotalSectors = fatVolume->pm_ResSectors +
			fatVolume->pm_FATsecs * fatVolume->pm_FATs + minDataSectors;
		if (fsSectors < minTotalSectors) {
			INFORM("possible sector count overflow (%" B_PRIu32 " vs. %" B_PRIu64 "), mounting "
				"read-only\n", fsSectors, minTotalSectors);
			readOnly = true;
		}
	}

	status = read_label(fatVolume, dev->si_fd, bootsectorBuffer, dev->si_name);
	if (status != B_OK)
		RETURN_ERROR(status);

	// set up the block cache to use sector-size blocks
	bsdVolume->mnt_cache
		= block_cache_create(dev->si_fd, fsSectors, fatVolume->pm_BytesPerSec, readOnly);
	if (bsdVolume->mnt_cache == NULL)
		return B_ERROR;

	status = read_fsinfo(fatVolume, devvp);
	if (status != B_OK) {
		block_cache_delete(bsdVolume->mnt_cache, false);
		return status;
	}

	bsdVolume->mnt_data = fatVolume;

	// allocate memory for the bitmap of allocated clusters, and then fill it in
	fatVolume->pm_inusemap = reinterpret_cast<u_int*>(malloc(
		howmany(fatVolume->pm_maxcluster + 1, N_INUSEBITS) * sizeof(*fatVolume->pm_inusemap)));
	if (fatVolume->pm_inusemap == NULL) {
		block_cache_delete(bsdVolume->mnt_cache, false);
		bsdVolume->mnt_data = NULL;
		RETURN_ERROR(B_NO_MEMORY);
	}
	MemoryDeleter inusemapDeleter(fatVolume->pm_inusemap);

	rw_lock_init(&fatVolume->pm_fatlock.haikuRW, "fatlock");

	// update this flag now so it will be applied in getblkx,
	// but wait to set the fatVolume flags; fillinusemap is designed to run before they are set
	if (readOnly == true)
		bsdVolume->mnt_flag |= MNT_RDONLY;

	// attempt to read the FAT into memory in advance of fillinusemap, to prevent fillinusemap
	// from doing a separate disk read for each block
	if (fatVolume->pm_FATsecs > 4) {
		size_t fatBlocks = fatVolume->pm_FATsecs;
		block_cache_prefetch(bsdVolume->mnt_cache, static_cast<off_t>(fatVolume->pm_fatblk),
			&fatBlocks);
	}

	// have the inuse map filled in
	rw_lock_write_lock(&fatVolume->pm_fatlock.haikuRW);
	status = B_FROM_POSIX_ERROR(fillinusemap(fatVolume));
	rw_lock_write_unlock(&fatVolume->pm_fatlock.haikuRW);
	if (status != 0) {
		rw_lock_destroy(&fatVolume->pm_fatlock.haikuRW);
		block_cache_delete(bsdVolume->mnt_cache, false);
		bsdVolume->mnt_data = NULL;
		RETURN_ERROR(status);
	}

	// some flags from the FreeBSD driver are not supported in the port
	ASSERT((fatVolume->pm_flags
			   & (MSDOSFSMNT_SHORTNAME | MSDOSFSMNT_LONGNAME | MSDOSFSMNT_NOWIN95 | MSDOSFS_ERR_RO))
		== 0);
	fatVolume->pm_flags |= fatFlags;

	if (readOnly == true) {
		fatVolume->pm_flags |= MSDOSFSMNT_RONLY;
	} else {
		status = B_FROM_POSIX_ERROR(markvoldirty(fatVolume, 1));
		if (status != B_OK) {
			rw_lock_destroy(&fatVolume->pm_fatlock.haikuRW);
			block_cache_delete(bsdVolume->mnt_cache, false);
			bsdVolume->mnt_data = NULL;
			RETURN_ERROR(status);
		}
		fatVolume->pm_fmod = 1;
	}

	status = iconv_init(fatVolume, oemPref);
	if (status != B_OK) {
		rw_lock_destroy(&fatVolume->pm_fatlock.haikuRW);
		block_cache_delete(bsdVolume->mnt_cache, false);
		bsdVolume->mnt_data = NULL;
		RETURN_ERROR(status);
	}

	rw_lock_init(&fatVolume->pm_checkpath_lock.haikuRW, "fat cp");

	volumeDeleter.Detach();
	inusemapDeleter.Detach();

	return B_OK;
}


/*! Clean up the msdosfsmount and the block cache.
	@pre pm_devvp and pm_dev still exist.
*/
status_t
fat_volume_uninit(msdosfsmount* volume)
{
	if (volume == NULL)
		return B_OK;

	status_t status = B_OK;
	if (((volume)->pm_flags & MSDOSFSMNT_RONLY) == 0) {
		rw_lock_write_lock(&volume->pm_fatlock.haikuRW);
		status = B_FROM_POSIX_ERROR(markvoldirty((volume), 0));
		rw_lock_write_unlock(&volume->pm_fatlock.haikuRW);
		if (status != B_OK) {
			markvoldirty((volume), 1);
			REPORT_ERROR(status);
		}
	}

	if ((volume->pm_flags & MSDOSFSMNT_KICONV) != 0 && msdosfs_iconv != NULL) {
		if (volume->pm_w2u != NULL)
			msdosfs_iconv->close(volume->pm_w2u);
		if (volume->pm_u2w != NULL)
			msdosfs_iconv->close(volume->pm_u2w);
		if (volume->pm_d2u != NULL)
			msdosfs_iconv->close(volume->pm_d2u);
		if (volume->pm_u2d != NULL)
			msdosfs_iconv->close(volume->pm_u2d);
		delete msdosfs_iconv;
		msdosfs_iconv = NULL;
	}

	status = write_fsinfo(volume);
	if (status != B_OK)
		REPORT_ERROR(status);

	if (volume->pm_mountp->mnt_cache != NULL) {
		block_cache_delete(volume->pm_mountp->mnt_cache,
			(volume->pm_flags & MSDOSFSMNT_RONLY) == 0);
		volume->pm_mountp->mnt_cache = NULL;
	}

	free(volume->pm_inusemap);

	rw_lock_destroy(&volume->pm_fatlock.haikuRW);
	rw_lock_destroy(&volume->pm_checkpath_lock.haikuRW);

	delete volume;

	return status;
}


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)
{
	vnode* bsdNode = reinterpret_cast<vnode*>(cookie);
	msdosfsmount* fatVolume = reinterpret_cast<msdosfsmount*>(bsdNode->v_mount->mnt_data);

	return file_map_translate(bsdNode->v_file_map, offset, size, vecs, _count,
		fatVolume->pm_bpcluster);
}


static status_t
iterative_io_finished_hook(void* cookie, io_request* request, status_t status, bool partialTransfer,
	size_t bytesTransferred)
{
	vnode* bsdNode = reinterpret_cast<vnode*>(cookie);
	denode* fatNode = reinterpret_cast<denode*>(bsdNode->v_data);

	rw_lock_read_unlock(&bsdNode->v_vnlock->haikuRW);
	put_vnode(bsdNode->v_mount->mnt_fsvolume, fatNode->de_inode);

	return B_OK;
}


static uint32
dosfs_get_supported_operations(partition_data* partition, uint32 mask)
{
	FUNCTION();

	return B_DISK_SYSTEM_SUPPORTS_INITIALIZING | B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME
		| B_DISK_SYSTEM_SUPPORTS_WRITING;
}


static status_t
dos_std_ops(int32 op, ...)
{
	switch (op) {
		case B_MODULE_INIT:
			FUNCTION_START("B_MODULE_INIT\n");
#ifdef _KERNEL_MODE
			add_debugger_command("fat", kprintf_volume, "dump a FAT private volume");
			add_debugger_command("fat_node", kprintf_node, "dump a FAT private node");
#endif // _KERNEL_MODE
			break;

		case B_MODULE_UNINIT:
			FUNCTION_START("B_MODULE_UNINIT\n");
#ifdef _KERNEL_MODE
			remove_debugger_command("fat", kprintf_volume);
			remove_debugger_command("fat_node", kprintf_node);
#endif // _KERNEL_MODE
			break;

		default:
			return B_ERROR;
	}

	return B_OK;
}


fs_volume_ops gFATVolumeOps = {
	&dosfs_unmount,
	&dosfs_read_fs_stat,
	&dosfs_write_fs_stat,
	&dosfs_sync,
	&dosfs_read_vnode,

	// index directory & index operations
	NULL, //&fs_open_index_dir,
	NULL, //&fs_close_index_dir,
	NULL, //&fs_free_index_dir_cookie,
	NULL, //&fs_read_index_dir,
	NULL, //&fs_rewind_index_dir,

	NULL, //&fs_create_index,
	NULL, //&fs_remove_index,
	NULL, //&fs_stat_index,

	// query operations
	NULL, //&fs_open_query,
	NULL, //&fs_close_query,
	NULL, //&fs_free_query_cookie,
	NULL, //&fs_read_query,
	NULL, //&fs_rewind_query,
};


fs_vnode_ops gFATVnodeOps = {
	// vnode operations
	&dosfs_walk,
	NULL, // fs_get_vnode_name,
	&dosfs_release_vnode,
	&dosfs_remove_vnode,

	// VM file access
	&dosfs_can_page,
	&dosfs_read_pages,
	&dosfs_write_pages,

	&dosfs_io,
	NULL, // cancel_io()

	&dosfs_get_file_map,

	NULL, // fs_ioctl()
	NULL, // fs_set_flags,
	NULL, // fs_select
	NULL, // fs_deselect
	&dosfs_fsync,

	NULL, // fs_read_symlink,
	NULL, // fs_create_symlink,

	&dosfs_link,
	&dosfs_unlink,
	&dosfs_rename,

	&dosfs_access,
	&dosfs_rstat,
	&dosfs_wstat,
	NULL, // fs_preallocate,

	// file operations
	&dosfs_create,
	&dosfs_open,
	&dosfs_close,
	&dosfs_free_cookie,
	&dosfs_read,
	&dosfs_write,

	// directory operations
	&dosfs_mkdir,
	&dosfs_rmdir,
	&dosfs_opendir,
	&dosfs_closedir,
	&dosfs_free_dircookie,
	&dosfs_readdir,
	&dosfs_rewinddir,

	// attribute directory operations
	&dosfs_open_attrdir,
	&dosfs_close_attrdir,
	&dosfs_free_attrdir_cookie,
	&dosfs_read_attrdir,
	&dosfs_rewind_attrdir,

	// attribute operations
	&dosfs_create_attr,
	&dosfs_open_attr,
	&dosfs_close_attr,
	&dosfs_free_attr_cookie,
	&dosfs_read_attr,
	&dosfs_write_attr,

	&dosfs_read_attr_stat,
	NULL, // fs_write_attr_stat,
	NULL, // fs_rename_attr,
	NULL, // fs_remove_attr
};


static file_system_module_info sFATBSDFileSystem = {
	{
		"file_systems/fat" B_CURRENT_FS_API_VERSION,
		0,
		dos_std_ops,
	},
	"fat", // short_name
	"FAT32 File System", // pretty_name

	// DDM flags
	0
		//	| B_DISK_SYSTEM_SUPPORTS_CHECKING
		//	| B_DISK_SYSTEM_SUPPORTS_REPAIRING
		//	| B_DISK_SYSTEM_SUPPORTS_RESIZING
		//	| B_DISK_SYSTEM_SUPPORTS_MOVING
		//	| B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_NAME
		//	| B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_PARAMETERS
		| B_DISK_SYSTEM_SUPPORTS_INITIALIZING
		| B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME
		//	| B_DISK_SYSTEM_SUPPORTS_DEFRAGMENTING
		//	| B_DISK_SYSTEM_SUPPORTS_DEFRAGMENTING_WHILE_MOUNTED
		//	| B_DISK_SYSTEM_SUPPORTS_CHECKING_WHILE_MOUNTED
		//	| B_DISK_SYSTEM_SUPPORTS_REPAIRING_WHILE_MOUNTED
		//	| B_DISK_SYSTEM_SUPPORTS_RESIZING_WHILE_MOUNTED
		//	| B_DISK_SYSTEM_SUPPORTS_MOVING_WHILE_MOUNTED
		//	| B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_NAME_WHILE_MOUNTED
		//	| B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_PARAMETERS_WHILE_MOUNTED
		| B_DISK_SYSTEM_SUPPORTS_WRITING,

	// scanning
	&dosfs_identify_partition,
	&dosfs_scan_partition,
	&dosfs_free_identify_partition_cookie,
	NULL, // free_partition_content_cookie

	&dosfs_mount,

	// capability querying operations
	&dosfs_get_supported_operations,

	NULL, // validate_resize
	NULL, // validate_move
	NULL, // validate_set_content_name
	NULL, // validate_set_content_parameters
	NULL, // validate_initialize

	// shadow partition modification
	NULL, // shadow_changed

	// writing
	NULL, // defragment
	NULL, // repair
	NULL, // resize
	NULL, // move
	NULL, // set_content_name
	NULL, // set_content_parameters
	dosfs_initialize,
dosfs_uninitialize};


module_info* modules[] = {
	reinterpret_cast<module_info*>(&sFATBSDFileSystem),
	NULL,
};