lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Wed, 21 Mar 2012 12:32:59 +0800
From:	Adrian McMenamin <lkmladrian@...il.com>
To:	viro@...iv.linux.org.uk, LKML <linux-kernel@...r.kernel.org>,
	linux-fsdevel@...r.kernel.org
Cc:	Linux-sh <linux-sh@...r.kernel.org>,
	Adrian McMenamin <adrianmcmenamin@...il.com>
Subject: [PATCH] VMUFAT filesystem [2/4]

diff --git a/fs/vmufat/inode.c b/fs/vmufat/inode.c
new file mode 100644
index 0000000..584fb56
--- /dev/null
+++ b/fs/vmufat/inode.c
@@ -0,0 +1,951 @@
+/*
+ * VMUFAT file system
+ *
+ * Copyright (C) 2002 - 2012	Adrian McMenamin
+ * Copyright (C) 2002		Paul Mundt
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/fs.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/magic.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/statfs.h>
+#include <linux/buffer_head.h>
+#include "vmufat.h"
+
+const struct inode_operations vmufat_inode_operations;
+const struct file_operations vmufat_file_operations;
+const struct address_space_operations vmufat_address_space_operations;
+const struct file_operations vmufat_file_dir_operations;
+struct kmem_cache *vmufat_blist_cachep;
+/* Linear day numbers of the respective 1sts in non-leap years. */
+int day_n[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+static struct dentry *vmufat_inode_lookup(struct inode *in, struct
dentry *dent,
+	struct nameidata *ignored)
+{
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	struct buffer_head *bufhead = NULL;
+	struct inode *ino;
+	char name[VMUFAT_NAMELEN];
+	int i, j, error = -EINVAL;
+	if (!dent || !in || !in->i_sb)
+		goto out;
+	if (dent->d_name.len > VMUFAT_NAMELEN) {
+		error = -ENAMETOOLONG;
+		goto out;
+	}
+	sb = in->i_sb;
+	if (!sb->s_fs_info)
+		goto out;
+	vmudetails = sb->s_fs_info;
+	error = 0;
+
+	for (i = vmudetails->dir_bnum;
+		i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+		brelse(bufhead);
+		bufhead = vmufat_sb_bread(sb, i);
+		if (!bufhead) {
+			error = -EIO;
+			goto out;
+		}
+		for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+			if (bufhead->b_data[j * VMU_DIR_RECORD_LEN] == 0)
+				goto fail;
+			/* get name and check for match */
+			memcpy(name,
+				bufhead->b_data + j * VMU_DIR_RECORD_LEN
+				+ VMUFAT_NAME_OFFSET, VMUFAT_NAMELEN);
+			if (memcmp(dent->d_name.name, name,
+				dent->d_name.len) == 0) {
+				ino = vmufat_get_inode(sb,
+					le16_to_cpu(((u16 *) bufhead->b_data)
+					[j * VMU_DIR_RECORD_LEN16
+					+ VMUFAT_FIRSTBLOCK_OFFSET16]));
+				if (IS_ERR_OR_NULL(ino)) {
+					if (IS_ERR(ino))
+						error = PTR_ERR(ino);
+					else
+						error = -EACCES;
+					goto out;
+				}
+				d_add(dent, ino);
+				goto out;
+			}
+		}
+	}
+fail:
+	d_add(dent, NULL); /* Did not find the file */
+out:
+	brelse(bufhead);
+	return ERR_PTR(error);
+}
+
+/*
+ * Find a block marked free in the FAT
+ */
+static int vmufat_find_free(struct super_block *sb)
+{
+	struct memcard *vmudetails;
+	int found = 0, testblk, fatblk, error, index_to_fat;
+	int diff;
+	__le16 fatdata;
+	struct buffer_head *bh_fat;
+
+	if (!sb || !sb->s_fs_info) {
+		error = -EINVAL;
+		goto fail;
+	}
+	vmudetails = sb->s_fs_info;
+
+	for (fatblk = vmudetails->fat_bnum;
+		fatblk > vmudetails->fat_bnum - vmudetails->fat_len;
+		fatblk--) {
+		bh_fat = vmufat_sb_bread(sb, fatblk);
+		if (!bh_fat) {
+			error = -EIO;
+			goto fail;
+		}
+
+		index_to_fat = 0xFF;
+		/* prefer not to allocate to higher blocks if we can
+		 * to ensure full compatibility with Dreamcast devices */
+		diff = index_to_fat - VMUFAT_START_ALLOC;
+		for (testblk = index_to_fat; testblk > 0; testblk--) {
+			if (diff > 0 && testblk - diff < 0)
+				diff = -VMUFAT_START_ALLOC - 1;
+			fatdata =
+				le16_to_cpu(((u16 *) bh_fat->b_data)
+				[testblk - diff]);
+			if (fatdata == VMUFAT_UNALLOCATED) {
+				found = 1;
+				put_bh(bh_fat);
+				goto out_of_loop;
+			}
+		}
+		put_bh(bh_fat);
+	}
+out_of_loop:
+	return (fatblk - 1 - vmudetails->fat_bnum + vmudetails->fat_len)
+			* VMU_BLK_SZ16 + testblk - diff;
+
+	printk(KERN_WARNING "VMUFAT: volume is full\n");
+	error = -ENOSPC;
+fail:
+	return error;
+}
+
+/* read the FAT for a given block */
+u16 vmufat_get_fat(struct super_block *sb, long block)
+{
+	struct buffer_head *bufhead;
+	int offset;
+	u16 block_content = VMUFAT_ERROR;
+	struct memcard *vmudetails;
+	if (!sb || !sb->s_fs_info)
+		goto out;
+	vmudetails = sb->s_fs_info;
+
+	/* which block in the FAT */
+	offset = block / VMU_BLK_SZ16;
+	if (offset >= vmudetails->fat_len)
+		goto out;
+
+	/* fat_bnum points to highest block in FAT */
+	bufhead = vmufat_sb_bread(sb, offset + 1 +
+		vmudetails->fat_bnum - vmudetails->fat_len);
+	if (!bufhead)
+		goto out;
+	/* look inside the block */
+	block_content = le16_to_cpu(((u16 *)bufhead->b_data)
+		[block % VMU_BLK_SZ16]);
+	put_bh(bufhead);
+out:
+	return block_content;
+}
+
+/* set the FAT for a given block */
+static int vmufat_set_fat(struct super_block *sb, long block,
+		u16 data_to_set)
+{
+	struct buffer_head *bh;
+	int offset, error = 0;
+	struct memcard *vmudetails;
+	if (!sb || !sb->s_fs_info) {
+		error = -EINVAL;
+		goto out;
+	}
+	vmudetails = sb->s_fs_info;
+
+	offset = block / VMU_BLK_SZ16;
+	if (offset >= vmudetails->fat_len) {
+		error = -EINVAL;
+		goto out;
+	}
+	bh = vmufat_sb_bread(sb, offset + 1 +
+		vmudetails->fat_bnum - vmudetails->fat_len);
+	if (!bh) {
+		error = -EIO;
+		goto out;
+	}
+	((u16 *) bh->b_data)[block % VMU_BLK_SZ16] = cpu_to_le16(data_to_set);
+	mark_buffer_dirty(bh);
+	put_bh(bh);
+out:
+	return 0;
+}
+
+
+static void vmufat_save_bcd_nortc(struct inode *in, char *bh, int index_to_dir)
+{
+	long years, days;
+	unsigned char bcd_century, nl_day, bcd_month;
+	unsigned char u8year;
+	__kernel_time_t unix_date;
+
+	unix_date = in->i_mtime.tv_sec;
+	days = unix_date / SECONDS_PER_DAY;
+	years = days / DAYS_PER_YEAR;
+	/* 1 Jan gets 1 day later after every leap year */
+	if ((years + 3) / 4 + DAYS_PER_YEAR * years >= days)
+		years--;
+	/* rebase days to account for leap years */
+	days -= (years + 3) / 4 + DAYS_PER_YEAR * years;
+	/* 1 Jan is Day 1 */
+	days++;
+	if (days == (FEB28 + 1) && !(years % 4)) {
+		nl_day = days;
+		bcd_month = 2;
+	} else {
+		nl_day = (years % 4) || days <= FEB28 ? days : days - 1;
+		for (bcd_month = 0; bcd_month < 12; bcd_month++)
+			if (day_n[bcd_month] > nl_day)
+				break;
+	}
+
+	bcd_century = 19;
+	/* TODO:accounts for 21st century but will fail in 2100
+		because of leap days */
+	if (years > 29)
+		bcd_century += 1 + (years - 30)/100;
+
+	bh[index_to_dir + VMUFAT_DIR_CENT] = bin2bcd(bcd_century);
+	u8year = years + 70; /* account for epoch */
+	if (u8year > 99)
+		u8year = u8year - 100;
+
+	bh[index_to_dir + VMUFAT_DIR_YEAR] = bin2bcd(u8year);
+	bh[index_to_dir + VMUFAT_DIR_MONTH] = bin2bcd(bcd_month);
+	bh[index_to_dir + VMUFAT_DIR_DAY] =
+	    bin2bcd(days - day_n[bcd_month - 1]);
+	bh[index_to_dir + VMUFAT_DIR_HOUR] =
+	    bin2bcd((unix_date / SECONDS_PER_HOUR) % HOURS_PER_DAY);
+	bh[index_to_dir + VMUFAT_DIR_MIN] =
+		bin2bcd((unix_date / SIXTY_MINS_OR_SECS)
+		 % SIXTY_MINS_OR_SECS);
+	bh[index_to_dir + VMUFAT_DIR_SEC] =
+		bin2bcd(unix_date % SIXTY_MINS_OR_SECS);
+}
+
+static void vmufat_save_bcd_rtc(struct rtc_device *rtc, struct inode *in,
+	char *bh, int index_to_dir)
+{
+	struct rtc_time now;
+
+	if (rtc_read_time(rtc, &now) < 0)
+		vmufat_save_bcd_nortc(in, bh, index_to_dir);
+	bh[index_to_dir + VMUFAT_DIR_CENT] = bin2bcd((char)(now.tm_year/100));
+	bh[index_to_dir + VMUFAT_DIR_YEAR] = bin2bcd((char)(now.tm_year % 100));
+	bh[index_to_dir + VMUFAT_DIR_MONTH] = bin2bcd((char)(now.tm_mon));
+	bh[index_to_dir + VMUFAT_DIR_DAY] = bin2bcd((char)(now.tm_mday));
+	bh[index_to_dir + VMUFAT_DIR_HOUR] = bin2bcd((char)(now.tm_hour));
+	bh[index_to_dir + VMUFAT_DIR_MIN] = bin2bcd((char)(now.tm_min));
+	bh[index_to_dir + VMUFAT_DIR_SEC] = bin2bcd((char)(now.tm_sec));
+	bh[index_to_dir + VMUFAT_DIR_DOW] = bin2bcd((char)(now.tm_wday));
+}
+
+/*
+ * write out the date in bcd format
+ * in the appropriate part of the
+ * directory entry
+ */
+void vmufat_save_bcd(struct inode *in, char *bh, int index_to_dir)
+{
+	struct rtc_device *rtc;
+	rtc = rtc_class_open("rtc0");
+	if (!rtc)
+		vmufat_save_bcd_nortc(in, bh, index_to_dir);
+	else {
+		vmufat_save_bcd_rtc(rtc, in, bh, index_to_dir);
+		rtc_class_close(rtc);
+	}
+}
+
+static int vmufat_allocate_inode(umode_t imode,
+		struct super_block *sb, struct inode *in)
+{
+	int error;
+	if (!sb || !in)
+		return -EINVAL;
+	/* Executable files must be at the start of the volume */
+	if (imode & EXEC) {
+		in->i_ino = VMUFAT_ZEROBLOCK;
+		if (vmufat_get_fat(sb, 0) != VMUFAT_UNALLOCATED) {
+			printk(KERN_WARNING "VMUFAT: cannot write excutable "
+				"file. Volume block 0 already allocated.\n");
+			error = -ENOSPC;
+			goto out;
+		}
+		error = 0;
+	} else {
+		error = vmufat_find_free(sb);
+		if (error >= 0)
+			in->i_ino = error;
+	}
+out:
+	return error;
+}
+
+static void vmufat_setup_inode(struct inode *in, umode_t imode,
+		struct super_block *sb)
+{
+	if (!in)
+		return;
+
+	in->i_uid = current_fsuid();
+	in->i_gid = current_fsgid();
+	in->i_mtime = in->i_atime = in->i_ctime = CURRENT_TIME;
+	in->i_mode = imode;
+	in->i_blocks = 1;
+	in->i_sb = sb;
+	insert_inode_hash(in);
+	in->i_op = &vmufat_inode_operations;
+	in->i_fop = &vmufat_file_operations;
+	in->i_mapping->a_ops = &vmufat_address_space_operations;
+}
+
+static void vmu_handle_zeroblock(int recno, struct buffer_head *bh, int ino)
+{
+	/* offset and header offset settings */
+	if (ino != VMUFAT_ZEROBLOCK) {
+		((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] =
+		    cpu_to_le16(ino);
+		((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] = 0;
+	} else {
+		((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] = 0;
+		((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] = 1;
+	}
+}
+
+static void vmu_write_name(int recno, struct buffer_head *bh, char *name,
+	int len)
+{
+	memset((char *) (bh->b_data + recno + VMUFAT_NAME_OFFSET), '\0',
+		VMUFAT_NAMELEN);
+	memcpy((char *) (bh->b_data + recno + VMUFAT_NAME_OFFSET),
+		name, len);
+}
+
+static int vmufat_inode_create(struct inode *dir, struct dentry *de,
+		umode_t imode, struct nameidata *nd)
+{
+	/* Create an inode */
+	int i, j, found = 0, error = 0, freeblock;
+	struct inode *inode;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	struct buffer_head *bh = NULL;
+	if (!dir || !de) {
+		error = -EINVAL;
+		goto out;
+	}
+
+	if (de->d_name.len > VMUFAT_NAMELEN) {
+		error = -ENAMETOOLONG;
+		goto out;
+	}
+
+	sb = dir->i_sb;
+	if (!sb || !sb->s_fs_info) {
+		error = -EINVAL;
+		goto out;
+	}
+	vmudetails = sb->s_fs_info;
+
+	inode = new_inode(sb);
+	if (!inode) {
+		error = -ENOSPC;
+		goto out;
+	}
+
+	down_interruptible(&vmudetails->vmu_sem);
+	freeblock = vmufat_allocate_inode(imode, sb, inode);
+	if (freeblock < 0) {
+		up(&vmudetails->vmu_sem);
+		error = freeblock;
+		goto clean_inode;
+	}
+	/* mark as single block file - may grow later */
+	error = vmufat_set_fat(sb, freeblock, VMUFAT_FILE_END);
+	up(&vmudetails->vmu_sem);
+	if (error)
+		goto clean_inode;
+
+	vmufat_setup_inode(inode, imode, sb);
+
+	/* Write to the directory
+	 * Now search for space for the directory entry */
+	down_interruptible(&vmudetails->vmu_sem);
+	for (i = vmudetails->dir_bnum;
+		i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+		brelse(bh);
+		bh = vmufat_sb_bread(sb, i);
+		if (!bh) {
+			up(&vmudetails->vmu_sem);
+			error = -EIO;
+			goto clean_fat;
+		}
+		for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+			if (((bh->b_data)[j * VMU_DIR_RECORD_LEN]) == 0) {
+				up(&vmudetails->vmu_sem);
+				found = 1;
+				goto dir_space_found;
+			}
+		}
+	}
+	if (found == 0)
+		goto clean_fat;
+dir_space_found:
+	j = j * VMU_DIR_RECORD_LEN;
+	/* Have the directory entry
+	 * so now update it */
+	if (imode & EXEC)
+		bh->b_data[j] = VMU_GAME;	/* exec file */
+	else
+		bh->b_data[j] = VMU_DATA;
+
+	/* copy protection settings */
+	if (bh->b_data[j + 1] != (char) NOCOPY)
+		bh->b_data[j + 1] = (char) CANCOPY;
+
+	vmu_handle_zeroblock(j / 2, bh, inode->i_ino);
+	vmu_write_name(j, bh, (char *) de->d_name.name, de->d_name.len);
+
+	/* BCD timestamp it */
+	vmufat_save_bcd(inode, bh->b_data, j);
+
+	((u16 *) bh->b_data)[j / 2 + VMUFAT_SIZE_OFFSET16] =
+	    cpu_to_le16(inode->i_blocks);
+	mark_buffer_dirty(bh);
+	brelse(bh);
+	error = vmufat_list_blocks(inode);
+	if (error)
+		goto clean_fat;
+	up(&vmudetails->vmu_sem);
+	d_instantiate(de, inode);
+	return error;
+
+clean_fat:
+	vmufat_set_fat(sb, freeblock, VMUFAT_UNALLOCATED);
+	up(&vmudetails->vmu_sem);
+clean_inode:
+	iput(inode);
+out:
+	if (error < 0)
+		printk(KERN_ERR "VMUFAT: inode creation fails with error"
+			" %i\n", error);
+	return error;
+}
+
+static int vmufat_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+	int filenamelen, index, j, k, error = -EINVAL;
+	struct vmufat_file_info *saved_file = NULL;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	struct buffer_head *bh = NULL;
+	if (!filp || !filp->f_dentry)
+		goto out;
+	dentry = filp->f_dentry;
+	inode = dentry->d_inode;
+	if (!inode)
+		goto out;
+	sb = inode->i_sb;
+	if (!sb)
+		goto out;
+	vmudetails = sb->s_fs_info;
+	if (!vmudetails)
+		goto out;
+	error = 0;
+
+	index = filp->f_pos;
+	if (index > 200)
+		return -EIO;
+	/* handle . for this directory and .. for parent */
+	switch ((unsigned int) filp->f_pos) {
+	case 0:
+		error = filldir(dirent, ".", 1, index++, inode->i_ino, DT_DIR);
+		if (error < 0)
+			goto out;
+	case 1:
+		error = filldir(dirent, "..", 2, index++,
+			    dentry->d_parent->d_inode->i_ino, DT_DIR);
+		if (error < 0)
+			goto out;
+	default:
+		break;
+	}
+
+	saved_file =
+	    kmalloc(sizeof(struct vmufat_file_info), GFP_KERNEL);
+	if (!saved_file) {
+		error = -ENOMEM;
+		goto out;
+	}
+
+	for (j = vmudetails->dir_bnum -
+		(index - 2) / VMU_DIR_ENTRIES_PER_BLOCK;
+		j > vmudetails->dir_bnum - vmudetails->dir_len; j--) {
+		brelse(bh);
+		bh = vmufat_sb_bread(sb, j);
+		if (!bh) {
+			error = -EIO;
+			goto finish;
+		}
+		for (k = (index - 2) % VMU_DIR_ENTRIES_PER_BLOCK;
+			k < VMU_DIR_ENTRIES_PER_BLOCK; k++) {
+			saved_file->ftype = bh->b_data[k * VMU_DIR_RECORD_LEN];
+			if (saved_file->ftype == 0)
+				goto finish;
+			saved_file->fblk =
+				le16_to_cpu(((u16 *) bh->b_data)
+				[k * VMU_DIR_RECORD_LEN16 + 1]);
+			if (saved_file->fblk == 0)
+				saved_file->fblk = VMUFAT_ZEROBLOCK;
+			memcpy(saved_file->fname,
+				bh->b_data + k * VMU_DIR_RECORD_LEN
+				+ VMUFAT_NAME_OFFSET, VMUFAT_NAMELEN);
+			filenamelen = strlen(saved_file->fname);
+			if (filenamelen > VMUFAT_NAMELEN)
+				filenamelen = VMUFAT_NAMELEN;
+			error = filldir(dirent, saved_file->fname, filenamelen,
+				index++, saved_file->fblk, DT_REG);
+			if (error < 0)
+				goto finish;
+		}
+	}
+
+finish:
+	filp->f_pos = index;
+	kfree(saved_file);
+	brelse(bh);
+out:
+	return error;
+}
+
+
+int vmufat_list_blocks(struct inode *in)
+{
+	struct vmufat_inode *vi;
+	struct super_block *sb;
+	long nextblock;
+	long ino;
+	struct memcard *vmudetails;
+	int error = -EINVAL;
+	struct list_head *iter, *iter2;
+	struct vmufat_block_list *vbl, *nvbl;
+	u16 fatdata;
+	if (!in || !in->i_sb)
+		goto out;
+
+	vi = VMUFAT_I(in);
+	if (!vi)
+		goto out;
+	sb = in->i_sb;
+	ino = in->i_ino;
+	vmudetails = sb->s_fs_info;
+	if (!vmudetails)
+		goto out;
+	error = 0;
+	nextblock = ino;
+	if (nextblock == VMUFAT_ZEROBLOCK)
+		nextblock = 0;
+
+	/* Delete any previous list of blocks */
+	list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+		vbl = list_entry(iter, struct vmufat_block_list, b_list);
+		list_del(iter);
+		kmem_cache_free(vmufat_blist_cachep, vbl);
+	}
+	vi->nblcks = 0;
+	do {
+		vbl = kmem_cache_alloc(vmufat_blist_cachep,
+			GFP_KERNEL);
+		if (!vbl) {
+			error = -ENOMEM;
+			goto unwind_out;
+		}
+		INIT_LIST_HEAD(&vbl->b_list);
+		vbl->bno = nextblock;
+		list_add_tail(&vbl->b_list, &vi->blocks.b_list);
+		vi->nblcks++;
+
+		/* Find next block in the FAT - if there is one */
+		fatdata = vmufat_get_fat(sb, nextblock);
+		if (fatdata == VMUFAT_UNALLOCATED) {
+			printk(KERN_ERR "VMUFAT: FAT table appears to have"
+				" been corrupted.\n");
+			error = -EIO;
+			goto unwind_out;
+		}
+		if (fatdata == VMUFAT_FILE_END)
+			break;	/*end of file */
+		nextblock = fatdata;
+	} while (1);
+out:
+	return error;
+
+unwind_out:
+	list_for_each_entry_safe(vbl, nvbl, &vi->blocks.b_list, b_list) {
+		list_del_init(&vbl->b_list);
+		kmem_cache_free(vmufat_blist_cachep, vbl);
+	}
+	return error;
+}
+
+static int vmufat_clean_fat(struct super_block *sb, int inum)
+{
+	int error = 0;
+	u16 fatword, nextword;
+	if (!sb) {
+		error = -EINVAL;
+		goto out;
+	}
+
+	nextword = inum;
+	do {
+		fatword = vmufat_get_fat(sb, nextword);
+		if (fatword == VMUFAT_ERROR) {
+			error = -EIO;
+			goto out;
+		}
+		error = vmufat_set_fat(sb, nextword, VMUFAT_UNALLOCATED);
+		if (error)
+			goto out;
+		if (fatword == VMUFAT_FILE_END)
+			goto out;
+		nextword = fatword;
+	} while (1);
+out:
+	return error;
+}
+
+/*
+ * Delete inode by marking space as free in FAT
+ * no need to waste time and effort by actually
+ * wiping underlying data on media
+ */
+static void vmufat_remove_inode(struct inode *in)
+{
+	struct buffer_head *bh = NULL, *bh_old = NULL;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	int i, j, k, l, startpt, found = 0;
+	if (!in || !in->i_sb)
+		goto ret;
+
+	if (in->i_ino == VMUFAT_ZEROBLOCK)
+		in->i_ino = 0;
+	sb = in->i_sb;
+	vmudetails = sb->s_fs_info;
+	if (!vmudetails)
+		goto ret;
+	if (in->i_ino > vmudetails->fat_len * sb->s_blocksize / 2) {
+		printk(KERN_ERR "VMUFAT: attempting to delete"
+			"inode beyond device size");
+		goto ret;
+	}
+
+	down_interruptible(&vmudetails->vmu_sem);
+	if (vmufat_clean_fat(sb, in->i_ino)) {
+		up(&vmudetails->vmu_sem);
+		goto failure;
+	}
+
+	/* Now clean the directory entry
+	 * Have to wander through this
+	 * to find the appropriate entry */
+	for (i = vmudetails->dir_bnum;
+		i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+		brelse(bh);
+		bh = vmufat_sb_bread(sb, i);
+		if (!bh) {
+			up(&vmudetails->vmu_sem);
+			goto failure;
+		}
+		for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+			if (bh->b_data[j * VMU_DIR_RECORD_LEN] == 0) {
+				up(&vmudetails->vmu_sem);
+				goto failure;
+			}
+			if (le16_to_cpu(((u16 *) bh->b_data)
+				[j * VMU_DIR_RECORD_LEN16 +
+				VMUFAT_FIRSTBLOCK_OFFSET16]) == in->i_ino) {
+				found = 1;
+				goto found;
+			}
+		}
+	}
+found:
+	if (found == 0) {
+		up(&vmudetails->vmu_sem);
+		goto failure;
+	}
+
+	/* Found directory entry - so NULL it now */
+	for (k = 0; k < VMU_DIR_RECORD_LEN; k++)
+		bh->b_data[j * VMU_DIR_RECORD_LEN + k] = 0;
+	mark_buffer_dirty(bh);
+	/* Patch up directory, by moving up last file */
+	found = 0;
+	startpt = j + 1;
+	for (l = i; l > vmudetails->dir_bnum - vmudetails->dir_len; l--) {
+		bh_old = vmufat_sb_bread(sb, l);
+		if (!bh_old) {
+			up(&vmudetails->vmu_sem);
+			goto failure;
+		}
+		for (k = startpt; k < VMU_DIR_ENTRIES_PER_BLOCK; k++) {
+			if (bh_old->b_data[k * VMU_DIR_RECORD_LEN] == 0) {
+				found = 1;
+				brelse(bh_old);
+				goto lastdirfound;
+			}
+		}
+		startpt = 0;
+		brelse(bh_old);
+	}
+lastdirfound:
+	if (found == 0) {	/* full directory */
+		l = vmudetails->dir_bnum - vmudetails->dir_len + 1;
+		k = VMU_DIR_ENTRIES_PER_BLOCK;
+	} else if (l == i && k == j + 1) /* deleted entry was last in dir */
+		goto finish;
+	else if (k == 0) {
+		l = l + 1;
+		k = VMU_DIR_ENTRIES_PER_BLOCK;
+		if (l == i && k == j + 1)
+			goto finish;
+	}
+	/* fill gap first then wipe out old entry */
+	bh_old = vmufat_sb_bread(sb, l);
+	if (!bh_old) {
+		up(&vmudetails->vmu_sem);
+		brelse(bh);
+		goto failure;
+	}
+	for (i = 0; i < VMU_DIR_RECORD_LEN; i++) {
+		bh->b_data[j * VMU_DIR_RECORD_LEN + i] =
+			bh_old->b_data[(k - 1) * VMU_DIR_RECORD_LEN + i];
+		bh_old->b_data[(k - 1) * VMU_DIR_RECORD_LEN + i] = 0;
+	}
+	mark_buffer_dirty(bh_old);
+	mark_buffer_dirty(bh);
+	brelse(bh_old);
+
+finish:
+	up(&vmudetails->vmu_sem);
+	brelse(bh);
+	return;
+
+failure:
+	printk(KERN_ERR "VMUFAT: Failure to read volume,"
+		" could not delete inode - filesystem may be damaged\n");
+ret:
+	return;
+}
+
+/*
+ * vmufat_unlink - delete a file pointed to
+ * by the dentry (only one directory in a
+ * vmufat fs so safe to ignore the inode
+ * supplied here
+ */
+static int vmufat_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct inode *in;
+	if (!dentry || !dentry->d_inode)
+		return -EINVAL;
+	in = dentry->d_inode;
+	vmufat_remove_inode(in);
+	return 0;
+}
+
+static int vmufat_get_block(struct inode *inode, sector_t iblock,
+	struct buffer_head *bh_result, int create)
+{
+	struct vmufat_inode *vin;
+	struct vmufat_block_list *vlist, *vblk;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	int cural;
+	int finblk, nxtblk, exeblk;
+	struct list_head *iter;
+	sector_t cntdwn = iblock;
+	sector_t phys;
+	int error = -EINVAL;
+	if (!inode || !inode->i_sb)
+		goto out;
+	vin = VMUFAT_I(inode);
+	if (!vin || !(&vin->blocks))
+		goto out;
+	vlist = &vin->blocks;
+	sb = inode->i_sb;
+	vmudetails = sb->s_fs_info;
+	if (!vmudetails)
+		goto out;
+
+	if (vin->nblcks <= 0)
+		goto out;
+	if (iblock < vin->nblcks) {
+		/* block is already here so read it into the buffer head */
+		list_for_each(iter, &vlist->b_list) {
+			if (cntdwn-- == 0)
+				break;
+		}
+		vblk = list_entry(iter, struct vmufat_block_list, b_list);
+		clear_buffer_new(bh_result);
+		error = 0;
+		phys = vblk->bno;
+		goto got_it;
+	}
+	if (!create)
+		goto out;
+	/*
+	 * check not looking for a block too far
+	 * beyond the end of the existing file
+	 */
+	if (iblock > vin->nblcks)
+		goto out;
+
+	/* if looking for a block that is not current - allocate it*/
+	cural = vin->nblcks;
+	list_for_each(iter, &vlist->b_list) {
+		if (cural-- == 1)
+			break;
+	}
+	vblk = list_entry(iter, struct vmufat_block_list, b_list);
+	finblk = vblk->bno;
+
+	down_interruptible(&vmudetails->vmu_sem);
+	/* Exec files have to be linear */
+	if (inode->i_ino == 0) {
+		exeblk = vmufat_get_fat(sb, finblk + 1);
+		if (exeblk != VMUFAT_UNALLOCATED) {
+			up(&vmudetails->vmu_sem);
+			printk(KERN_WARNING "VMUFAT: Cannot allocate linear "
+				"space needed for executible\n");
+			error = -ENOSPC;
+			goto out;
+		}
+		nxtblk = finblk + 1;
+	} else {
+		nxtblk = vmufat_find_free(sb);
+		if (nxtblk < 0) {
+			up(&vmudetails->vmu_sem);
+			error = nxtblk;
+			goto out;
+		}
+	}
+	error = vmufat_set_fat(sb, finblk, nxtblk);
+	if (error) {
+		up(&vmudetails->vmu_sem);
+		goto out;
+	}
+	error = vmufat_set_fat(sb, nxtblk, VMUFAT_FILE_END);
+	up(&vmudetails->vmu_sem);
+	if (error)
+		goto out;
+	error = vmufat_list_blocks(inode);
+	mark_inode_dirty(inode);
+	if (error)
+		goto out;
+	set_buffer_new(bh_result);
+	phys = nxtblk;
+	error = 0;
+got_it:
+	map_bh(bh_result, sb, phys);
+out:
+	return error;
+}
+
+static int vmufat_writepage(struct page *page, struct writeback_control *wbc)
+{
+	return block_write_full_page(page, vmufat_get_block, wbc);
+}
+
+static int vmufat_write_begin(struct file *filp, struct address_space *mapping,
+		loff_t pos, unsigned len, unsigned flags,
+		struct page **pagep, void **fsdata)
+{
+	*pagep = NULL;
+	return block_write_begin(mapping, pos, len, flags, pagep,
+		vmufat_get_block);
+}
+
+static int vmufat_readpage(struct file *file, struct page *page)
+{
+	return block_read_full_page(page, vmufat_get_block);
+}
+
+
+const struct address_space_operations
+		vmufat_address_space_operations = {
+	.readpage =	vmufat_readpage,
+	.writepage =	vmufat_writepage,
+	.write_begin =	vmufat_write_begin,
+	.write_end =	generic_write_end,
+};
+
+const struct inode_operations vmufat_inode_operations = {
+	.lookup =	vmufat_inode_lookup,
+	.create =	vmufat_inode_create,
+	.unlink =	vmufat_unlink,
+};
+
+const struct file_operations vmufat_file_dir_operations = {
+	.owner =	THIS_MODULE,
+	.read =		generic_read_dir,
+	.readdir =	vmufat_readdir,
+	.fsync =	generic_file_fsync,
+};
+
+const struct file_operations vmufat_file_operations = {
+	.llseek =	generic_file_llseek,
+	.read =		do_sync_read,
+	.write =	do_sync_write,
+	.aio_read =	generic_file_aio_read,
+	.aio_write =	generic_file_aio_write,
+	.fsync =	generic_file_fsync,
+};
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ