/*
	Copyright 1999-2001, Be Incorporated.   All Rights Reserved.
	This file may be used under the terms of the Be Sample Code License.
*/
 
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
 
#include <fs_cache.h>
#include <fs_info.h>
#include <Drivers.h>
#include <KernelExport.h>
#include <NodeMonitor.h>
 
#include <time.h>
 
#include "iter.h"
#include "dosfs.h"
#include "dlist.h"
#include "fat.h"
#include "dir.h"
#include "file.h"
#include "attr.h"
#include "vcache.h"
#include "util.h"
 
#define DPRINTF(a,b) if (debug_file > (a)) dprintf b
 
#define MAX_FILE_SIZE 0xffffffffLL
 
 
typedef struct filecookie {
	uint32		mode;		// open mode
} filecookie;
 
 
mode_t
make_mode(nspace *volume, vnode *node)
{
	mode_t result = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH;
	if (node->mode & FAT_SUBDIR) {
		result &= ~S_IFREG;
		result |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
	}
	if ((node->mode & FAT_READ_ONLY) != 0)
		result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
 
	return result;
}
 
 
status_t
dosfs_get_vnode_name(fs_volume *_ns, fs_vnode *_node, char *buffer,
	size_t bufferSize)
{
	vnode   *node = (vnode*)_node->private_node;
	strlcpy(buffer, node->filename, bufferSize);
	return B_OK;
}
 
 
status_t write_vnode_entry(nspace *vol, vnode *node)
{
	uint32 i;
	struct diri diri;
	uint8 *buffer;
 
 
	// TODO : is it needed ? vfs job ?
	// don't update entries of deleted files
	//if (is_vnode_removed(vol->id, node->vnid) > 0) return 0;
 
	// XXX: should check if directory position is still valid even
	// though we do the is_vnode_removed check above
 
	if ((node->cluster != 0) && !IS_DATA_CLUSTER(node->cluster)) {
		dprintf("write_vnode_entry called on invalid cluster (%" B_PRIu32 ")\n",
			node->cluster);
		return EINVAL;
	}
 
	buffer = diri_init(vol, VNODE_PARENT_DIR_CLUSTER(node), node->eindex, &diri);
	if (buffer == NULL)
		return ENOENT;
 
	diri_make_writable(&diri);
	buffer[0x0b] = node->mode; // file attributes
 
	memset(buffer+0xc, 0, 0x16-0xc);
	i = time_t2dos(node->st_crtim);
	buffer[0x0e] = i & 0xff;
	buffer[0x0f] = (i >> 8) & 0xff;
	buffer[0x10] = (i >> 16) & 0xff;
	buffer[0x11] = (i >> 24) & 0xff;
	i = time_t2dos(node->st_time);
	buffer[0x16] = i & 0xff;
	buffer[0x17] = (i >> 8) & 0xff;
	buffer[0x18] = (i >> 16) & 0xff;
	buffer[0x19] = (i >> 24) & 0xff;
	buffer[0x1a] = node->cluster & 0xff;	// starting cluster
	buffer[0x1b] = (node->cluster >> 8) & 0xff;
	if (vol->fat_bits == 32) {
		buffer[0x14] = (node->cluster >> 16) & 0xff;
		buffer[0x15] = (node->cluster >> 24) & 0xff;
	}
	if (node->mode & FAT_SUBDIR) {
		buffer[0x1c] = buffer[0x1d] = buffer[0x1e] = buffer[0x1f] = 0;
	} else {
		buffer[0x1c] = node->st_size & 0xff;	// file size
		buffer[0x1d] = (node->st_size >> 8) & 0xff;
		buffer[0x1e] = (node->st_size >> 16) & 0xff;
		buffer[0x1f] = (node->st_size >> 24) & 0xff;
	}
 
	diri_free(&diri);
 
	// TODO: figure out which stats have actually changed
	notify_stat_changed(vol->id, -1, node->vnid, B_STAT_MODE | B_STAT_UID
		| B_STAT_GID | B_STAT_SIZE | B_STAT_ACCESS_TIME
		| B_STAT_MODIFICATION_TIME | B_STAT_CREATION_TIME
		| B_STAT_CHANGE_TIME);
 
	return B_OK;
}
 
 
// called when fs is done with vnode
// after close, etc. free vnode resources here
status_t
dosfs_release_vnode(fs_volume *_vol, fs_vnode *_node, bool reenter)
{
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *node = (vnode *)_node->private_node;
 
	TOUCH(reenter);
 
	DPRINTF(0, ("dosfs_release_vnode (ino_t %" B_PRIdINO ")\n", node->vnid));
 
	if ((vol->fs_flags & FS_FLAGS_OP_SYNC) && node->dirty) {
		LOCK_VOL(vol);
		_dosfs_sync(vol);
		UNLOCK_VOL(vol);
	}
 
	if (node != NULL) {
#if TRACK_FILENAME
		if (node->filename) free(node->filename);
#endif
 
		if (node->vnid != vol->root_vnode.vnid) {
			file_cache_delete(node->cache);
			file_map_delete(node->file_map);
			free(node);
		}
	}
 
	return 0;
}
 
 
status_t
dosfs_rstat(fs_volume *_vol, fs_vnode *_node, struct stat *st)
{
	nspace	*vol = (nspace*)_vol->private_volume;
	vnode	*node = (vnode*)_node->private_node;
 
	LOCK_VOL(vol);
 
	DPRINTF(1, ("dosfs_rstat (vnode id %" B_PRIdINO ")\n", node->vnid));
 
	st->st_dev = vol->id;
	st->st_ino = node->vnid;
	st->st_mode = make_mode(vol, node);
 
	st->st_nlink = 1;
	st->st_uid = 0;
	st->st_gid = 0;
	st->st_size = node->st_size;
	st->st_blocks = (node->st_size + 511) / 512;
	st->st_blksize = 0x10000; /* this value was chosen arbitrarily */
	st->st_atim.tv_sec = st->st_mtim.tv_sec = st->st_ctim.tv_sec
		= node->st_time;
	st->st_crtim.tv_sec = node->st_crtim;
	st->st_atim.tv_nsec = st->st_mtim.tv_nsec = st->st_ctim.tv_nsec
		= st->st_crtim.tv_nsec = 0;
 
	UNLOCK_VOL(vol);
 
	return B_NO_ERROR;
}
 
 
status_t
dosfs_wstat(fs_volume *_vol, fs_vnode *_node, const struct stat *st,
	uint32 mask)
{
	int err = B_OK;
	nspace	*vol = (nspace*)_vol->private_volume;
	vnode	*node = (vnode*)_node->private_node;
	bool dirty = false;
 
	LOCK_VOL(vol);
 
	DPRINTF(0, ("dosfs_wstat (vnode id %" B_PRIdINO ")\n", node->vnid));
 
	if (vol->flags & B_FS_IS_READONLY) {
		dprintf("can't wstat on read-only volume\n");
		UNLOCK_VOL(vol);
		return EROFS;
	}
 
	if (node->disk_image == 2) {
		dprintf("can't wstat disk image\n");
		UNLOCK_VOL(vol);
		return EPERM;
	}
 
	if ((mask & B_STAT_MODE) != 0) {
		DPRINTF(0, ("setting file mode to %o\n", st->st_mode));
		if (st->st_mode & S_IWUSR)
			node->mode &= ~FAT_READ_ONLY;
		else
			node->mode |= FAT_READ_ONLY;
		dirty = true;
	}
 
	if ((mask & B_STAT_SIZE) != 0) {
		DPRINTF(0, ("setting file size to %" B_PRIdOFF "\n", st->st_size));
		if (node->mode & FAT_SUBDIR) {
			dprintf("dosfs_wstat: can't set file size of directory!\n");
			err = EISDIR;
		} else if (st->st_size > MAX_FILE_SIZE) {
			dprintf("dosfs_wstat: desired file size exceeds fat limit\n");
			err = E2BIG;
		} else {
			uint32 clusters = (st->st_size + vol->bytes_per_sector
					* vol->sectors_per_cluster - 1) / vol->bytes_per_sector
				/ vol->sectors_per_cluster;
			DPRINTF(0, ("setting fat chain length to %" B_PRIu32 " clusters\n",
				clusters));
			if ((err = set_fat_chain_length(vol, node, clusters, false))
					== B_OK) {
				node->st_size = st->st_size;
				node->iteration++;
				dirty = true;
				file_cache_set_size(node->cache, node->st_size);
				file_map_set_size(node->file_map, node->st_size);
			}
		}
	}
 
	if ((mask & B_STAT_MODIFICATION_TIME) != 0) {
		DPRINTF(0, ("setting modification time\n"));
		if ((node->mode & FAT_SUBDIR) == 0)
			node->mode |= FAT_ARCHIVE;
		node->st_time = st->st_mtime;
		dirty = true;
	}
 
	if ((mask & B_STAT_CREATION_TIME) != 0) {
		DPRINTF(0, ("setting creation time\n"));
		// As a file's modification time is also set when it is created,
		// the archive bit should be set automatically.
		node->st_crtim = st->st_crtime;
		dirty = true;
	}
 
	if (dirty) {
		write_vnode_entry(vol, node);
 
		if (vol->fs_flags & FS_FLAGS_OP_SYNC) {
			// sync the filesystem
			_dosfs_sync(vol);
			node->dirty = false;
		}
	}
 
	if (err != B_OK) DPRINTF(0, ("dosfs_wstat (%s)\n", strerror(err)));
 
	UNLOCK_VOL(vol);
 
	return err;
}
 
 
status_t
dosfs_open(fs_volume *_vol, fs_vnode *_node, int omode, void **_cookie)
{
	status_t	result = EINVAL;
	nspace *vol = (nspace *)_vol->private_volume;
	vnode* 	node = (vnode*)_node->private_node;
	filecookie *cookie;
 
	*_cookie = NULL;
 
	LOCK_VOL(vol);
 
	DPRINTF(0, ("dosfs_open: vnode id %" B_PRIdINO ", omode %o\n", node->vnid,
		omode));
 
	if (omode & O_CREAT) {
		dprintf("dosfs_open called with O_CREAT. call dosfs_create instead!\n");
		result = EINVAL;
		goto error;
	}
 
	if ((vol->flags & B_FS_IS_READONLY) ||
		(node->mode & FAT_READ_ONLY) ||
		(node->disk_image != 0) ||
		// allow opening directories for ioctl() calls
		// and to let BVolume to work
		(node->mode & FAT_SUBDIR)) {
		omode = (omode & ~O_RWMASK) | O_RDONLY;
	}
 
	if ((omode & O_TRUNC) && ((omode & O_RWMASK) == O_RDONLY)) {
		DPRINTF(0, ("can't open file for reading with O_TRUNC\n"));
		result = EPERM;
		goto error;
	}
 
	if (omode & O_TRUNC) {
		DPRINTF(0, ("dosfs_open called with O_TRUNC set\n"));
		if ((result = set_fat_chain_length(vol, node, 0, false)) != B_OK) {
			dprintf("dosfs_open: error truncating file\n");
			goto error;
		}
		node->mode = 0;
		node->st_size = 0;
		node->iteration++;
	}
 
	if ((cookie = calloc(sizeof(filecookie), 1)) == NULL) {
		result = ENOMEM;
		goto error;
	}
 
	cookie->mode = omode;
	*_cookie = cookie;
	result = B_OK;
 
error:
	if (result != B_OK) DPRINTF(0, ("dosfs_open (%s)\n", strerror(result)));
 
	UNLOCK_VOL(vol);
	return result;
}
 
 
status_t
dosfs_read(fs_volume *_vol, fs_vnode *_node, void *_cookie, off_t pos,
			void *buf, size_t *len)
{
	nspace	*vol = (nspace *)_vol->private_volume;
	vnode	*node = (vnode *)_node->private_node;
	filecookie *cookie = (filecookie *)_cookie;
	int result = B_OK;
 
	LOCK_VOL(vol);
 
	if (node->mode & FAT_SUBDIR) {
		DPRINTF(0, ("dosfs_read called on subdirectory %" B_PRIdINO "\n",
			node->vnid));
		*len = 0;
		UNLOCK_VOL(vol);
		return EISDIR;
	}
 
	DPRINTF(0, ("dosfs_read called %" B_PRIuSIZE " bytes at %" B_PRIdOFF
		" (vnode id %" B_PRIdINO ")\n", *len, pos, node->vnid));
 
	result = file_cache_read(node->cache, cookie, pos, buf, len);
 
	if (result != B_OK) {
		DPRINTF(0, ("dosfs_read (%s)\n", strerror(result)));
	} else {
		DPRINTF(0, ("dosfs_read: read %" B_PRIuSIZE " bytes\n", *len));
	}
	UNLOCK_VOL(vol);
 
	return result;
}
 
 
status_t
dosfs_write(fs_volume *_vol, fs_vnode *_node, void *_cookie, off_t pos,
	const void *buf, size_t *len)
{
	nspace	*vol = (nspace *)_vol->private_volume;
	vnode	*node = (vnode *)_node->private_node;
	filecookie *cookie = (filecookie *)_cookie;
	int result = B_OK;
 
	LOCK_VOL(vol);
 
	if ((vol->flags & B_FS_IS_READONLY) != 0) {
		UNLOCK_VOL(vol);
		return EROFS;
	}
 
	if (node->mode & FAT_SUBDIR) {
		DPRINTF(0, ("dosfs_write called on subdirectory %" B_PRIdINO "\n",
			node->vnid));
		*len = 0;
		UNLOCK_VOL(vol);
		return EISDIR;
 
	}
 
	DPRINTF(0, ("dosfs_write called %" B_PRIuSIZE " bytes at %" B_PRIdOFF
		" from buffer at %p (vnode id %" B_PRIdINO ")\n", *len, pos, buf,
		node->vnid));
 
	if ((cookie->mode & O_RWMASK) == O_RDONLY) {
		dprintf("dosfs_write: called on file opened as read-only\n");
		*len = 0;
		result = EPERM;
		goto bi;
	}
 
	if (pos < 0) pos = 0;
 
	if (cookie->mode & O_APPEND) {
		pos = node->st_size;
	}
 
	if (pos >= MAX_FILE_SIZE) {
		dprintf("dosfs_write: write position exceeds fat limits\n");
		*len = 0;
		result = E2BIG;
		goto bi;
	}
 
	if (pos + *len >= MAX_FILE_SIZE) {
		*len = (size_t)(MAX_FILE_SIZE - pos);
	}
 
	// extend file size if needed
	if (pos + *len > node->st_size) {
		uint32 clusters = (pos + *len + vol->bytes_per_sector*vol->sectors_per_cluster - 1) / vol->bytes_per_sector / vol->sectors_per_cluster;
		if (node->st_size <= (clusters - 1) * vol->sectors_per_cluster * vol->bytes_per_sector) {
			if ((result = set_fat_chain_length(vol, node, clusters, false))
					!= B_OK) {
				goto bi;
			}
			node->iteration++;
		}
		node->st_size = pos + *len;
		/* needs to be written to disk asap so that later vnid calculations
		 * by get_next_dirent are correct
		 */
		write_vnode_entry(vol, node);
 
		DPRINTF(0, ("setting file size to %" B_PRIdOFF " (%" B_PRIu32
			" clusters)\n", node->st_size, clusters));
		node->dirty = true;
		file_cache_set_size(node->cache, node->st_size);
		file_map_set_size(node->file_map, node->st_size);
	}
 
	result = file_cache_write(node->cache, cookie, pos, buf, len);
 
bi:
	if (result != B_OK) {
		DPRINTF(0, ("dosfs_write (%s)\n", strerror(result)));
	} else {
		DPRINTF(0, ("dosfs_write: wrote %" B_PRIuSIZE " bytes\n", *len));
	}
	UNLOCK_VOL(vol);
 
	return result;
}
 
 
status_t
dosfs_close(fs_volume *_vol, fs_vnode *_node, void *_cookie)
{
	nspace	*vol = (nspace *)_vol->private_volume;
	vnode	*node = (vnode *)_node->private_node;
 
	LOCK_VOL(vol);
 
	DPRINTF(0, ("dosfs_close (vnode id %" B_PRIdINO ")\n", node->vnid));
 
	if ((vol->fs_flags & FS_FLAGS_OP_SYNC) && node->dirty) {
		_dosfs_sync(vol);
		node->dirty = false;
	}
 
	UNLOCK_VOL(vol);
 
	return 0;
}
 
 
status_t
dosfs_free_cookie(fs_volume *_vol, fs_vnode *_node, void *_cookie)
{
	nspace *vol = _vol->private_volume;
	vnode *node = _node->private_node;
	filecookie *cookie = _cookie;
	LOCK_VOL(vol);
 
	DPRINTF(0, ("dosfs_free_cookie (vnode id %" B_PRIdINO ")\n", node->vnid));
 
	free(cookie);
 
	UNLOCK_VOL(vol);
 
	return 0;
}
 
 
status_t
dosfs_create(fs_volume *_vol, fs_vnode *_dir, const char *name, int omode,
	int perms, void **_cookie, ino_t *vnid)
{
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *dir = (vnode *)_dir->private_node, *file;
	filecookie *cookie;
	status_t result = EINVAL;
	bool dups_exist;
 
	LOCK_VOL(vol);
 
	ASSERT(name != NULL);
	if (name == NULL) {
		dprintf("dosfs_create called with null name\n");
		UNLOCK_VOL(vol);
		return EINVAL;
	}
 
	DPRINTF(0, ("dosfs_create called: %" B_PRIdINO "/%s perms=%o omode=%o\n",
		dir->vnid, name, perms, omode));
 
	if (vol->flags & B_FS_IS_READONLY) {
		dprintf("dosfs_create called on read-only volume\n");
		UNLOCK_VOL(vol);
		return EROFS;
	}
 
	// TODO : is it needed ? vfs job ?
	/*if (is_vnode_removed(vol->id, dir->vnid) > 0) {
		dprintf("dosfs_create() called in removed directory. disallowed.\n");
		UNLOCK_VOL(vol);
		return EPERM;
	}*/
 
	if ((omode & O_RWMASK) == O_RDONLY) {
		dprintf("invalid permissions used in creating file\n");
		UNLOCK_VOL(vol);
		return EPERM;
	}
 
	// create file cookie; do it here to make cleaning up easier
	if ((cookie = calloc(sizeof(filecookie), 1)) == NULL) {
		result = ENOMEM;
		goto bi;
	}
 
	result = findfile_case_duplicates(vol, dir, name, vnid, &file, &dups_exist);
	if (result == B_OK) {
		if (omode & O_EXCL) {
			dprintf("exclusive dosfs_create called on existing file %s\n", name);
			put_vnode(_vol, file->vnid);
			result = EEXIST;
			goto bi;
		}
 
		if (file->mode & FAT_SUBDIR) {
			dprintf("can't dosfs_create over an existing subdirectory\n");
			put_vnode(_vol, file->vnid);
			result = EPERM;
			goto bi;
		}
 
		if (file->disk_image) {
			dprintf("can't dosfs_create over a disk image\n");
			put_vnode(_vol, file->vnid);
			result = EPERM;
			goto bi;
		}
 
		if (omode & O_TRUNC) {
			set_fat_chain_length(vol, file, 0, false);
			file->st_size = 0;
			file->iteration++;
		}
	} else if (result == ENOENT && dups_exist) {
		// the file doesn't exist in the exact case, but another does in the
		// non-exact case. We wont create the new file.
		result = EEXIST;
		goto bi;
	} else if (result == ENOENT && !dups_exist) {
		// the file doesn't already exist in any case
		vnode dummy; /* used only to create directory entry */
 
		dummy.dir_vnid = dir->vnid;
		dummy.cluster = 0;
		dummy.end_cluster = 0;
		dummy.mode = 0;
		dummy.st_size = 0;
		time(&(dummy.st_time));
		dummy.st_crtim = dummy.st_time;
 
		if ((result = create_dir_entry(vol, dir, &dummy, name, &(dummy.sindex), &(dummy.eindex))) != B_OK) {
			dprintf("dosfs_create: error creating directory entry for %s (%s)\n", name, strerror(result));
			goto bi;
		}
		dummy.vnid = GENERATE_DIR_INDEX_VNID(dummy.dir_vnid, dummy.sindex);
		// XXX: dangerous construct
		if (find_vnid_in_vcache(vol, dummy.vnid) == B_OK) {
			dummy.vnid = generate_unique_vnid(vol);
			if ((result = add_to_vcache(vol, dummy.vnid, GENERATE_DIR_INDEX_VNID(dummy.dir_vnid, dummy.sindex))) < 0) {
				// XXX: should remove entry on failure
				if (vol->fs_flags & FS_FLAGS_OP_SYNC)
					_dosfs_sync(vol);
				goto bi;
			}
		}
		*vnid = dummy.vnid;
 
		result = get_vnode(_vol, *vnid, (void **)&file);
		if (result < B_OK) {
			if (vol->fs_flags & FS_FLAGS_OP_SYNC)
				_dosfs_sync(vol);
			goto bi;
		}
	} else {
		goto bi;
	}
 
	cookie->mode = omode;
	*_cookie = cookie;
 
	notify_entry_created(vol->id, dir->vnid, name, *vnid);
 
	result = 0;
 
	if (vol->fs_flags & FS_FLAGS_OP_SYNC)
		_dosfs_sync(vol);
 
bi:	if (result != B_OK) free(cookie);
 
	UNLOCK_VOL(vol);
 
	if (result != B_OK) DPRINTF(0, ("dosfs_create (%s)\n", strerror(result)));
 
	return result;
}
 
 
status_t
dosfs_mkdir(fs_volume *_vol, fs_vnode *_dir, const char *name, int perms)
{
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *dir = (vnode *)_dir->private_node, dummy;
	status_t result = EINVAL;
	struct csi csi;
	uchar *buffer;
	uint32 i;
 
	LOCK_VOL(vol);
 
	// TODO : is it needed ? vfs job ?
	/*if (is_vnode_removed(vol->id, dir->vnid) > 0) {
		dprintf("dosfs_mkdir() called in removed directory. disallowed.\n");
		UNLOCK_VOL(vol);
		return EPERM;
	}*/
 
	DPRINTF(0, ("dosfs_mkdir called: %" B_PRIdINO "/%s (perm %o)\n", dir->vnid,
		name, perms));
 
	if ((dir->mode & FAT_SUBDIR) == 0) {
		dprintf("dosfs_mkdir: vnode id %" B_PRIdINO " is not a directory\n",
			dir->vnid);
		UNLOCK_VOL(vol);
		return EINVAL;
	}
 
	// S_IFDIR is never set in perms, so we patch it
	perms &= ~S_IFMT; perms |= S_IFDIR;
 
	if (vol->flags & B_FS_IS_READONLY) {
		dprintf("mkdir called on read-only volume\n");
		UNLOCK_VOL(vol);
		return EROFS;
	}
 
	/* only used to create directory entry */
	dummy.dir_vnid = dir->vnid;
	if ((result = allocate_n_fat_entries(vol, 1, (int32 *)&(dummy.cluster))) < 0) {
		dprintf("dosfs_mkdir: error allocating space for %s (%s))\n", name, strerror(result));
		goto bi;
	}
	dummy.end_cluster = dummy.cluster;
	dummy.mode = FAT_SUBDIR;
	if (!(perms & (S_IWUSR | S_IWGRP | S_IWGRP))) {
		dummy.mode |= FAT_READ_ONLY;
	}
	dummy.st_size = vol->bytes_per_sector*vol->sectors_per_cluster;
	time(&(dummy.st_time));
	dummy.st_crtim = dummy.st_time;
 
	dummy.vnid = GENERATE_DIR_CLUSTER_VNID(dummy.dir_vnid, dummy.cluster);
	// XXX: dangerous construct
	if (find_vnid_in_vcache(vol, dummy.vnid) == B_OK) {
		dummy.vnid = generate_unique_vnid(vol);
		if ((result = add_to_vcache(vol, dummy.vnid, GENERATE_DIR_CLUSTER_VNID(dummy.dir_vnid, dummy.cluster))) < 0)
			goto bi2;
	}
 
	if ((result = dlist_add(vol, dummy.vnid)) < 0) {
		dprintf("dosfs_mkdir: error adding directory %s to dlist (%s)\n", name, strerror(result));
		goto bi3;
	}
 
	buffer = malloc(vol->bytes_per_sector);
	if (!buffer) {
		result = ENOMEM;
		goto bi4;
	}
 
	if ((result = create_dir_entry(vol, dir, &dummy, name, &(dummy.sindex), &(dummy.eindex))) != B_OK) {
		dprintf("dosfs_mkdir: error creating directory entry for %s (%s))\n", name, strerror(result));
		goto bi5;
	}
 
	// create '.' and '..' entries and then end of directories
	memset(buffer, 0, vol->bytes_per_sector);
	memset(buffer, ' ', 11);
	memset(buffer+0x20, ' ', 11);
	buffer[0] = buffer[0x20] = buffer[0x21] = '.';
	buffer[0x0b] = buffer[0x2b] = FAT_SUBDIR;
	i = time_t2dos(dummy.st_time);
	buffer[0x0e] = i & 0xff;
	buffer[0x0f] = (i >> 8) & 0xff;
	buffer[0x10] = (i >> 16) & 0xff;
	buffer[0x11] = (i >> 24) & 0xff;
	buffer[0x16] = i & 0xff;
	buffer[0x17] = (i >> 8) & 0xff;
	buffer[0x18] = (i >> 16) & 0xff;
	buffer[0x19] = (i >> 24) & 0xff;
	i = time_t2dos(dir->st_crtim);
	buffer[0x2e] = i & 0xff;
	buffer[0x2f] = (i >> 8) & 0xff;
	buffer[0x30] = (i >> 16) & 0xff;
	buffer[0x31] = (i >> 24) & 0xff;
	i = time_t2dos(dir->st_time);
	buffer[0x36] = i & 0xff;
	buffer[0x37] = (i >> 8) & 0xff;
	buffer[0x38] = (i >> 16) & 0xff;
	buffer[0x39] = (i >> 24) & 0xff;
	buffer[0x1a] = dummy.cluster & 0xff;
	buffer[0x1b] = (dummy.cluster >> 8) & 0xff;
	if (vol->fat_bits == 32) {
		buffer[0x14] = (dummy.cluster >> 16) & 0xff;
		buffer[0x15] = (dummy.cluster >> 24) & 0xff;
	}
	// root directory is always denoted by cluster 0, even for fat32 (!)
	if (dir->vnid != vol->root_vnode.vnid) {
		buffer[0x3a] = dir->cluster & 0xff;
		buffer[0x3b] = (dir->cluster >> 8) & 0xff;
		if (vol->fat_bits == 32) {
			buffer[0x34] = (dir->cluster >> 16) & 0xff;
			buffer[0x35] = (dir->cluster >> 24) & 0xff;
		}
	}
 
	init_csi(vol, dummy.cluster, 0, &csi);
	csi_write_block(&csi, buffer);
 
	// clear out rest of cluster to keep scandisk happy
	memset(buffer, 0, vol->bytes_per_sector);
	for (i=1;i<vol->sectors_per_cluster;i++) {
		if (iter_csi(&csi, 1) != B_OK) {
			dprintf("dosfs_mkdir: error writing directory cluster\n");
			break;
		}
		csi_write_block(&csi, buffer);
	}
 
	free(buffer);
 
	notify_entry_created(vol->id, dir->vnid, name, dummy.vnid);
 
	result = B_OK;
 
	if (vol->fs_flags & FS_FLAGS_OP_SYNC)
		_dosfs_sync(vol);
 
	UNLOCK_VOL(vol);
	return result;
 
bi5:
	free(buffer);
bi4:
	dlist_remove(vol, dummy.vnid);
bi3:
	if (IS_ARTIFICIAL_VNID(dummy.vnid))
		remove_from_vcache(vol, dummy.vnid);
bi2:
	clear_fat_chain(vol, dummy.cluster, false);
	if (vol->fs_flags & FS_FLAGS_OP_SYNC)
		_dosfs_sync(vol);
bi:
	UNLOCK_VOL(vol);
	if (result != B_OK) DPRINTF(0, ("dosfs_mkdir (%s)\n", strerror(result)));
	return result;
}
 
 
status_t
dosfs_rename(fs_volume *_vol, fs_vnode *_odir, const char *oldname,
	fs_vnode *_ndir, const char *newname)
{
	status_t result = EINVAL;
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *odir = (vnode *)_odir->private_node;
	vnode *ndir = (vnode *)_ndir->private_node;
	vnode *file, *file2;
	uint32 ns, ne;
	bool dups_exist;
	bool dirty = false;
 
	LOCK_VOL(vol);
 
	DPRINTF(0, ("dosfs_rename called: %" B_PRIdINO "/%s->%" B_PRIdINO "/%s\n",
		odir->vnid, oldname, ndir->vnid, newname));
 
	if (!oldname || !(*oldname) || !newname || !(*newname)) {
		result = EINVAL;
		goto bi;
	}
 
	if(!is_filename_legal(newname)) {
		dprintf("dosfs_rename called with invalid name '%s'\n", newname);
		result = EINVAL;
		goto bi;
	}
 
	if (vol->flags & B_FS_IS_READONLY) {
		dprintf("rename called on read-only volume\n");
		result = EROFS;
		goto bi;
	}
 
	if ((odir->vnid == ndir->vnid) && !strcmp(oldname, newname)) {
		result = EPERM;
		goto bi;
	}
 
	// locate the file
	if ((result = findfile_case(vol,odir,oldname,NULL,&file)) != B_OK) {
		DPRINTF(0, ("dosfs_rename: can't find file %s in directory %" B_PRIdINO
			"\n", oldname, odir->vnid));
		goto bi;
	}
 
	if (file->disk_image) {
		dprintf("rename called on disk image or disk image directory\n");
		result = EPERM;
		goto bi1;
	}
 
	// don't move a directory into one of its children
	if (file->mode & FAT_SUBDIR) {
		ino_t vnid = ndir->vnid;
		while (1) {
			vnode *dir;
			ino_t parent;
 
			if (vnid == file->vnid) {
				result = EINVAL;
				goto bi1;
			}
 
			if (vnid == vol->root_vnode.vnid)
				break;
 
			result = get_vnode(_vol, vnid, (void **)&dir);
			if (result < B_OK)
				goto bi1;
			parent = dir->dir_vnid;
			put_vnode(_vol, vnid);
			vnid = parent;
		}
	}
 
	// see if file already exists and erase it if it does
	result = findfile_case_duplicates(vol, ndir, newname, NULL, &file2, &dups_exist);
	if (result == B_OK) {
		if (file2->mode & FAT_SUBDIR) {
			dprintf("destination already occupied by a directory\n");
			result = EPERM;
			goto bi2;
		}
 
		if (file2->disk_image) {
			DPRINTF(0, ("dosfs_rename: can't replace disk image or disk image directory\n"));
			result = EPERM;
			goto bi2;
		}
		ns = file2->sindex; ne = file2->eindex;
 
		// let others know the old file is gone
		notify_entry_removed(vol->id, ndir->vnid, oldname, file2->vnid);
 
		// Make sure this vnode 1) is in the vcache and 2) no longer has a
		// location associated with it. See discussion in dosfs_unlink()
		vcache_set_entry(vol, file2->vnid, generate_unique_vnid(vol));
 
		// mark vnode for removal (dosfs_remove_vnode will clear the fat chain)
		// note we don't have to lock the file because the fat chain doesn't
		// get wiped from the disk until dosfs_remove_vnode() is called; we'll
		// have a phantom chain in effect until the last file is closed.
		remove_vnode(_vol, file2->vnid); // must be done in this order
		put_vnode(_vol, file2->vnid);
 
		dirty = true;
 
		// erase old directory entry
		if ((result = erase_dir_entry(vol, file)) != B_OK) {
			dprintf("dosfs_rename: error erasing old directory entry for %s (%s)\n", newname, strerror(result));
			goto bi1;
		}
	} else if (result == ENOENT && (!dups_exist || (odir->vnid == ndir->vnid && !strcasecmp(oldname, newname)))) {
		// there isn't an entry and there are no duplicates in the target dir or
		// there isn't an entry and the target dir is the same as the source dir and
		//   the source and target name are the same, case-insensitively
 
		// erase old directory entry
		if ((result = erase_dir_entry(vol, file)) != B_OK) {
			dprintf("dosfs_rename: error erasing old directory entry for %s (%s)\n", newname, strerror(result));
			goto bi1;
		}
 
		dirty = true;
 
		// create the new directory entry
		if ((result = create_dir_entry(vol, ndir, file, newname, &ns, &ne)) != B_OK) {
			dprintf("dosfs_rename: error creating directory entry for %s\n", newname);
			goto bi1;
		}
	} else if (result == ENOENT && dups_exist) {
		// the entry doesn't exist but a non-case entry does, so we can't do it
		result = EEXIST;
		goto bi1;
	} else {
		goto bi1;
	}
 
	// shrink the directory (an error here is not disastrous)
	compact_directory(vol, odir);
 
	dirty = true;
 
	// update vnode information
	file->dir_vnid = ndir->vnid;
	file->sindex = ns;
	file->eindex = ne;
 
	// update vcache
	vcache_set_entry(vol, file->vnid,
			(file->st_size) ?
			GENERATE_DIR_CLUSTER_VNID(file->dir_vnid, file->cluster) :
			GENERATE_DIR_INDEX_VNID(file->dir_vnid, file->sindex));
 
	// XXX: only write changes in the directory entry if needed
	//      (i.e. old entry, not new)
	write_vnode_entry(vol, file);
 
	if (file->mode & FAT_SUBDIR) {
		// update '..' directory entry if needed
		// this should most properly be in write_vnode, but it is safe
		// to keep it here since this is the only way the cluster of
		// the parent can change.
		struct diri diri;
		uint8 *buffer;
		if ((buffer = diri_init(vol, file->cluster, 1, &diri)) == NULL) {
			dprintf("error opening directory :(\n");
			result = EIO;
			goto bi2;
		}
 
		diri_make_writable(&diri);
 
		if (memcmp(buffer, "..         ", 11)) {
			dprintf("invalid directory :(\n");
			result = EIO;
			goto bi2;
		}
		if (ndir->vnid == vol->root_vnode.vnid) {
			// root directory always has cluster = 0
			buffer[0x1a] = buffer[0x1b] = 0;
		} else {
			buffer[0x1a] = ndir->cluster & 0xff;
			buffer[0x1b] = (ndir->cluster >> 8) & 0xff;
			if (vol->fat_bits == 32) {
				buffer[0x14] = (ndir->cluster >> 16) & 0xff;
				buffer[0x15] = (ndir->cluster >> 24) & 0xff;
			}
		}
		diri_free(&diri);
	}
 
#if TRACK_FILENAME
	if (file->filename) free(file->filename);
	file->filename = malloc(strlen(newname) + 1);
	if (file->filename) strcpy(file->filename, newname);
#endif
 
	notify_entry_moved(vol->id, odir->vnid, oldname, ndir->vnid, newname,
		file->vnid);
 
	// update MIME information
	if(!(file->mode & FAT_SUBDIR)) {
		set_mime_type(file, newname);
		notify_attribute_changed(vol->id, -1, file->vnid, "BEOS:TYPE",
			B_ATTR_CHANGED);
	}
 
	result = 0;
 
bi2:
	if (result != B_OK)
		put_vnode(_vol, file2->vnid);
bi1:
	put_vnode(_vol, file->vnid);
bi:
	if ((vol->fs_flags & FS_FLAGS_OP_SYNC) && dirty)
		_dosfs_sync(vol);
	UNLOCK_VOL(vol);
	if (result != B_OK) DPRINTF(0, ("dosfs_rename (%s)\n", strerror(result)));
	return result;
}
 
 
status_t
dosfs_remove_vnode(fs_volume *_vol, fs_vnode *_node, bool reenter)
{
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *node = (vnode *)_node->private_node;
 
	LOCK_VOL(vol);
 
	DPRINTF(0, ("dosfs_remove_vnode (%" B_PRIdINO ")\n", node->vnid));
 
	if (vol->flags & B_FS_IS_READONLY) {
		dprintf("dosfs_remove_vnode: read-only volume\n");
		UNLOCK_VOL(vol);
		return EROFS;
	}
 
	// clear the fat chain
	ASSERT((node->cluster == 0) || IS_DATA_CLUSTER(node->cluster));
	/* XXX: the following assertion was tripped */
	ASSERT((node->cluster != 0) || (node->st_size == 0));
	if (node->cluster != 0)
		clear_fat_chain(vol, node->cluster, (node->mode & FAT_SUBDIR) != 0);
 
	/* remove vnode id from the cache */
	if (find_vnid_in_vcache(vol, node->vnid) == B_OK)
		remove_from_vcache(vol, node->vnid);
 
	/* at this point, the node shouldn't be in the dlist anymore */
	if ((node->mode & FAT_SUBDIR) != 0) {
		ASSERT(dlist_find(vol, CLUSTER_OF_DIR_CLUSTER_VNID(node->vnid)) == -1);
	}
 
	free(node);
 
	if (!reenter && vol->fs_flags & FS_FLAGS_OP_SYNC) {
		// sync the entire filesystem,
		// but only if we're not reentrant. Presumably the
		// function that called this will sync.
		_dosfs_sync(vol);
	}
 
	UNLOCK_VOL(vol);
 
	return B_OK;
}
 
 
// get rid of node or directory
static status_t
do_unlink(fs_volume *_vol, fs_vnode *_dir, const char *name, bool is_file)
{
	status_t result = EINVAL;
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *dir = (vnode *)_dir->private_node, *file;
	ino_t vnid;
 
	if (!strcmp(name, "."))
		return EPERM;
 
	if (!strcmp(name, ".."))
		return EPERM;
 
	LOCK_VOL(vol);
 
	DPRINTF(0, ("do_unlink %" B_PRIdINO "/%s\n", dir->vnid, name));
 
	if (vol->flags & B_FS_IS_READONLY) {
		dprintf("do_unlink: read-only volume\n");
		result = EROFS;
		goto bi;
	}
 
	// locate the file
	if ((result = findfile_case(vol,dir,name,&vnid,&file)) != B_OK) {
		DPRINTF(0, ("do_unlink: can't find file %s in directory %" B_PRIdINO
			"\n", name, dir->vnid));
		result = ENOENT;
		goto bi;
	}
 
	if (file->disk_image) {
		DPRINTF(0, ("do_unlink: can't unlink disk image or disk image directory\n"));
		result = EPERM;
		goto bi1;
	}
 
	// don't need to check file permissions because it will be done for us
	// also don't need to lock the file (see dosfs_rename for reasons why)
	if (is_file) {
		if (file->mode & FAT_SUBDIR) {
			result = EISDIR;
			goto bi1;
		}
	} else {
		if ((file->mode & FAT_SUBDIR) == 0) {
			result = ENOTDIR;
			goto bi1;
		}
 
		if (file->vnid == vol->root_vnode.vnid) {
			// this actually isn't a problem since the root vnode
			// will always be busy while the volume mounted
			dprintf("dosfs_rmdir: don't call this on the root directory\n");
			result = EPERM;
			goto bi1;
		}
 
		if ((result = check_dir_empty(vol, file)) < 0) {
			if (result == ENOTEMPTY) DPRINTF(0, ("dosfs_rmdir called on non-empty directory\n"));
			goto bi1;
		}
	}
 
	// erase the entry in the parent directory
	if ((result = erase_dir_entry(vol, file)) != B_OK)
		goto bi1;
 
	// shrink the parent directory (errors here are not disastrous)
	compact_directory(vol, dir);
 
	notify_entry_removed(vol->id, dir->vnid, name, file->vnid);
 
	/* 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(vol, file->vnid, generate_unique_vnid(vol));
 
	if (!is_file)
		dlist_remove(vol, file->vnid);
 
	// fsil doesn't call dosfs_write_vnode for us, so we have to free the
	// vnode manually here.
	remove_vnode(_vol, file->vnid);
 
	result = 0;
 
	if (vol->fs_flags & FS_FLAGS_OP_SYNC)
		_dosfs_sync(vol);
 
bi1:
	put_vnode(_vol, vnid);		// get 1 free
bi:
	UNLOCK_VOL(vol);
 
	if (result != B_OK) DPRINTF(0, ("do_unlink (%s)\n", strerror(result)));
 
	return result;
}
 
 
status_t
dosfs_unlink(fs_volume *vol, fs_vnode *dir, const char *name)
{
	DPRINTF(1, ("dosfs_unlink called\n"));
 
	return do_unlink(vol, dir, name, true);
}
 
 
status_t
dosfs_rmdir(fs_volume *vol, fs_vnode *dir, const char *name)
{
	DPRINTF(1, ("dosfs_rmdir called\n"));
 
	return do_unlink(vol, dir, name, false);
}
 
 
bool
dosfs_can_page(fs_volume *_vol, fs_vnode *_node, void *_cookie)
{
	// ToDo: we're obviously not even asked...
	return false;
}
 
 
status_t
dosfs_read_pages(fs_volume *_vol, fs_vnode *_node, void *_cookie, off_t pos,
	const iovec *vecs, size_t count, size_t *_numBytes)
{
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *node = (vnode *)_node->private_node;
	uint32 vecIndex = 0;
	size_t vecOffset = 0;
	size_t bytesLeft = *_numBytes;
	status_t status;
 
	if (node->cache == NULL)
		return(B_BAD_VALUE);
 
	LOCK_VOL(vol);
 
	while (true) {
		struct file_io_vec fileVecs[8];
		size_t fileVecCount = 8;
		bool bufferOverflow;
		size_t bytes = bytesLeft;
 
		status = file_map_translate(node->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(vol->fd, fileVecs,
			fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes);
		if (status != B_OK || !bufferOverflow)
			break;
 
		pos += bytes;
		bytesLeft -= bytes;
	}
 
	UNLOCK_VOL(vol);
 
	return status;
}
 
 
status_t
dosfs_write_pages(fs_volume *_vol, fs_vnode *_node, void *_cookie, off_t pos,
	const iovec *vecs, size_t count, size_t *_numBytes)
{
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *node = (vnode *)_node->private_node;
	uint32 vecIndex = 0;
	size_t vecOffset = 0;
	size_t bytesLeft = *_numBytes;
	status_t status;
 
	if (node->cache == NULL)
		return B_BAD_VALUE;
 
	LOCK_VOL(vol);
 
	if ((vol->flags & B_FS_IS_READONLY) != 0) {
		UNLOCK_VOL(vol);
		return EROFS;
	}
 
	while (true) {
		struct file_io_vec fileVecs[8];
		size_t fileVecCount = 8;
		bool bufferOverflow;
		size_t bytes = bytesLeft;
 
		status = file_map_translate(node->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(vol->fd, fileVecs,
			fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes);
		if (status != B_OK || !bufferOverflow)
			break;
 
		pos += bytes;
		bytesLeft -= bytes;
	}
 
	UNLOCK_VOL(vol);
 
	return status;
}
 
 
status_t
dosfs_get_file_map(fs_volume *_vol, fs_vnode *_node, off_t position,
	size_t length, struct file_io_vec *vecs, size_t *_count)
{
	nspace *vol = (nspace *)_vol->private_volume;
	vnode *node = (vnode *)_node->private_node;
	struct csi iter;
	status_t result;
	uint32 skipSectors;
	off_t offset;
	size_t index = 0;
	size_t max = *_count;
 
	LOCK_VOL(vol);
	*_count = 0;
 
	if ((node->mode & FAT_SUBDIR) != 0) {
		DPRINTF(0, ("dosfs_get_file_map called on subdirectory %" B_PRIdINO
			"\n", node->vnid));
		UNLOCK_VOL(vol);
		return EISDIR;
	}
 
	DPRINTF(0, ("dosfs_get_file_map called %" B_PRIuSIZE " bytes at %" B_PRIdOFF
		" (vnode id %" B_PRIdINO ")\n", length, position, node->vnid));
 
	if (position < 0)
		position = 0;
 
	if (node->st_size == 0 || length == 0 || position >= node->st_size) {
		result = B_OK;
		goto bi;
	}
 
	// Truncate to file size, taking overflow into account.
	if (position + length >= node->st_size || position + length < position)
		length = node->st_size - position;
 
	result = init_csi(vol, node->cluster, 0, &iter);
	if (result != B_OK) {
		dprintf("dosfs_get_file_map: invalid starting cluster (%" B_PRIu32
			")\n", node->cluster);
		result = EIO;
		goto bi;
	}
 
	skipSectors = position / vol->bytes_per_sector;
	if (skipSectors > 0) {
		result = iter_csi(&iter, skipSectors);
		if (result != B_OK) {
			dprintf("dosfs_get_file_map: end of file reached (init)\n");
			result = EIO;
			goto bi;
		}
	}
 
	ASSERT(iter.cluster == get_nth_fat_entry(vol, node->cluster,
			position / vol->bytes_per_sector / vol->sectors_per_cluster));
 
	offset = position % vol->bytes_per_sector;
	while (length > 0) {
		off_t block = csi_to_block(&iter);
		uint32 sectors = 1;
 
		length -= min(length, vol->bytes_per_sector - offset);
 
		while (length > 0) {
			result = iter_csi(&iter, 1);
			if (result != B_OK) {
				dprintf("dosfs_get_file_map: end of file reached\n");
				result = EIO;
				goto bi;
			}
 
			if (block + sectors != csi_to_block(&iter)) {
				// Disjoint sectors, need to flush and begin a new vector.
				break;
			}
 
			length -= min(length, vol->bytes_per_sector);
			sectors++;
		}
 
		vecs[index].offset = block * vol->bytes_per_sector + offset;
		vecs[index].length = sectors * vol->bytes_per_sector - offset;
		index++;
 
		if (length == 0)
			break;
 
		if (index >= max) {
			// we're out of file_io_vecs; let's bail out
			result = B_BUFFER_OVERFLOW;
			goto bi;
		}
 
		offset = 0;
	}
 
	result = B_OK;
bi:
	*_count = index;
 
	if (result != B_OK) {
		DPRINTF(0, ("dosfs_get_file_map (%s)\n", strerror(result)));
	}
	UNLOCK_VOL(vol);
 
	return result;
}

V595 The 'node' pointer was utilized before it was verified against nullptr. Check lines: 140, 146.