diff options
| author | James Morris <jmorris@namei.org> | 2008-11-14 11:29:12 +1100 | 
|---|---|---|
| committer | James Morris <jmorris@namei.org> | 2008-11-14 11:29:12 +1100 | 
| commit | 2b828925652340277a889cbc11b2d0637f7cdaf7 (patch) | |
| tree | 32fcb3d3e466fc419fad2d3717956a5b5ad3d35a /fs/fat | |
| parent | 3a3b7ce9336952ea7b9564d976d068a238976c9d (diff) | |
| parent | 58e20d8d344b0ee083febb18c2b021d2427e56ca (diff) | |
| download | linux-2b828925652340277a889cbc11b2d0637f7cdaf7.tar.bz2 | |
Merge branch 'master' into next
Conflicts:
	security/keys/internal.h
	security/keys/process_keys.c
	security/keys/request_key.c
Fixed conflicts above by using the non 'tsk' versions.
Signed-off-by: James Morris <jmorris@namei.org>
Diffstat (limited to 'fs/fat')
| -rw-r--r-- | fs/fat/Makefile | 6 | ||||
| -rw-r--r-- | fs/fat/cache.c | 25 | ||||
| -rw-r--r-- | fs/fat/dir.c | 20 | ||||
| -rw-r--r-- | fs/fat/fat.h | 329 | ||||
| -rw-r--r-- | fs/fat/fatent.c | 24 | ||||
| -rw-r--r-- | fs/fat/file.c | 49 | ||||
| -rw-r--r-- | fs/fat/inode.c | 131 | ||||
| -rw-r--r-- | fs/fat/misc.c | 155 | ||||
| -rw-r--r-- | fs/fat/namei_msdos.c | 706 | ||||
| -rw-r--r-- | fs/fat/namei_vfat.c | 1098 | 
10 files changed, 2395 insertions, 148 deletions
| diff --git a/fs/fat/Makefile b/fs/fat/Makefile index bfb5f06cf2c8..e06190322c1c 100644 --- a/fs/fat/Makefile +++ b/fs/fat/Makefile @@ -3,5 +3,9 @@  #  obj-$(CONFIG_FAT_FS) += fat.o +obj-$(CONFIG_VFAT_FS) += vfat.o +obj-$(CONFIG_MSDOS_FS) += msdos.o -fat-objs := cache.o dir.o fatent.o file.o inode.o misc.o +fat-y := cache.o dir.o fatent.o file.o inode.o misc.o +vfat-y := namei_vfat.o +msdos-y := namei_msdos.o diff --git a/fs/fat/cache.c b/fs/fat/cache.c index 3222f51c41cf..b42602298087 100644 --- a/fs/fat/cache.c +++ b/fs/fat/cache.c @@ -9,8 +9,8 @@   */  #include <linux/fs.h> -#include <linux/msdos_fs.h>  #include <linux/buffer_head.h> +#include "fat.h"  /* this must be > 0. */  #define FAT_MAX_CACHE	8 @@ -293,10 +293,12 @@ static int fat_bmap_cluster(struct inode *inode, int cluster)  }  int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys, -	     unsigned long *mapped_blocks) +	     unsigned long *mapped_blocks, int create)  {  	struct super_block *sb = inode->i_sb;  	struct msdos_sb_info *sbi = MSDOS_SB(sb); +	const unsigned long blocksize = sb->s_blocksize; +	const unsigned char blocksize_bits = sb->s_blocksize_bits;  	sector_t last_block;  	int cluster, offset; @@ -309,10 +311,21 @@ int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys,  		}  		return 0;  	} -	last_block = (MSDOS_I(inode)->mmu_private + (sb->s_blocksize - 1)) -		>> sb->s_blocksize_bits; -	if (sector >= last_block) -		return 0; + +	last_block = (i_size_read(inode) + (blocksize - 1)) >> blocksize_bits; +	if (sector >= last_block) { +		if (!create) +			return 0; + +		/* +		 * ->mmu_private can access on only allocation path. +		 * (caller must hold ->i_mutex) +		 */ +		last_block = (MSDOS_I(inode)->mmu_private + (blocksize - 1)) +			>> blocksize_bits; +		if (sector >= last_block) +			return 0; +	}  	cluster = sector >> (sbi->cluster_bits - sb->s_blocksize_bits);  	offset  = sector & (sbi->sec_per_clus - 1); diff --git a/fs/fat/dir.c b/fs/fat/dir.c index bae1c3292522..67e058357098 100644 --- a/fs/fat/dir.c +++ b/fs/fat/dir.c @@ -16,11 +16,11 @@  #include <linux/module.h>  #include <linux/slab.h>  #include <linux/time.h> -#include <linux/msdos_fs.h>  #include <linux/smp_lock.h>  #include <linux/buffer_head.h>  #include <linux/compat.h>  #include <asm/uaccess.h> +#include "fat.h"  static inline loff_t fat_make_i_pos(struct super_block *sb,  				    struct buffer_head *bh, @@ -77,7 +77,7 @@ next:  	*bh = NULL;  	iblock = *pos >> sb->s_blocksize_bits; -	err = fat_bmap(dir, iblock, &phys, &mapped_blocks); +	err = fat_bmap(dir, iblock, &phys, &mapped_blocks, 0);  	if (err || !phys)  		return -1;	/* beyond EOF or error */ @@ -86,7 +86,7 @@ next:  	*bh = sb_bread(sb, phys);  	if (*bh == NULL) {  		printk(KERN_ERR "FAT: Directory bread(block %llu) failed\n", -		       (unsigned long long)phys); +		       (llu)phys);  		/* skip this block */  		*pos = (iblock + 1) << sb->s_blocksize_bits;  		goto next; @@ -373,9 +373,10 @@ parse_record:  		if (de->attr == ATTR_EXT) {  			int status = fat_parse_long(inode, &cpos, &bh, &de,  						    &unicode, &nr_slots); -			if (status < 0) -				return status; -			else if (status == PARSE_INVALID) +			if (status < 0) { +				err = status; +				goto end_of_dir; +			} else if (status == PARSE_INVALID)  				continue;  			else if (status == PARSE_NOT_LONGNAME)  				goto parse_record; @@ -832,6 +833,7 @@ static long fat_compat_dir_ioctl(struct file *filp, unsigned cmd,  #endif /* CONFIG_COMPAT */  const struct file_operations fat_dir_operations = { +	.llseek		= generic_file_llseek,  	.read		= generic_read_dir,  	.readdir	= fat_readdir,  	.ioctl		= fat_dir_ioctl, @@ -1089,6 +1091,7 @@ int fat_alloc_new_dir(struct inode *dir, struct timespec *ts)  	struct msdos_dir_entry *de;  	sector_t blknr;  	__le16 date, time; +	u8 time_cs;  	int err, cluster;  	err = fat_alloc_clusters(dir, &cluster, 1); @@ -1102,7 +1105,7 @@ int fat_alloc_new_dir(struct inode *dir, struct timespec *ts)  		goto error_free;  	} -	fat_date_unix2dos(ts->tv_sec, &time, &date, sbi->options.tz_utc); +	fat_time_unix2fat(sbi, ts, &time, &date, &time_cs);  	de = (struct msdos_dir_entry *)bhs[0]->b_data;  	/* filling the new directory slots ("." and ".." entries) */ @@ -1112,13 +1115,14 @@ int fat_alloc_new_dir(struct inode *dir, struct timespec *ts)  	de[0].lcase = de[1].lcase = 0;  	de[0].time = de[1].time = time;  	de[0].date = de[1].date = date; -	de[0].ctime_cs = de[1].ctime_cs = 0;  	if (sbi->options.isvfat) {  		/* extra timestamps */  		de[0].ctime = de[1].ctime = time; +		de[0].ctime_cs = de[1].ctime_cs = time_cs;  		de[0].adate = de[0].cdate = de[1].adate = de[1].cdate = date;  	} else {  		de[0].ctime = de[1].ctime = 0; +		de[0].ctime_cs = de[1].ctime_cs = 0;  		de[0].adate = de[0].cdate = de[1].adate = de[1].cdate = 0;  	}  	de[0].start = cpu_to_le16(cluster); diff --git a/fs/fat/fat.h b/fs/fat/fat.h new file mode 100644 index 000000000000..ea440d65819c --- /dev/null +++ b/fs/fat/fat.h @@ -0,0 +1,329 @@ +#ifndef _FAT_H +#define _FAT_H + +#include <linux/buffer_head.h> +#include <linux/string.h> +#include <linux/nls.h> +#include <linux/fs.h> +#include <linux/mutex.h> +#include <linux/msdos_fs.h> + +/* + * vfat shortname flags + */ +#define VFAT_SFN_DISPLAY_LOWER	0x0001 /* convert to lowercase for display */ +#define VFAT_SFN_DISPLAY_WIN95	0x0002 /* emulate win95 rule for display */ +#define VFAT_SFN_DISPLAY_WINNT	0x0004 /* emulate winnt rule for display */ +#define VFAT_SFN_CREATE_WIN95	0x0100 /* emulate win95 rule for create */ +#define VFAT_SFN_CREATE_WINNT	0x0200 /* emulate winnt rule for create */ + +struct fat_mount_options { +	uid_t fs_uid; +	gid_t fs_gid; +	unsigned short fs_fmask; +	unsigned short fs_dmask; +	unsigned short codepage;  /* Codepage for shortname conversions */ +	char *iocharset;          /* Charset used for filename input/display */ +	unsigned short shortname; /* flags for shortname display/create rule */ +	unsigned char name_check; /* r = relaxed, n = normal, s = strict */ +	unsigned short allow_utime;/* permission for setting the [am]time */ +	unsigned quiet:1,         /* set = fake successful chmods and chowns */ +		 showexec:1,      /* set = only set x bit for com/exe/bat */ +		 sys_immutable:1, /* set = system files are immutable */ +		 dotsOK:1,        /* set = hidden and system files are named '.filename' */ +		 isvfat:1,        /* 0=no vfat long filename support, 1=vfat support */ +		 utf8:1,	  /* Use of UTF-8 character set (Default) */ +		 unicode_xlate:1, /* create escape sequences for unhandled Unicode */ +		 numtail:1,       /* Does first alias have a numeric '~1' type tail? */ +		 flush:1,	  /* write things quickly */ +		 nocase:1,	  /* Does this need case conversion? 0=need case conversion*/ +		 usefree:1,	  /* Use free_clusters for FAT32 */ +		 tz_utc:1,	  /* Filesystem timestamps are in UTC */ +		 rodir:1;	  /* allow ATTR_RO for directory */ +}; + +#define FAT_HASH_BITS	8 +#define FAT_HASH_SIZE	(1UL << FAT_HASH_BITS) + +/* + * MS-DOS file system in-core superblock data + */ +struct msdos_sb_info { +	unsigned short sec_per_clus; /* sectors/cluster */ +	unsigned short cluster_bits; /* log2(cluster_size) */ +	unsigned int cluster_size;   /* cluster size */ +	unsigned char fats,fat_bits; /* number of FATs, FAT bits (12 or 16) */ +	unsigned short fat_start; +	unsigned long fat_length;    /* FAT start & length (sec.) */ +	unsigned long dir_start; +	unsigned short dir_entries;  /* root dir start & entries */ +	unsigned long data_start;    /* first data sector */ +	unsigned long max_cluster;   /* maximum cluster number */ +	unsigned long root_cluster;  /* first cluster of the root directory */ +	unsigned long fsinfo_sector; /* sector number of FAT32 fsinfo */ +	struct mutex fat_lock; +	unsigned int prev_free;      /* previously allocated cluster number */ +	unsigned int free_clusters;  /* -1 if undefined */ +	unsigned int free_clus_valid; /* is free_clusters valid? */ +	struct fat_mount_options options; +	struct nls_table *nls_disk;  /* Codepage used on disk */ +	struct nls_table *nls_io;    /* Charset used for input and display */ +	const void *dir_ops;		     /* Opaque; default directory operations */ +	int dir_per_block;	     /* dir entries per block */ +	int dir_per_block_bits;	     /* log2(dir_per_block) */ + +	int fatent_shift; +	struct fatent_operations *fatent_ops; + +	spinlock_t inode_hash_lock; +	struct hlist_head inode_hashtable[FAT_HASH_SIZE]; +}; + +#define FAT_CACHE_VALID	0	/* special case for valid cache */ + +/* + * MS-DOS file system inode data in memory + */ +struct msdos_inode_info { +	spinlock_t cache_lru_lock; +	struct list_head cache_lru; +	int nr_caches; +	/* for avoiding the race between fat_free() and fat_get_cluster() */ +	unsigned int cache_valid_id; + +	/* NOTE: mmu_private is 64bits, so must hold ->i_mutex to access */ +	loff_t mmu_private;	/* physically allocated size */ + +	int i_start;		/* first cluster or 0 */ +	int i_logstart;		/* logical first cluster */ +	int i_attrs;		/* unused attribute bits */ +	loff_t i_pos;		/* on-disk position of directory entry or 0 */ +	struct hlist_node i_fat_hash;	/* hash by i_location */ +	struct inode vfs_inode; +}; + +struct fat_slot_info { +	loff_t i_pos;		/* on-disk position of directory entry */ +	loff_t slot_off;	/* offset for slot or de start */ +	int nr_slots;		/* number of slots + 1(de) in filename */ +	struct msdos_dir_entry *de; +	struct buffer_head *bh; +}; + +static inline struct msdos_sb_info *MSDOS_SB(struct super_block *sb) +{ +	return sb->s_fs_info; +} + +static inline struct msdos_inode_info *MSDOS_I(struct inode *inode) +{ +	return container_of(inode, struct msdos_inode_info, vfs_inode); +} + +/* + * If ->i_mode can't hold S_IWUGO (i.e. ATTR_RO), we use ->i_attrs to + * save ATTR_RO instead of ->i_mode. + * + * If it's directory and !sbi->options.rodir, ATTR_RO isn't read-only + * bit, it's just used as flag for app. + */ +static inline int fat_mode_can_hold_ro(struct inode *inode) +{ +	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb); +	mode_t mask; + +	if (S_ISDIR(inode->i_mode)) { +		if (!sbi->options.rodir) +			return 0; +		mask = ~sbi->options.fs_dmask; +	} else +		mask = ~sbi->options.fs_fmask; + +	if (!(mask & S_IWUGO)) +		return 0; +	return 1; +} + +/* Convert attribute bits and a mask to the UNIX mode. */ +static inline mode_t fat_make_mode(struct msdos_sb_info *sbi, +				   u8 attrs, mode_t mode) +{ +	if (attrs & ATTR_RO && !((attrs & ATTR_DIR) && !sbi->options.rodir)) +		mode &= ~S_IWUGO; + +	if (attrs & ATTR_DIR) +		return (mode & ~sbi->options.fs_dmask) | S_IFDIR; +	else +		return (mode & ~sbi->options.fs_fmask) | S_IFREG; +} + +/* Return the FAT attribute byte for this inode */ +static inline u8 fat_make_attrs(struct inode *inode) +{ +	u8 attrs = MSDOS_I(inode)->i_attrs; +	if (S_ISDIR(inode->i_mode)) +		attrs |= ATTR_DIR; +	if (fat_mode_can_hold_ro(inode) && !(inode->i_mode & S_IWUGO)) +		attrs |= ATTR_RO; +	return attrs; +} + +static inline void fat_save_attrs(struct inode *inode, u8 attrs) +{ +	if (fat_mode_can_hold_ro(inode)) +		MSDOS_I(inode)->i_attrs = attrs & ATTR_UNUSED; +	else +		MSDOS_I(inode)->i_attrs = attrs & (ATTR_UNUSED | ATTR_RO); +} + +static inline unsigned char fat_checksum(const __u8 *name) +{ +	unsigned char s = name[0]; +	s = (s<<7) + (s>>1) + name[1];	s = (s<<7) + (s>>1) + name[2]; +	s = (s<<7) + (s>>1) + name[3];	s = (s<<7) + (s>>1) + name[4]; +	s = (s<<7) + (s>>1) + name[5];	s = (s<<7) + (s>>1) + name[6]; +	s = (s<<7) + (s>>1) + name[7];	s = (s<<7) + (s>>1) + name[8]; +	s = (s<<7) + (s>>1) + name[9];	s = (s<<7) + (s>>1) + name[10]; +	return s; +} + +static inline sector_t fat_clus_to_blknr(struct msdos_sb_info *sbi, int clus) +{ +	return ((sector_t)clus - FAT_START_ENT) * sbi->sec_per_clus +		+ sbi->data_start; +} + +static inline void fat16_towchar(wchar_t *dst, const __u8 *src, size_t len) +{ +#ifdef __BIG_ENDIAN +	while (len--) { +		*dst++ = src[0] | (src[1] << 8); +		src += 2; +	} +#else +	memcpy(dst, src, len * 2); +#endif +} + +static inline void fatwchar_to16(__u8 *dst, const wchar_t *src, size_t len) +{ +#ifdef __BIG_ENDIAN +	while (len--) { +		dst[0] = *src & 0x00FF; +		dst[1] = (*src & 0xFF00) >> 8; +		dst += 2; +		src++; +	} +#else +	memcpy(dst, src, len * 2); +#endif +} + +/* fat/cache.c */ +extern void fat_cache_inval_inode(struct inode *inode); +extern int fat_get_cluster(struct inode *inode, int cluster, +			   int *fclus, int *dclus); +extern int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys, +		    unsigned long *mapped_blocks, int create); + +/* fat/dir.c */ +extern const struct file_operations fat_dir_operations; +extern int fat_search_long(struct inode *inode, const unsigned char *name, +			   int name_len, struct fat_slot_info *sinfo); +extern int fat_dir_empty(struct inode *dir); +extern int fat_subdirs(struct inode *dir); +extern int fat_scan(struct inode *dir, const unsigned char *name, +		    struct fat_slot_info *sinfo); +extern int fat_get_dotdot_entry(struct inode *dir, struct buffer_head **bh, +				struct msdos_dir_entry **de, loff_t *i_pos); +extern int fat_alloc_new_dir(struct inode *dir, struct timespec *ts); +extern int fat_add_entries(struct inode *dir, void *slots, int nr_slots, +			   struct fat_slot_info *sinfo); +extern int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo); + +/* fat/fatent.c */ +struct fat_entry { +	int entry; +	union { +		u8 *ent12_p[2]; +		__le16 *ent16_p; +		__le32 *ent32_p; +	} u; +	int nr_bhs; +	struct buffer_head *bhs[2]; +}; + +static inline void fatent_init(struct fat_entry *fatent) +{ +	fatent->nr_bhs = 0; +	fatent->entry = 0; +	fatent->u.ent32_p = NULL; +	fatent->bhs[0] = fatent->bhs[1] = NULL; +} + +static inline void fatent_set_entry(struct fat_entry *fatent, int entry) +{ +	fatent->entry = entry; +	fatent->u.ent32_p = NULL; +} + +static inline void fatent_brelse(struct fat_entry *fatent) +{ +	int i; +	fatent->u.ent32_p = NULL; +	for (i = 0; i < fatent->nr_bhs; i++) +		brelse(fatent->bhs[i]); +	fatent->nr_bhs = 0; +	fatent->bhs[0] = fatent->bhs[1] = NULL; +} + +extern void fat_ent_access_init(struct super_block *sb); +extern int fat_ent_read(struct inode *inode, struct fat_entry *fatent, +			int entry); +extern int fat_ent_write(struct inode *inode, struct fat_entry *fatent, +			 int new, int wait); +extern int fat_alloc_clusters(struct inode *inode, int *cluster, +			      int nr_cluster); +extern int fat_free_clusters(struct inode *inode, int cluster); +extern int fat_count_free_clusters(struct super_block *sb); + +/* fat/file.c */ +extern int fat_generic_ioctl(struct inode *inode, struct file *filp, +			     unsigned int cmd, unsigned long arg); +extern const struct file_operations fat_file_operations; +extern const struct inode_operations fat_file_inode_operations; +extern int fat_setattr(struct dentry * dentry, struct iattr * attr); +extern void fat_truncate(struct inode *inode); +extern int fat_getattr(struct vfsmount *mnt, struct dentry *dentry, +		       struct kstat *stat); + +/* fat/inode.c */ +extern void fat_attach(struct inode *inode, loff_t i_pos); +extern void fat_detach(struct inode *inode); +extern struct inode *fat_iget(struct super_block *sb, loff_t i_pos); +extern struct inode *fat_build_inode(struct super_block *sb, +			struct msdos_dir_entry *de, loff_t i_pos); +extern int fat_sync_inode(struct inode *inode); +extern int fat_fill_super(struct super_block *sb, void *data, int silent, +			const struct inode_operations *fs_dir_inode_ops, int isvfat); + +extern int fat_flush_inodes(struct super_block *sb, struct inode *i1, +		            struct inode *i2); +/* fat/misc.c */ +extern void fat_fs_panic(struct super_block *s, const char *fmt, ...) +	__attribute__ ((format (printf, 2, 3))) __cold; +extern void fat_clusters_flush(struct super_block *sb); +extern int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster); +extern void fat_time_fat2unix(struct msdos_sb_info *sbi, struct timespec *ts, +			      __le16 __time, __le16 __date, u8 time_cs); +extern void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec *ts, +			      __le16 *time, __le16 *date, u8 *time_cs); +extern int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs); + +int fat_cache_init(void); +void fat_cache_destroy(void); + +/* helper for printk */ +typedef unsigned long long	llu; + +#endif /* !_FAT_H */ diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c index fb98b3d847ed..da6eea47872f 100644 --- a/fs/fat/fatent.c +++ b/fs/fat/fatent.c @@ -7,6 +7,7 @@  #include <linux/fs.h>  #include <linux/msdos_fs.h>  #include <linux/blkdev.h> +#include "fat.h"  struct fatent_operations {  	void (*ent_blocknr)(struct super_block *, int, int *, sector_t *); @@ -92,8 +93,7 @@ static int fat12_ent_bread(struct super_block *sb, struct fat_entry *fatent,  err_brelse:  	brelse(bhs[0]);  err: -	printk(KERN_ERR "FAT: FAT read failed (blocknr %llu)\n", -	       (unsigned long long)blocknr); +	printk(KERN_ERR "FAT: FAT read failed (blocknr %llu)\n", (llu)blocknr);  	return -EIO;  } @@ -106,7 +106,7 @@ static int fat_ent_bread(struct super_block *sb, struct fat_entry *fatent,  	fatent->bhs[0] = sb_bread(sb, blocknr);  	if (!fatent->bhs[0]) {  		printk(KERN_ERR "FAT: FAT read failed (blocknr %llu)\n", -		       (unsigned long long)blocknr); +		       (llu)blocknr);  		return -EIO;  	}  	fatent->nr_bhs = 1; @@ -316,10 +316,20 @@ static inline int fat_ent_update_ptr(struct super_block *sb,  	/* Is this fatent's blocks including this entry? */  	if (!fatent->nr_bhs || bhs[0]->b_blocknr != blocknr)  		return 0; -	/* Does this entry need the next block? */ -	if (sbi->fat_bits == 12 && (offset + 1) >= sb->s_blocksize) { -		if (fatent->nr_bhs != 2 || bhs[1]->b_blocknr != (blocknr + 1)) -			return 0; +	if (sbi->fat_bits == 12) { +		if ((offset + 1) < sb->s_blocksize) { +			/* This entry is on bhs[0]. */ +			if (fatent->nr_bhs == 2) { +				brelse(bhs[1]); +				fatent->nr_bhs = 1; +			} +		} else { +			/* This entry needs the next block. */ +			if (fatent->nr_bhs != 2) +				return 0; +			if (bhs[1]->b_blocknr != (blocknr + 1)) +				return 0; +		}  	}  	ops->ent_set_ptr(fatent, offset);  	return 1; diff --git a/fs/fat/file.c b/fs/fat/file.c index 81e203288340..0a7f4a9918b3 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -10,13 +10,13 @@  #include <linux/module.h>  #include <linux/mount.h>  #include <linux/time.h> -#include <linux/msdos_fs.h>  #include <linux/buffer_head.h>  #include <linux/writeback.h>  #include <linux/backing-dev.h>  #include <linux/blkdev.h>  #include <linux/fsnotify.h>  #include <linux/security.h> +#include "fat.h"  int fat_generic_ioctl(struct inode *inode, struct file *filp,  		      unsigned int cmd, unsigned long arg) @@ -29,10 +29,9 @@ int fat_generic_ioctl(struct inode *inode, struct file *filp,  	{  		u32 attr; -		if (inode->i_ino == MSDOS_ROOT_INO) -			attr = ATTR_DIR; -		else -			attr = fat_attr(inode); +		mutex_lock(&inode->i_mutex); +		attr = fat_make_attrs(inode); +		mutex_unlock(&inode->i_mutex);  		return put_user(attr, user_attr);  	} @@ -62,20 +61,16 @@ int fat_generic_ioctl(struct inode *inode, struct file *filp,  		/* Merge in ATTR_VOLUME and ATTR_DIR */  		attr |= (MSDOS_I(inode)->i_attrs & ATTR_VOLUME) |  			(is_dir ? ATTR_DIR : 0); -		oldattr = fat_attr(inode); +		oldattr = fat_make_attrs(inode);  		/* Equivalent to a chmod() */  		ia.ia_valid = ATTR_MODE | ATTR_CTIME;  		ia.ia_ctime = current_fs_time(inode->i_sb); -		if (is_dir) { -			ia.ia_mode = MSDOS_MKMODE(attr, -				S_IRWXUGO & ~sbi->options.fs_dmask) -				| S_IFDIR; -		} else { -			ia.ia_mode = MSDOS_MKMODE(attr, -				(S_IRUGO | S_IWUGO | (inode->i_mode & S_IXUGO)) -				& ~sbi->options.fs_fmask) -				| S_IFREG; +		if (is_dir) +			ia.ia_mode = fat_make_mode(sbi, attr, S_IRWXUGO); +		else { +			ia.ia_mode = fat_make_mode(sbi, attr, +				S_IRUGO | S_IWUGO | (inode->i_mode & S_IXUGO));  		}  		/* The root directory has no attributes */ @@ -115,7 +110,7 @@ int fat_generic_ioctl(struct inode *inode, struct file *filp,  				inode->i_flags &= S_IMMUTABLE;  		} -		MSDOS_I(inode)->i_attrs = attr & ATTR_UNUSED; +		fat_save_attrs(inode, attr);  		mark_inode_dirty(inode);  up:  		mnt_drop_write(filp->f_path.mnt); @@ -274,7 +269,7 @@ static int fat_sanitize_mode(const struct msdos_sb_info *sbi,  	/*  	 * Note, the basic check is already done by a caller of -	 * (attr->ia_mode & ~MSDOS_VALID_MODE) +	 * (attr->ia_mode & ~FAT_VALID_MODE)  	 */  	if (S_ISREG(inode->i_mode)) @@ -287,11 +282,18 @@ static int fat_sanitize_mode(const struct msdos_sb_info *sbi,  	/*  	 * Of the r and x bits, all (subject to umask) must be present. Of the  	 * w bits, either all (subject to umask) or none must be present. +	 * +	 * If fat_mode_can_hold_ro(inode) is false, can't change w bits.  	 */  	if ((perm & (S_IRUGO | S_IXUGO)) != (inode->i_mode & (S_IRUGO|S_IXUGO)))  		return -EPERM; -	if ((perm & S_IWUGO) && ((perm & S_IWUGO) != (S_IWUGO & ~mask))) -		return -EPERM; +	if (fat_mode_can_hold_ro(inode)) { +		if ((perm & S_IWUGO) && ((perm & S_IWUGO) != (S_IWUGO & ~mask))) +			return -EPERM; +	} else { +		if ((perm & S_IWUGO) != (S_IWUGO & ~mask)) +			return -EPERM; +	}  	*mode_ptr &= S_IFMT | perm; @@ -314,13 +316,15 @@ static int fat_allow_set_time(struct msdos_sb_info *sbi, struct inode *inode)  }  #define TIMES_SET_FLAGS	(ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET) +/* valid file mode bits */ +#define FAT_VALID_MODE	(S_IFREG | S_IFDIR | S_IRWXUGO)  int fat_setattr(struct dentry *dentry, struct iattr *attr)  {  	struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);  	struct inode *inode = dentry->d_inode; -	int error = 0;  	unsigned int ia_valid; +	int error;  	/*  	 * Expand the file. Since inode_setattr() updates ->i_size @@ -356,7 +360,7 @@ int fat_setattr(struct dentry *dentry, struct iattr *attr)  	    ((attr->ia_valid & ATTR_GID) &&  	     (attr->ia_gid != sbi->options.fs_gid)) ||  	    ((attr->ia_valid & ATTR_MODE) && -	     (attr->ia_mode & ~MSDOS_VALID_MODE))) +	     (attr->ia_mode & ~FAT_VALID_MODE)))  		error = -EPERM;  	if (error) { @@ -374,7 +378,8 @@ int fat_setattr(struct dentry *dentry, struct iattr *attr)  			attr->ia_valid &= ~ATTR_MODE;  	} -	error = inode_setattr(inode, attr); +	if (attr->ia_valid) +		error = inode_setattr(inode, attr);  out:  	return error;  } diff --git a/fs/fat/inode.c b/fs/fat/inode.c index cf621acd9e9a..d937aaf77374 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -16,7 +16,6 @@  #include <linux/slab.h>  #include <linux/smp_lock.h>  #include <linux/seq_file.h> -#include <linux/msdos_fs.h>  #include <linux/pagemap.h>  #include <linux/mpage.h>  #include <linux/buffer_head.h> @@ -27,7 +26,9 @@  #include <linux/uio.h>  #include <linux/writeback.h>  #include <linux/log2.h> +#include <linux/hash.h>  #include <asm/unaligned.h> +#include "fat.h"  #ifndef CONFIG_FAT_DEFAULT_IOCHARSET  /* if user don't select VFAT, this is undefined. */ @@ -63,7 +64,7 @@ static inline int __fat_get_block(struct inode *inode, sector_t iblock,  	sector_t phys;  	int err, offset; -	err = fat_bmap(inode, iblock, &phys, &mapped_blocks); +	err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create);  	if (err)  		return err;  	if (phys) { @@ -93,7 +94,7 @@ static inline int __fat_get_block(struct inode *inode, sector_t iblock,  	*max_blocks = min(mapped_blocks, *max_blocks);  	MSDOS_I(inode)->mmu_private += *max_blocks << sb->s_blocksize_bits; -	err = fat_bmap(inode, iblock, &phys, &mapped_blocks); +	err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create);  	if (err)  		return err; @@ -198,7 +199,14 @@ static ssize_t fat_direct_IO(int rw, struct kiocb *iocb,  static sector_t _fat_bmap(struct address_space *mapping, sector_t block)  { -	return generic_block_bmap(mapping, block, fat_get_block); +	sector_t blocknr; + +	/* fat_get_cluster() assumes the requested blocknr isn't truncated. */ +	mutex_lock(&mapping->host->i_mutex); +	blocknr = generic_block_bmap(mapping, block, fat_get_block); +	mutex_unlock(&mapping->host->i_mutex); + +	return blocknr;  }  static const struct address_space_operations fat_aops = { @@ -247,25 +255,21 @@ static void fat_hash_init(struct super_block *sb)  		INIT_HLIST_HEAD(&sbi->inode_hashtable[i]);  } -static inline unsigned long fat_hash(struct super_block *sb, loff_t i_pos) +static inline unsigned long fat_hash(loff_t i_pos)  { -	unsigned long tmp = (unsigned long)i_pos | (unsigned long) sb; -	tmp = tmp + (tmp >> FAT_HASH_BITS) + (tmp >> FAT_HASH_BITS * 2); -	return tmp & FAT_HASH_MASK; +	return hash_32(i_pos, FAT_HASH_BITS);  }  void fat_attach(struct inode *inode, loff_t i_pos)  { -	struct super_block *sb = inode->i_sb; -	struct msdos_sb_info *sbi = MSDOS_SB(sb); +	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb); +	struct hlist_head *head = sbi->inode_hashtable + fat_hash(i_pos);  	spin_lock(&sbi->inode_hash_lock);  	MSDOS_I(inode)->i_pos = i_pos; -	hlist_add_head(&MSDOS_I(inode)->i_fat_hash, -			sbi->inode_hashtable + fat_hash(sb, i_pos)); +	hlist_add_head(&MSDOS_I(inode)->i_fat_hash, head);  	spin_unlock(&sbi->inode_hash_lock);  } -  EXPORT_SYMBOL_GPL(fat_attach);  void fat_detach(struct inode *inode) @@ -276,13 +280,12 @@ void fat_detach(struct inode *inode)  	hlist_del_init(&MSDOS_I(inode)->i_fat_hash);  	spin_unlock(&sbi->inode_hash_lock);  } -  EXPORT_SYMBOL_GPL(fat_detach);  struct inode *fat_iget(struct super_block *sb, loff_t i_pos)  {  	struct msdos_sb_info *sbi = MSDOS_SB(sb); -	struct hlist_head *head = sbi->inode_hashtable + fat_hash(sb, i_pos); +	struct hlist_head *head = sbi->inode_hashtable + fat_hash(i_pos);  	struct hlist_node *_p;  	struct msdos_inode_info *i;  	struct inode *inode = NULL; @@ -341,8 +344,7 @@ static int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)  	if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {  		inode->i_generation &= ~1; -		inode->i_mode = MSDOS_MKMODE(de->attr, -			S_IRWXUGO & ~sbi->options.fs_dmask) | S_IFDIR; +		inode->i_mode = fat_make_mode(sbi, de->attr, S_IRWXUGO);  		inode->i_op = sbi->dir_ops;  		inode->i_fop = &fat_dir_operations; @@ -359,10 +361,9 @@ static int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)  		inode->i_nlink = fat_subdirs(inode);  	} else { /* not a directory */  		inode->i_generation |= 1; -		inode->i_mode = MSDOS_MKMODE(de->attr, -		    ((sbi->options.showexec && !is_exec(de->name + 8)) -			? S_IRUGO|S_IWUGO : S_IRWXUGO) -		    & ~sbi->options.fs_fmask) | S_IFREG; +		inode->i_mode = fat_make_mode(sbi, de->attr, +			((sbi->options.showexec && !is_exec(de->name + 8)) +			 ? S_IRUGO|S_IWUGO : S_IRWXUGO));  		MSDOS_I(inode)->i_start = le16_to_cpu(de->start);  		if (sbi->fat_bits == 32)  			MSDOS_I(inode)->i_start |= (le16_to_cpu(de->starthi) << 16); @@ -378,25 +379,16 @@ static int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)  		if (sbi->options.sys_immutable)  			inode->i_flags |= S_IMMUTABLE;  	} -	MSDOS_I(inode)->i_attrs = de->attr & ATTR_UNUSED; +	fat_save_attrs(inode, de->attr); +  	inode->i_blocks = ((inode->i_size + (sbi->cluster_size - 1))  			   & ~((loff_t)sbi->cluster_size - 1)) >> 9; -	inode->i_mtime.tv_sec = -		date_dos2unix(le16_to_cpu(de->time), le16_to_cpu(de->date), -			      sbi->options.tz_utc); -	inode->i_mtime.tv_nsec = 0; + +	fat_time_fat2unix(sbi, &inode->i_mtime, de->time, de->date, 0);  	if (sbi->options.isvfat) { -		int secs = de->ctime_cs / 100; -		int csecs = de->ctime_cs % 100; -		inode->i_ctime.tv_sec  = -			date_dos2unix(le16_to_cpu(de->ctime), -				      le16_to_cpu(de->cdate), -				      sbi->options.tz_utc) + secs; -		inode->i_ctime.tv_nsec = csecs * 10000000; -		inode->i_atime.tv_sec = -			date_dos2unix(0, le16_to_cpu(de->adate), -				      sbi->options.tz_utc); -		inode->i_atime.tv_nsec = 0; +		fat_time_fat2unix(sbi, &inode->i_ctime, de->ctime, +				  de->cdate, de->ctime_cs); +		fat_time_fat2unix(sbi, &inode->i_atime, 0, de->adate, 0);  	} else  		inode->i_ctime = inode->i_atime = inode->i_mtime; @@ -443,13 +435,8 @@ static void fat_delete_inode(struct inode *inode)  static void fat_clear_inode(struct inode *inode)  { -	struct super_block *sb = inode->i_sb; -	struct msdos_sb_info *sbi = MSDOS_SB(sb); - -	spin_lock(&sbi->inode_hash_lock);  	fat_cache_inval_inode(inode); -	hlist_del_init(&MSDOS_I(inode)->i_fat_hash); -	spin_unlock(&sbi->inode_hash_lock); +	fat_detach(inode);  }  static void fat_write_super(struct super_block *sb) @@ -555,6 +542,20 @@ static int fat_statfs(struct dentry *dentry, struct kstatfs *buf)  	return 0;  } +static inline loff_t fat_i_pos_read(struct msdos_sb_info *sbi, +				    struct inode *inode) +{ +	loff_t i_pos; +#if BITS_PER_LONG == 32 +	spin_lock(&sbi->inode_hash_lock); +#endif +	i_pos = MSDOS_I(inode)->i_pos; +#if BITS_PER_LONG == 32 +	spin_unlock(&sbi->inode_hash_lock); +#endif +	return i_pos; +} +  static int fat_write_inode(struct inode *inode, int wait)  {  	struct super_block *sb = inode->i_sb; @@ -564,9 +565,12 @@ static int fat_write_inode(struct inode *inode, int wait)  	loff_t i_pos;  	int err; +	if (inode->i_ino == MSDOS_ROOT_INO) +		return 0; +  retry: -	i_pos = MSDOS_I(inode)->i_pos; -	if (inode->i_ino == MSDOS_ROOT_INO || !i_pos) +	i_pos = fat_i_pos_read(sbi, inode); +	if (!i_pos)  		return 0;  	bh = sb_bread(sb, i_pos >> sbi->dir_per_block_bits); @@ -588,19 +592,17 @@ retry:  		raw_entry->size = 0;  	else  		raw_entry->size = cpu_to_le32(inode->i_size); -	raw_entry->attr = fat_attr(inode); +	raw_entry->attr = fat_make_attrs(inode);  	raw_entry->start = cpu_to_le16(MSDOS_I(inode)->i_logstart);  	raw_entry->starthi = cpu_to_le16(MSDOS_I(inode)->i_logstart >> 16); -	fat_date_unix2dos(inode->i_mtime.tv_sec, &raw_entry->time, -			  &raw_entry->date, sbi->options.tz_utc); +	fat_time_unix2fat(sbi, &inode->i_mtime, &raw_entry->time, +			  &raw_entry->date, NULL);  	if (sbi->options.isvfat) {  		__le16 atime; -		fat_date_unix2dos(inode->i_ctime.tv_sec, &raw_entry->ctime, -				  &raw_entry->cdate, sbi->options.tz_utc); -		fat_date_unix2dos(inode->i_atime.tv_sec, &atime, -				  &raw_entry->adate, sbi->options.tz_utc); -		raw_entry->ctime_cs = (inode->i_ctime.tv_sec & 1) * 100 + -			inode->i_ctime.tv_nsec / 10000000; +		fat_time_unix2fat(sbi, &inode->i_ctime, &raw_entry->ctime, +				  &raw_entry->cdate, &raw_entry->ctime_cs); +		fat_time_unix2fat(sbi, &inode->i_atime, &atime, +				  &raw_entry->adate, NULL);  	}  	spin_unlock(&sbi->inode_hash_lock);  	mark_buffer_dirty(bh); @@ -819,8 +821,10 @@ static int fat_show_options(struct seq_file *m, struct vfsmount *mnt)  			seq_puts(m, ",uni_xlate");  		if (!opts->numtail)  			seq_puts(m, ",nonumtail"); +		if (opts->rodir) +			seq_puts(m, ",rodir");  	} -	if (sbi->options.flush) +	if (opts->flush)  		seq_puts(m, ",flush");  	if (opts->tz_utc)  		seq_puts(m, ",tz=UTC"); @@ -836,7 +840,7 @@ enum {  	Opt_charset, Opt_shortname_lower, Opt_shortname_win95,  	Opt_shortname_winnt, Opt_shortname_mixed, Opt_utf8_no, Opt_utf8_yes,  	Opt_uni_xl_no, Opt_uni_xl_yes, Opt_nonumtail_no, Opt_nonumtail_yes, -	Opt_obsolate, Opt_flush, Opt_tz_utc, Opt_err, +	Opt_obsolate, Opt_flush, Opt_tz_utc, Opt_rodir, Opt_err,  };  static const match_table_t fat_tokens = { @@ -908,6 +912,7 @@ static const match_table_t vfat_tokens = {  	{Opt_nonumtail_yes, "nonumtail=yes"},  	{Opt_nonumtail_yes, "nonumtail=true"},  	{Opt_nonumtail_yes, "nonumtail"}, +	{Opt_rodir, "rodir"},  	{Opt_err, NULL}  }; @@ -927,10 +932,13 @@ static int parse_options(char *options, int is_vfat, int silent, int *debug,  	opts->allow_utime = -1;  	opts->codepage = fat_default_codepage;  	opts->iocharset = fat_default_iocharset; -	if (is_vfat) +	if (is_vfat) {  		opts->shortname = VFAT_SFN_DISPLAY_LOWER|VFAT_SFN_CREATE_WIN95; -	else +		opts->rodir = 0; +	} else {  		opts->shortname = 0; +		opts->rodir = 1; +	}  	opts->name_check = 'n';  	opts->quiet = opts->showexec = opts->sys_immutable = opts->dotsOK =  0;  	opts->utf8 = opts->unicode_xlate = 0; @@ -1081,6 +1089,9 @@ static int parse_options(char *options, int is_vfat, int silent, int *debug,  		case Opt_nonumtail_yes:		/* empty or 1 or yes or true */  			opts->numtail = 0;	/* negated option */  			break; +		case Opt_rodir: +			opts->rodir = 1; +			break;  		/* obsolete mount options */  		case Opt_obsolate: @@ -1126,7 +1137,7 @@ static int fat_read_root(struct inode *inode)  	inode->i_gid = sbi->options.fs_gid;  	inode->i_version++;  	inode->i_generation = 0; -	inode->i_mode = (S_IRWXUGO & ~sbi->options.fs_dmask) | S_IFDIR; +	inode->i_mode = fat_make_mode(sbi, ATTR_DIR, S_IRWXUGO);  	inode->i_op = sbi->dir_ops;  	inode->i_fop = &fat_dir_operations;  	if (sbi->fat_bits == 32) { @@ -1143,7 +1154,7 @@ static int fat_read_root(struct inode *inode)  	MSDOS_I(inode)->i_logstart = 0;  	MSDOS_I(inode)->mmu_private = inode->i_size; -	MSDOS_I(inode)->i_attrs = ATTR_NONE; +	fat_save_attrs(inode, ATTR_DIR);  	inode->i_mtime.tv_sec = inode->i_atime.tv_sec = inode->i_ctime.tv_sec = 0;  	inode->i_mtime.tv_nsec = inode->i_atime.tv_nsec = inode->i_ctime.tv_nsec = 0;  	inode->i_nlink = fat_subdirs(inode)+2; diff --git a/fs/fat/misc.c b/fs/fat/misc.c index 79fb98ad36d4..ac39ebcc1496 100644 --- a/fs/fat/misc.c +++ b/fs/fat/misc.c @@ -8,8 +8,8 @@  #include <linux/module.h>  #include <linux/fs.h> -#include <linux/msdos_fs.h>  #include <linux/buffer_head.h> +#include "fat.h"  /*   * fat_fs_panic reports a severe file system problem and sets the file system @@ -124,8 +124,9 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)  			mark_inode_dirty(inode);  	}  	if (new_fclus != (inode->i_blocks >> (sbi->cluster_bits - 9))) { -		fat_fs_panic(sb, "clusters badly computed (%d != %lu)", -			new_fclus, inode->i_blocks >> (sbi->cluster_bits - 9)); +		fat_fs_panic(sb, "clusters badly computed (%d != %llu)", +			     new_fclus, +			     (llu)(inode->i_blocks >> (sbi->cluster_bits - 9)));  		fat_cache_inval_inode(inode);  	}  	inode->i_blocks += nr_cluster << (sbi->cluster_bits - 9); @@ -135,65 +136,131 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)  extern struct timezone sys_tz; +/* + * The epoch of FAT timestamp is 1980. + *     :  bits :     value + * date:  0 -  4: day	(1 -  31) + * date:  5 -  8: month	(1 -  12) + * date:  9 - 15: year	(0 - 127) from 1980 + * time:  0 -  4: sec	(0 -  29) 2sec counts + * time:  5 - 10: min	(0 -  59) + * time: 11 - 15: hour	(0 -  23) + */ +#define SECS_PER_MIN	60 +#define SECS_PER_HOUR	(60 * 60) +#define SECS_PER_DAY	(SECS_PER_HOUR * 24) +#define UNIX_SECS_1980	315532800L +#if BITS_PER_LONG == 64 +#define UNIX_SECS_2108	4354819200L +#endif +/* days between 1.1.70 and 1.1.80 (2 leap days) */ +#define DAYS_DELTA	(365 * 10 + 2) +/* 120 (2100 - 1980) isn't leap year */ +#define YEAR_2100	120 +#define IS_LEAP_YEAR(y)	(!((y) & 3) && (y) != YEAR_2100) +  /* Linear day numbers of the respective 1sts in non-leap years. */ -static int day_n[] = { -   /* Jan  Feb  Mar  Apr   May  Jun  Jul  Aug  Sep  Oct  Nov  Dec */ -	0,  31,  59,  90,  120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0 +static time_t days_in_year[] = { +	/* Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec */ +	0,   0,  31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0,  }; -/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */ -int date_dos2unix(unsigned short time, unsigned short date, int tz_utc) +/* Convert a FAT time/date pair to a UNIX date (seconds since 1 1 70). */ +void fat_time_fat2unix(struct msdos_sb_info *sbi, struct timespec *ts, +		       __le16 __time, __le16 __date, u8 time_cs)  { -	int month, year, secs; +	u16 time = le16_to_cpu(__time), date = le16_to_cpu(__date); +	time_t second, day, leap_day, month, year; -	/* -	 * first subtract and mask after that... Otherwise, if -	 * date == 0, bad things happen -	 */ -	month = ((date >> 5) - 1) & 15; -	year = date >> 9; -	secs = (time & 31)*2+60*((time >> 5) & 63)+(time >> 11)*3600+86400* -	    ((date & 31)-1+day_n[month]+(year/4)+year*365-((year & 3) == 0 && -	    month < 2 ? 1 : 0)+3653); -			/* days since 1.1.70 plus 80's leap day */ -	if (!tz_utc) -		secs += sys_tz.tz_minuteswest*60; -	return secs; +	year  = date >> 9; +	month = max(1, (date >> 5) & 0xf); +	day   = max(1, date & 0x1f) - 1; + +	leap_day = (year + 3) / 4; +	if (year > YEAR_2100)		/* 2100 isn't leap year */ +		leap_day--; +	if (IS_LEAP_YEAR(year) && month > 2) +		leap_day++; + +	second =  (time & 0x1f) << 1; +	second += ((time >> 5) & 0x3f) * SECS_PER_MIN; +	second += (time >> 11) * SECS_PER_HOUR; +	second += (year * 365 + leap_day +		   + days_in_year[month] + day +		   + DAYS_DELTA) * SECS_PER_DAY; + +	if (!sbi->options.tz_utc) +		second += sys_tz.tz_minuteswest * SECS_PER_MIN; + +	if (time_cs) { +		ts->tv_sec = second + (time_cs / 100); +		ts->tv_nsec = (time_cs % 100) * 10000000; +	} else { +		ts->tv_sec = second; +		ts->tv_nsec = 0; +	}  } -/* Convert linear UNIX date to a MS-DOS time/date pair. */ -void fat_date_unix2dos(int unix_date, __le16 *time, __le16 *date, int tz_utc) +/* Convert linear UNIX date to a FAT time/date pair. */ +void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec *ts, +		       __le16 *time, __le16 *date, u8 *time_cs)  { -	int day, year, nl_day, month; +	time_t second = ts->tv_sec; +	time_t day, leap_day, month, year; -	if (!tz_utc) -		unix_date -= sys_tz.tz_minuteswest*60; +	if (!sbi->options.tz_utc) +		second -= sys_tz.tz_minuteswest * SECS_PER_MIN;  	/* Jan 1 GMT 00:00:00 1980. But what about another time zone? */ -	if (unix_date < 315532800) -		unix_date = 315532800; - -	*time = cpu_to_le16((unix_date % 60)/2+(((unix_date/60) % 60) << 5)+ -	    (((unix_date/3600) % 24) << 11)); -	day = unix_date/86400-3652; -	year = day/365; -	if ((year+3)/4+365*year > day) +	if (second < UNIX_SECS_1980) { +		*time = 0; +		*date = cpu_to_le16((0 << 9) | (1 << 5) | 1); +		if (time_cs) +			*time_cs = 0; +		return; +	} +#if BITS_PER_LONG == 64 +	if (second >= UNIX_SECS_2108) { +		*time = cpu_to_le16((23 << 11) | (59 << 5) | 29); +		*date = cpu_to_le16((127 << 9) | (12 << 5) | 31); +		if (time_cs) +			*time_cs = 199; +		return; +	} +#endif + +	day = second / SECS_PER_DAY - DAYS_DELTA; +	year = day / 365; +	leap_day = (year + 3) / 4; +	if (year > YEAR_2100)		/* 2100 isn't leap year */ +		leap_day--; +	if (year * 365 + leap_day > day)  		year--; -	day -= (year+3)/4+365*year; -	if (day == 59 && !(year & 3)) { -		nl_day = day; +	leap_day = (year + 3) / 4; +	if (year > YEAR_2100)		/* 2100 isn't leap year */ +		leap_day--; +	day -= year * 365 + leap_day; + +	if (IS_LEAP_YEAR(year) && day == days_in_year[3]) {  		month = 2;  	} else { -		nl_day = (year & 3) || day <= 59 ? day : day-1; -		for (month = 0; month < 12; month++) { -			if (day_n[month] > nl_day) +		if (IS_LEAP_YEAR(year) && day > days_in_year[3]) +			day--; +		for (month = 1; month < 12; month++) { +			if (days_in_year[month + 1] > day)  				break;  		}  	} -	*date = cpu_to_le16(nl_day-day_n[month-1]+1+(month << 5)+(year << 9)); -} +	day -= days_in_year[month]; -EXPORT_SYMBOL_GPL(fat_date_unix2dos); +	*time = cpu_to_le16(((second / SECS_PER_HOUR) % 24) << 11 +			    | ((second / SECS_PER_MIN) % 60) << 5 +			    | (second % SECS_PER_MIN) >> 1); +	*date = cpu_to_le16((year << 9) | (month << 5) | (day + 1)); +	if (time_cs) +		*time_cs = (ts->tv_sec & 1) * 100 + ts->tv_nsec / 10000000; +} +EXPORT_SYMBOL_GPL(fat_time_unix2fat);  int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs)  { diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c new file mode 100644 index 000000000000..7ba03a4acbe0 --- /dev/null +++ b/fs/fat/namei_msdos.c @@ -0,0 +1,706 @@ +/* + *  linux/fs/msdos/namei.c + * + *  Written 1992,1993 by Werner Almesberger + *  Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu> + *  Rewritten for constant inumbers 1999 by Al Viro + */ + +#include <linux/module.h> +#include <linux/time.h> +#include <linux/buffer_head.h> +#include <linux/smp_lock.h> +#include "fat.h" + +/* Characters that are undesirable in an MS-DOS file name */ +static unsigned char bad_chars[] = "*?<>|\""; +static unsigned char bad_if_strict[] = "+=,; "; + +/***** Formats an MS-DOS file name. Rejects invalid names. */ +static int msdos_format_name(const unsigned char *name, int len, +			     unsigned char *res, struct fat_mount_options *opts) +	/* +	 * name is the proposed name, len is its length, res is +	 * the resulting name, opts->name_check is either (r)elaxed, +	 * (n)ormal or (s)trict, opts->dotsOK allows dots at the +	 * beginning of name (for hidden files) +	 */ +{ +	unsigned char *walk; +	unsigned char c; +	int space; + +	if (name[0] == '.') {	/* dotfile because . and .. already done */ +		if (opts->dotsOK) { +			/* Get rid of dot - test for it elsewhere */ +			name++; +			len--; +		} else +			return -EINVAL; +	} +	/* +	 * disallow names that _really_ start with a dot +	 */ +	space = 1; +	c = 0; +	for (walk = res; len && walk - res < 8; walk++) { +		c = *name++; +		len--; +		if (opts->name_check != 'r' && strchr(bad_chars, c)) +			return -EINVAL; +		if (opts->name_check == 's' && strchr(bad_if_strict, c)) +			return -EINVAL; +		if (c >= 'A' && c <= 'Z' && opts->name_check == 's') +			return -EINVAL; +		if (c < ' ' || c == ':' || c == '\\') +			return -EINVAL; +	/* +	 * 0xE5 is legal as a first character, but we must substitute +	 * 0x05 because 0xE5 marks deleted files.  Yes, DOS really +	 * does this. +	 * It seems that Microsoft hacked DOS to support non-US +	 * characters after the 0xE5 character was already in use to +	 * mark deleted files. +	 */ +		if ((res == walk) && (c == 0xE5)) +			c = 0x05; +		if (c == '.') +			break; +		space = (c == ' '); +		*walk = (!opts->nocase && c >= 'a' && c <= 'z') ? c - 32 : c; +	} +	if (space) +		return -EINVAL; +	if (opts->name_check == 's' && len && c != '.') { +		c = *name++; +		len--; +		if (c != '.') +			return -EINVAL; +	} +	while (c != '.' && len--) +		c = *name++; +	if (c == '.') { +		while (walk - res < 8) +			*walk++ = ' '; +		while (len > 0 && walk - res < MSDOS_NAME) { +			c = *name++; +			len--; +			if (opts->name_check != 'r' && strchr(bad_chars, c)) +				return -EINVAL; +			if (opts->name_check == 's' && +			    strchr(bad_if_strict, c)) +				return -EINVAL; +			if (c < ' ' || c == ':' || c == '\\') +				return -EINVAL; +			if (c == '.') { +				if (opts->name_check == 's') +					return -EINVAL; +				break; +			} +			if (c >= 'A' && c <= 'Z' && opts->name_check == 's') +				return -EINVAL; +			space = c == ' '; +			if (!opts->nocase && c >= 'a' && c <= 'z') +				*walk++ = c - 32; +			else +				*walk++ = c; +		} +		if (space) +			return -EINVAL; +		if (opts->name_check == 's' && len) +			return -EINVAL; +	} +	while (walk - res < MSDOS_NAME) +		*walk++ = ' '; + +	return 0; +} + +/***** Locates a directory entry.  Uses unformatted name. */ +static int msdos_find(struct inode *dir, const unsigned char *name, int len, +		      struct fat_slot_info *sinfo) +{ +	struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb); +	unsigned char msdos_name[MSDOS_NAME]; +	int err; + +	err = msdos_format_name(name, len, msdos_name, &sbi->options); +	if (err) +		return -ENOENT; + +	err = fat_scan(dir, msdos_name, sinfo); +	if (!err && sbi->options.dotsOK) { +		if (name[0] == '.') { +			if (!(sinfo->de->attr & ATTR_HIDDEN)) +				err = -ENOENT; +		} else { +			if (sinfo->de->attr & ATTR_HIDDEN) +				err = -ENOENT; +		} +		if (err) +			brelse(sinfo->bh); +	} +	return err; +} + +/* + * Compute the hash for the msdos name corresponding to the dentry. + * Note: if the name is invalid, we leave the hash code unchanged so + * that the existing dentry can be used. The msdos fs routines will + * return ENOENT or EINVAL as appropriate. + */ +static int msdos_hash(struct dentry *dentry, struct qstr *qstr) +{ +	struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options; +	unsigned char msdos_name[MSDOS_NAME]; +	int error; + +	error = msdos_format_name(qstr->name, qstr->len, msdos_name, options); +	if (!error) +		qstr->hash = full_name_hash(msdos_name, MSDOS_NAME); +	return 0; +} + +/* + * Compare two msdos names. If either of the names are invalid, + * we fall back to doing the standard name comparison. + */ +static int msdos_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b) +{ +	struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options; +	unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME]; +	int error; + +	error = msdos_format_name(a->name, a->len, a_msdos_name, options); +	if (error) +		goto old_compare; +	error = msdos_format_name(b->name, b->len, b_msdos_name, options); +	if (error) +		goto old_compare; +	error = memcmp(a_msdos_name, b_msdos_name, MSDOS_NAME); +out: +	return error; + +old_compare: +	error = 1; +	if (a->len == b->len) +		error = memcmp(a->name, b->name, a->len); +	goto out; +} + +static struct dentry_operations msdos_dentry_operations = { +	.d_hash		= msdos_hash, +	.d_compare	= msdos_cmp, +}; + +/* + * AV. Wrappers for FAT sb operations. Is it wise? + */ + +/***** Get inode using directory and name */ +static struct dentry *msdos_lookup(struct inode *dir, struct dentry *dentry, +				   struct nameidata *nd) +{ +	struct super_block *sb = dir->i_sb; +	struct fat_slot_info sinfo; +	struct inode *inode; +	int err; + +	lock_super(sb); + +	err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo); +	if (err) { +		if (err == -ENOENT) { +			inode = NULL; +			goto out; +		} +		goto error; +	} + +	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); +	brelse(sinfo.bh); +	if (IS_ERR(inode)) { +		err = PTR_ERR(inode); +		goto error; +	} +out: +	unlock_super(sb); +	dentry->d_op = &msdos_dentry_operations; +	dentry = d_splice_alias(inode, dentry); +	if (dentry) +		dentry->d_op = &msdos_dentry_operations; +	return dentry; + +error: +	unlock_super(sb); +	return ERR_PTR(err); +} + +/***** Creates a directory entry (name is already formatted). */ +static int msdos_add_entry(struct inode *dir, const unsigned char *name, +			   int is_dir, int is_hid, int cluster, +			   struct timespec *ts, struct fat_slot_info *sinfo) +{ +	struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb); +	struct msdos_dir_entry de; +	__le16 time, date; +	int err; + +	memcpy(de.name, name, MSDOS_NAME); +	de.attr = is_dir ? ATTR_DIR : ATTR_ARCH; +	if (is_hid) +		de.attr |= ATTR_HIDDEN; +	de.lcase = 0; +	fat_time_unix2fat(sbi, ts, &time, &date, NULL); +	de.cdate = de.adate = 0; +	de.ctime = 0; +	de.ctime_cs = 0; +	de.time = time; +	de.date = date; +	de.start = cpu_to_le16(cluster); +	de.starthi = cpu_to_le16(cluster >> 16); +	de.size = 0; + +	err = fat_add_entries(dir, &de, 1, sinfo); +	if (err) +		return err; + +	dir->i_ctime = dir->i_mtime = *ts; +	if (IS_DIRSYNC(dir)) +		(void)fat_sync_inode(dir); +	else +		mark_inode_dirty(dir); + +	return 0; +} + +/***** Create a file */ +static int msdos_create(struct inode *dir, struct dentry *dentry, int mode, +			struct nameidata *nd) +{ +	struct super_block *sb = dir->i_sb; +	struct inode *inode = NULL; +	struct fat_slot_info sinfo; +	struct timespec ts; +	unsigned char msdos_name[MSDOS_NAME]; +	int err, is_hid; + +	lock_super(sb); + +	err = msdos_format_name(dentry->d_name.name, dentry->d_name.len, +				msdos_name, &MSDOS_SB(sb)->options); +	if (err) +		goto out; +	is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.'); +	/* Have to do it due to foo vs. .foo conflicts */ +	if (!fat_scan(dir, msdos_name, &sinfo)) { +		brelse(sinfo.bh); +		err = -EINVAL; +		goto out; +	} + +	ts = CURRENT_TIME_SEC; +	err = msdos_add_entry(dir, msdos_name, 0, is_hid, 0, &ts, &sinfo); +	if (err) +		goto out; +	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); +	brelse(sinfo.bh); +	if (IS_ERR(inode)) { +		err = PTR_ERR(inode); +		goto out; +	} +	inode->i_mtime = inode->i_atime = inode->i_ctime = ts; +	/* timestamp is already written, so mark_inode_dirty() is unneeded. */ + +	d_instantiate(dentry, inode); +out: +	unlock_super(sb); +	if (!err) +		err = fat_flush_inodes(sb, dir, inode); +	return err; +} + +/***** Remove a directory */ +static int msdos_rmdir(struct inode *dir, struct dentry *dentry) +{ +	struct super_block *sb = dir->i_sb; +	struct inode *inode = dentry->d_inode; +	struct fat_slot_info sinfo; +	int err; + +	lock_super(sb); +	/* +	 * Check whether the directory is not in use, then check +	 * whether it is empty. +	 */ +	err = fat_dir_empty(inode); +	if (err) +		goto out; +	err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo); +	if (err) +		goto out; + +	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */ +	if (err) +		goto out; +	drop_nlink(dir); + +	clear_nlink(inode); +	inode->i_ctime = CURRENT_TIME_SEC; +	fat_detach(inode); +out: +	unlock_super(sb); +	if (!err) +		err = fat_flush_inodes(sb, dir, inode); + +	return err; +} + +/***** Make a directory */ +static int msdos_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ +	struct super_block *sb = dir->i_sb; +	struct fat_slot_info sinfo; +	struct inode *inode; +	unsigned char msdos_name[MSDOS_NAME]; +	struct timespec ts; +	int err, is_hid, cluster; + +	lock_super(sb); + +	err = msdos_format_name(dentry->d_name.name, dentry->d_name.len, +				msdos_name, &MSDOS_SB(sb)->options); +	if (err) +		goto out; +	is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.'); +	/* foo vs .foo situation */ +	if (!fat_scan(dir, msdos_name, &sinfo)) { +		brelse(sinfo.bh); +		err = -EINVAL; +		goto out; +	} + +	ts = CURRENT_TIME_SEC; +	cluster = fat_alloc_new_dir(dir, &ts); +	if (cluster < 0) { +		err = cluster; +		goto out; +	} +	err = msdos_add_entry(dir, msdos_name, 1, is_hid, cluster, &ts, &sinfo); +	if (err) +		goto out_free; +	inc_nlink(dir); + +	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); +	brelse(sinfo.bh); +	if (IS_ERR(inode)) { +		err = PTR_ERR(inode); +		/* the directory was completed, just return a error */ +		goto out; +	} +	inode->i_nlink = 2; +	inode->i_mtime = inode->i_atime = inode->i_ctime = ts; +	/* timestamp is already written, so mark_inode_dirty() is unneeded. */ + +	d_instantiate(dentry, inode); + +	unlock_super(sb); +	fat_flush_inodes(sb, dir, inode); +	return 0; + +out_free: +	fat_free_clusters(dir, cluster); +out: +	unlock_super(sb); +	return err; +} + +/***** Unlink a file */ +static int msdos_unlink(struct inode *dir, struct dentry *dentry) +{ +	struct inode *inode = dentry->d_inode; +	struct super_block *sb= inode->i_sb; +	struct fat_slot_info sinfo; +	int err; + +	lock_super(sb); +	err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo); +	if (err) +		goto out; + +	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */ +	if (err) +		goto out; +	clear_nlink(inode); +	inode->i_ctime = CURRENT_TIME_SEC; +	fat_detach(inode); +out: +	unlock_super(sb); +	if (!err) +		err = fat_flush_inodes(sb, dir, inode); + +	return err; +} + +static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name, +			   struct dentry *old_dentry, +			   struct inode *new_dir, unsigned char *new_name, +			   struct dentry *new_dentry, int is_hid) +{ +	struct buffer_head *dotdot_bh; +	struct msdos_dir_entry *dotdot_de; +	struct inode *old_inode, *new_inode; +	struct fat_slot_info old_sinfo, sinfo; +	struct timespec ts; +	loff_t dotdot_i_pos, new_i_pos; +	int err, old_attrs, is_dir, update_dotdot, corrupt = 0; + +	old_sinfo.bh = sinfo.bh = dotdot_bh = NULL; +	old_inode = old_dentry->d_inode; +	new_inode = new_dentry->d_inode; + +	err = fat_scan(old_dir, old_name, &old_sinfo); +	if (err) { +		err = -EIO; +		goto out; +	} + +	is_dir = S_ISDIR(old_inode->i_mode); +	update_dotdot = (is_dir && old_dir != new_dir); +	if (update_dotdot) { +		if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de, +					 &dotdot_i_pos) < 0) { +			err = -EIO; +			goto out; +		} +	} + +	old_attrs = MSDOS_I(old_inode)->i_attrs; +	err = fat_scan(new_dir, new_name, &sinfo); +	if (!err) { +		if (!new_inode) { +			/* "foo" -> ".foo" case. just change the ATTR_HIDDEN */ +			if (sinfo.de != old_sinfo.de) { +				err = -EINVAL; +				goto out; +			} +			if (is_hid) +				MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN; +			else +				MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN; +			if (IS_DIRSYNC(old_dir)) { +				err = fat_sync_inode(old_inode); +				if (err) { +					MSDOS_I(old_inode)->i_attrs = old_attrs; +					goto out; +				} +			} else +				mark_inode_dirty(old_inode); + +			old_dir->i_version++; +			old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC; +			if (IS_DIRSYNC(old_dir)) +				(void)fat_sync_inode(old_dir); +			else +				mark_inode_dirty(old_dir); +			goto out; +		} +	} + +	ts = CURRENT_TIME_SEC; +	if (new_inode) { +		if (err) +			goto out; +		if (is_dir) { +			err = fat_dir_empty(new_inode); +			if (err) +				goto out; +		} +		new_i_pos = MSDOS_I(new_inode)->i_pos; +		fat_detach(new_inode); +	} else { +		err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0, +				      &ts, &sinfo); +		if (err) +			goto out; +		new_i_pos = sinfo.i_pos; +	} +	new_dir->i_version++; + +	fat_detach(old_inode); +	fat_attach(old_inode, new_i_pos); +	if (is_hid) +		MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN; +	else +		MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN; +	if (IS_DIRSYNC(new_dir)) { +		err = fat_sync_inode(old_inode); +		if (err) +			goto error_inode; +	} else +		mark_inode_dirty(old_inode); + +	if (update_dotdot) { +		int start = MSDOS_I(new_dir)->i_logstart; +		dotdot_de->start = cpu_to_le16(start); +		dotdot_de->starthi = cpu_to_le16(start >> 16); +		mark_buffer_dirty(dotdot_bh); +		if (IS_DIRSYNC(new_dir)) { +			err = sync_dirty_buffer(dotdot_bh); +			if (err) +				goto error_dotdot; +		} +		drop_nlink(old_dir); +		if (!new_inode) +			inc_nlink(new_dir); +	} + +	err = fat_remove_entries(old_dir, &old_sinfo);	/* and releases bh */ +	old_sinfo.bh = NULL; +	if (err) +		goto error_dotdot; +	old_dir->i_version++; +	old_dir->i_ctime = old_dir->i_mtime = ts; +	if (IS_DIRSYNC(old_dir)) +		(void)fat_sync_inode(old_dir); +	else +		mark_inode_dirty(old_dir); + +	if (new_inode) { +		drop_nlink(new_inode); +		if (is_dir) +			drop_nlink(new_inode); +		new_inode->i_ctime = ts; +	} +out: +	brelse(sinfo.bh); +	brelse(dotdot_bh); +	brelse(old_sinfo.bh); +	return err; + +error_dotdot: +	/* data cluster is shared, serious corruption */ +	corrupt = 1; + +	if (update_dotdot) { +		int start = MSDOS_I(old_dir)->i_logstart; +		dotdot_de->start = cpu_to_le16(start); +		dotdot_de->starthi = cpu_to_le16(start >> 16); +		mark_buffer_dirty(dotdot_bh); +		corrupt |= sync_dirty_buffer(dotdot_bh); +	} +error_inode: +	fat_detach(old_inode); +	fat_attach(old_inode, old_sinfo.i_pos); +	MSDOS_I(old_inode)->i_attrs = old_attrs; +	if (new_inode) { +		fat_attach(new_inode, new_i_pos); +		if (corrupt) +			corrupt |= fat_sync_inode(new_inode); +	} else { +		/* +		 * If new entry was not sharing the data cluster, it +		 * shouldn't be serious corruption. +		 */ +		int err2 = fat_remove_entries(new_dir, &sinfo); +		if (corrupt) +			corrupt |= err2; +		sinfo.bh = NULL; +	} +	if (corrupt < 0) { +		fat_fs_panic(new_dir->i_sb, +			     "%s: Filesystem corrupted (i_pos %lld)", +			     __func__, sinfo.i_pos); +	} +	goto out; +} + +/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */ +static int msdos_rename(struct inode *old_dir, struct dentry *old_dentry, +			struct inode *new_dir, struct dentry *new_dentry) +{ +	struct super_block *sb = old_dir->i_sb; +	unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME]; +	int err, is_hid; + +	lock_super(sb); + +	err = msdos_format_name(old_dentry->d_name.name, +				old_dentry->d_name.len, old_msdos_name, +				&MSDOS_SB(old_dir->i_sb)->options); +	if (err) +		goto out; +	err = msdos_format_name(new_dentry->d_name.name, +				new_dentry->d_name.len, new_msdos_name, +				&MSDOS_SB(new_dir->i_sb)->options); +	if (err) +		goto out; + +	is_hid = +	     (new_dentry->d_name.name[0] == '.') && (new_msdos_name[0] != '.'); + +	err = do_msdos_rename(old_dir, old_msdos_name, old_dentry, +			      new_dir, new_msdos_name, new_dentry, is_hid); +out: +	unlock_super(sb); +	if (!err) +		err = fat_flush_inodes(sb, old_dir, new_dir); +	return err; +} + +static const struct inode_operations msdos_dir_inode_operations = { +	.create		= msdos_create, +	.lookup		= msdos_lookup, +	.unlink		= msdos_unlink, +	.mkdir		= msdos_mkdir, +	.rmdir		= msdos_rmdir, +	.rename		= msdos_rename, +	.setattr	= fat_setattr, +	.getattr	= fat_getattr, +}; + +static int msdos_fill_super(struct super_block *sb, void *data, int silent) +{ +	int res; + +	res = fat_fill_super(sb, data, silent, &msdos_dir_inode_operations, 0); +	if (res) +		return res; + +	sb->s_flags |= MS_NOATIME; +	sb->s_root->d_op = &msdos_dentry_operations; +	return 0; +} + +static int msdos_get_sb(struct file_system_type *fs_type, +			int flags, const char *dev_name, +			void *data, struct vfsmount *mnt) +{ +	return get_sb_bdev(fs_type, flags, dev_name, data, msdos_fill_super, +			   mnt); +} + +static struct file_system_type msdos_fs_type = { +	.owner		= THIS_MODULE, +	.name		= "msdos", +	.get_sb		= msdos_get_sb, +	.kill_sb	= kill_block_super, +	.fs_flags	= FS_REQUIRES_DEV, +}; + +static int __init init_msdos_fs(void) +{ +	return register_filesystem(&msdos_fs_type); +} + +static void __exit exit_msdos_fs(void) +{ +	unregister_filesystem(&msdos_fs_type); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Werner Almesberger"); +MODULE_DESCRIPTION("MS-DOS filesystem support"); + +module_init(init_msdos_fs) +module_exit(exit_msdos_fs) diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c new file mode 100644 index 000000000000..bf326d4356a3 --- /dev/null +++ b/fs/fat/namei_vfat.c @@ -0,0 +1,1098 @@ +/* + *  linux/fs/vfat/namei.c + * + *  Written 1992,1993 by Werner Almesberger + * + *  Windows95/Windows NT compatible extended MSDOS filesystem + *    by Gordon Chaffee Copyright (C) 1995.  Send bug reports for the + *    VFAT filesystem to <chaffee@cs.berkeley.edu>.  Specify + *    what file operation caused you trouble and if you can duplicate + *    the problem, send a script that demonstrates it. + * + *  Short name translation 1999, 2001 by Wolfram Pienkoss <wp@bszh.de> + * + *  Support Multibyte characters and cleanup by + *				OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> + */ + +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/buffer_head.h> +#include <linux/namei.h> +#include "fat.h" + +/* + * If new entry was created in the parent, it could create the 8.3 + * alias (the shortname of logname).  So, the parent may have the + * negative-dentry which matches the created 8.3 alias. + * + * If it happened, the negative dentry isn't actually negative + * anymore.  So, drop it. + */ +static int vfat_revalidate_shortname(struct dentry *dentry) +{ +	int ret = 1; +	spin_lock(&dentry->d_lock); +	if (dentry->d_time != dentry->d_parent->d_inode->i_version) +		ret = 0; +	spin_unlock(&dentry->d_lock); +	return ret; +} + +static int vfat_revalidate(struct dentry *dentry, struct nameidata *nd) +{ +	/* This is not negative dentry. Always valid. */ +	if (dentry->d_inode) +		return 1; +	return vfat_revalidate_shortname(dentry); +} + +static int vfat_revalidate_ci(struct dentry *dentry, struct nameidata *nd) +{ +	/* +	 * This is not negative dentry. Always valid. +	 * +	 * Note, rename() to existing directory entry will have ->d_inode, +	 * and will use existing name which isn't specified name by user. +	 * +	 * We may be able to drop this positive dentry here. But dropping +	 * positive dentry isn't good idea. So it's unsupported like +	 * rename("filename", "FILENAME") for now. +	 */ +	if (dentry->d_inode) +		return 1; + +	/* +	 * This may be nfsd (or something), anyway, we can't see the +	 * intent of this. So, since this can be for creation, drop it. +	 */ +	if (!nd) +		return 0; + +	/* +	 * Drop the negative dentry, in order to make sure to use the +	 * case sensitive name which is specified by user if this is +	 * for creation. +	 */ +	if (!(nd->flags & (LOOKUP_CONTINUE | LOOKUP_PARENT))) { +		if (nd->flags & LOOKUP_CREATE) +			return 0; +	} + +	return vfat_revalidate_shortname(dentry); +} + +/* returns the length of a struct qstr, ignoring trailing dots */ +static unsigned int vfat_striptail_len(struct qstr *qstr) +{ +	unsigned int len = qstr->len; + +	while (len && qstr->name[len - 1] == '.') +		len--; +	return len; +} + +/* + * Compute the hash for the vfat name corresponding to the dentry. + * Note: if the name is invalid, we leave the hash code unchanged so + * that the existing dentry can be used. The vfat fs routines will + * return ENOENT or EINVAL as appropriate. + */ +static int vfat_hash(struct dentry *dentry, struct qstr *qstr) +{ +	qstr->hash = full_name_hash(qstr->name, vfat_striptail_len(qstr)); +	return 0; +} + +/* + * Compute the hash for the vfat name corresponding to the dentry. + * Note: if the name is invalid, we leave the hash code unchanged so + * that the existing dentry can be used. The vfat fs routines will + * return ENOENT or EINVAL as appropriate. + */ +static int vfat_hashi(struct dentry *dentry, struct qstr *qstr) +{ +	struct nls_table *t = MSDOS_SB(dentry->d_inode->i_sb)->nls_io; +	const unsigned char *name; +	unsigned int len; +	unsigned long hash; + +	name = qstr->name; +	len = vfat_striptail_len(qstr); + +	hash = init_name_hash(); +	while (len--) +		hash = partial_name_hash(nls_tolower(t, *name++), hash); +	qstr->hash = end_name_hash(hash); + +	return 0; +} + +/* + * Case insensitive compare of two vfat names. + */ +static int vfat_cmpi(struct dentry *dentry, struct qstr *a, struct qstr *b) +{ +	struct nls_table *t = MSDOS_SB(dentry->d_inode->i_sb)->nls_io; +	unsigned int alen, blen; + +	/* A filename cannot end in '.' or we treat it like it has none */ +	alen = vfat_striptail_len(a); +	blen = vfat_striptail_len(b); +	if (alen == blen) { +		if (nls_strnicmp(t, a->name, b->name, alen) == 0) +			return 0; +	} +	return 1; +} + +/* + * Case sensitive compare of two vfat names. + */ +static int vfat_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b) +{ +	unsigned int alen, blen; + +	/* A filename cannot end in '.' or we treat it like it has none */ +	alen = vfat_striptail_len(a); +	blen = vfat_striptail_len(b); +	if (alen == blen) { +		if (strncmp(a->name, b->name, alen) == 0) +			return 0; +	} +	return 1; +} + +static struct dentry_operations vfat_ci_dentry_ops = { +	.d_revalidate	= vfat_revalidate_ci, +	.d_hash		= vfat_hashi, +	.d_compare	= vfat_cmpi, +}; + +static struct dentry_operations vfat_dentry_ops = { +	.d_revalidate	= vfat_revalidate, +	.d_hash		= vfat_hash, +	.d_compare	= vfat_cmp, +}; + +/* Characters that are undesirable in an MS-DOS file name */ + +static inline wchar_t vfat_bad_char(wchar_t w) +{ +	return (w < 0x0020) +	    || (w == '*') || (w == '?') || (w == '<') || (w == '>') +	    || (w == '|') || (w == '"') || (w == ':') || (w == '/') +	    || (w == '\\'); +} + +static inline wchar_t vfat_replace_char(wchar_t w) +{ +	return (w == '[') || (w == ']') || (w == ';') || (w == ',') +	    || (w == '+') || (w == '='); +} + +static wchar_t vfat_skip_char(wchar_t w) +{ +	return (w == '.') || (w == ' '); +} + +static inline int vfat_is_used_badchars(const wchar_t *s, int len) +{ +	int i; + +	for (i = 0; i < len; i++) +		if (vfat_bad_char(s[i])) +			return -EINVAL; + +	if (s[i - 1] == ' ') /* last character cannot be space */ +		return -EINVAL; + +	return 0; +} + +static int vfat_find_form(struct inode *dir, unsigned char *name) +{ +	struct fat_slot_info sinfo; +	int err = fat_scan(dir, name, &sinfo); +	if (err) +		return -ENOENT; +	brelse(sinfo.bh); +	return 0; +} + +/* + * 1) Valid characters for the 8.3 format alias are any combination of + * letters, uppercase alphabets, digits, any of the + * following special characters: + *     $ % ' ` - @ { } ~ ! # ( ) & _ ^ + * In this case Longfilename is not stored in disk. + * + * WinNT's Extension: + * File name and extension name is contain uppercase/lowercase + * only. And it is expressed by CASE_LOWER_BASE and CASE_LOWER_EXT. + * + * 2) File name is 8.3 format, but it contain the uppercase and + * lowercase char, muliti bytes char, etc. In this case numtail is not + * added, but Longfilename is stored. + * + * 3) When the one except for the above, or the following special + * character are contained: + *        .   [ ] ; , + = + * numtail is added, and Longfilename must be stored in disk . + */ +struct shortname_info { +	unsigned char lower:1, +		      upper:1, +		      valid:1; +}; +#define INIT_SHORTNAME_INFO(x)	do {		\ +	(x)->lower = 1;				\ +	(x)->upper = 1;				\ +	(x)->valid = 1;				\ +} while (0) + +static inline int to_shortname_char(struct nls_table *nls, +				    unsigned char *buf, int buf_size, +				    wchar_t *src, struct shortname_info *info) +{ +	int len; + +	if (vfat_skip_char(*src)) { +		info->valid = 0; +		return 0; +	} +	if (vfat_replace_char(*src)) { +		info->valid = 0; +		buf[0] = '_'; +		return 1; +	} + +	len = nls->uni2char(*src, buf, buf_size); +	if (len <= 0) { +		info->valid = 0; +		buf[0] = '_'; +		len = 1; +	} else if (len == 1) { +		unsigned char prev = buf[0]; + +		if (buf[0] >= 0x7F) { +			info->lower = 0; +			info->upper = 0; +		} + +		buf[0] = nls_toupper(nls, buf[0]); +		if (isalpha(buf[0])) { +			if (buf[0] == prev) +				info->lower = 0; +			else +				info->upper = 0; +		} +	} else { +		info->lower = 0; +		info->upper = 0; +	} + +	return len; +} + +/* + * Given a valid longname, create a unique shortname.  Make sure the + * shortname does not exist + * Returns negative number on error, 0 for a normal + * return, and 1 for valid shortname + */ +static int vfat_create_shortname(struct inode *dir, struct nls_table *nls, +				 wchar_t *uname, int ulen, +				 unsigned char *name_res, unsigned char *lcase) +{ +	struct fat_mount_options *opts = &MSDOS_SB(dir->i_sb)->options; +	wchar_t *ip, *ext_start, *end, *name_start; +	unsigned char base[9], ext[4], buf[8], *p; +	unsigned char charbuf[NLS_MAX_CHARSET_SIZE]; +	int chl, chi; +	int sz = 0, extlen, baselen, i, numtail_baselen, numtail2_baselen; +	int is_shortname; +	struct shortname_info base_info, ext_info; + +	is_shortname = 1; +	INIT_SHORTNAME_INFO(&base_info); +	INIT_SHORTNAME_INFO(&ext_info); + +	/* Now, we need to create a shortname from the long name */ +	ext_start = end = &uname[ulen]; +	while (--ext_start >= uname) { +		if (*ext_start == 0x002E) {	/* is `.' */ +			if (ext_start == end - 1) { +				sz = ulen; +				ext_start = NULL; +			} +			break; +		} +	} + +	if (ext_start == uname - 1) { +		sz = ulen; +		ext_start = NULL; +	} else if (ext_start) { +		/* +		 * Names which start with a dot could be just +		 * an extension eg. "...test".  In this case Win95 +		 * uses the extension as the name and sets no extension. +		 */ +		name_start = &uname[0]; +		while (name_start < ext_start) { +			if (!vfat_skip_char(*name_start)) +				break; +			name_start++; +		} +		if (name_start != ext_start) { +			sz = ext_start - uname; +			ext_start++; +		} else { +			sz = ulen; +			ext_start = NULL; +		} +	} + +	numtail_baselen = 6; +	numtail2_baselen = 2; +	for (baselen = i = 0, p = base, ip = uname; i < sz; i++, ip++) { +		chl = to_shortname_char(nls, charbuf, sizeof(charbuf), +					ip, &base_info); +		if (chl == 0) +			continue; + +		if (baselen < 2 && (baselen + chl) > 2) +			numtail2_baselen = baselen; +		if (baselen < 6 && (baselen + chl) > 6) +			numtail_baselen = baselen; +		for (chi = 0; chi < chl; chi++) { +			*p++ = charbuf[chi]; +			baselen++; +			if (baselen >= 8) +				break; +		} +		if (baselen >= 8) { +			if ((chi < chl - 1) || (ip + 1) - uname < sz) +				is_shortname = 0; +			break; +		} +	} +	if (baselen == 0) { +		return -EINVAL; +	} + +	extlen = 0; +	if (ext_start) { +		for (p = ext, ip = ext_start; extlen < 3 && ip < end; ip++) { +			chl = to_shortname_char(nls, charbuf, sizeof(charbuf), +						ip, &ext_info); +			if (chl == 0) +				continue; + +			if ((extlen + chl) > 3) { +				is_shortname = 0; +				break; +			} +			for (chi = 0; chi < chl; chi++) { +				*p++ = charbuf[chi]; +				extlen++; +			} +			if (extlen >= 3) { +				if (ip + 1 != end) +					is_shortname = 0; +				break; +			} +		} +	} +	ext[extlen] = '\0'; +	base[baselen] = '\0'; + +	/* Yes, it can happen. ".\xe5" would do it. */ +	if (base[0] == DELETED_FLAG) +		base[0] = 0x05; + +	/* OK, at this point we know that base is not longer than 8 symbols, +	 * ext is not longer than 3, base is nonempty, both don't contain +	 * any bad symbols (lowercase transformed to uppercase). +	 */ + +	memset(name_res, ' ', MSDOS_NAME); +	memcpy(name_res, base, baselen); +	memcpy(name_res + 8, ext, extlen); +	*lcase = 0; +	if (is_shortname && base_info.valid && ext_info.valid) { +		if (vfat_find_form(dir, name_res) == 0) +			return -EEXIST; + +		if (opts->shortname & VFAT_SFN_CREATE_WIN95) { +			return (base_info.upper && ext_info.upper); +		} else if (opts->shortname & VFAT_SFN_CREATE_WINNT) { +			if ((base_info.upper || base_info.lower) && +			    (ext_info.upper || ext_info.lower)) { +				if (!base_info.upper && base_info.lower) +					*lcase |= CASE_LOWER_BASE; +				if (!ext_info.upper && ext_info.lower) +					*lcase |= CASE_LOWER_EXT; +				return 1; +			} +			return 0; +		} else { +			BUG(); +		} +	} + +	if (opts->numtail == 0) +		if (vfat_find_form(dir, name_res) < 0) +			return 0; + +	/* +	 * Try to find a unique extension.  This used to +	 * iterate through all possibilities sequentially, +	 * but that gave extremely bad performance.  Windows +	 * only tries a few cases before using random +	 * values for part of the base. +	 */ + +	if (baselen > 6) { +		baselen = numtail_baselen; +		name_res[7] = ' '; +	} +	name_res[baselen] = '~'; +	for (i = 1; i < 10; i++) { +		name_res[baselen + 1] = i + '0'; +		if (vfat_find_form(dir, name_res) < 0) +			return 0; +	} + +	i = jiffies & 0xffff; +	sz = (jiffies >> 16) & 0x7; +	if (baselen > 2) { +		baselen = numtail2_baselen; +		name_res[7] = ' '; +	} +	name_res[baselen + 4] = '~'; +	name_res[baselen + 5] = '1' + sz; +	while (1) { +		sprintf(buf, "%04X", i); +		memcpy(&name_res[baselen], buf, 4); +		if (vfat_find_form(dir, name_res) < 0) +			break; +		i -= 11; +	} +	return 0; +} + +/* Translate a string, including coded sequences into Unicode */ +static int +xlate_to_uni(const unsigned char *name, int len, unsigned char *outname, +	     int *longlen, int *outlen, int escape, int utf8, +	     struct nls_table *nls) +{ +	const unsigned char *ip; +	unsigned char nc; +	unsigned char *op; +	unsigned int ec; +	int i, k, fill; +	int charlen; + +	if (utf8) { +		int name_len = strlen(name); + +		*outlen = utf8_mbstowcs((wchar_t *)outname, name, PATH_MAX); + +		/* +		 * We stripped '.'s before and set len appropriately, +		 * but utf8_mbstowcs doesn't care about len +		 */ +		*outlen -= (name_len - len); + +		if (*outlen > 255) +			return -ENAMETOOLONG; + +		op = &outname[*outlen * sizeof(wchar_t)]; +	} else { +		if (nls) { +			for (i = 0, ip = name, op = outname, *outlen = 0; +			     i < len && *outlen <= 255; +			     *outlen += 1) +			{ +				if (escape && (*ip == ':')) { +					if (i > len - 5) +						return -EINVAL; +					ec = 0; +					for (k = 1; k < 5; k++) { +						nc = ip[k]; +						ec <<= 4; +						if (nc >= '0' && nc <= '9') { +							ec |= nc - '0'; +							continue; +						} +						if (nc >= 'a' && nc <= 'f') { +							ec |= nc - ('a' - 10); +							continue; +						} +						if (nc >= 'A' && nc <= 'F') { +							ec |= nc - ('A' - 10); +							continue; +						} +						return -EINVAL; +					} +					*op++ = ec & 0xFF; +					*op++ = ec >> 8; +					ip += 5; +					i += 5; +				} else { +					if ((charlen = nls->char2uni(ip, len - i, (wchar_t *)op)) < 0) +						return -EINVAL; +					ip += charlen; +					i += charlen; +					op += 2; +				} +			} +			if (i < len) +				return -ENAMETOOLONG; +		} else { +			for (i = 0, ip = name, op = outname, *outlen = 0; +			     i < len && *outlen <= 255; +			     i++, *outlen += 1) +			{ +				*op++ = *ip++; +				*op++ = 0; +			} +			if (i < len) +				return -ENAMETOOLONG; +		} +	} + +	*longlen = *outlen; +	if (*outlen % 13) { +		*op++ = 0; +		*op++ = 0; +		*outlen += 1; +		if (*outlen % 13) { +			fill = 13 - (*outlen % 13); +			for (i = 0; i < fill; i++) { +				*op++ = 0xff; +				*op++ = 0xff; +			} +			*outlen += fill; +		} +	} + +	return 0; +} + +static int vfat_build_slots(struct inode *dir, const unsigned char *name, +			    int len, int is_dir, int cluster, +			    struct timespec *ts, +			    struct msdos_dir_slot *slots, int *nr_slots) +{ +	struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb); +	struct fat_mount_options *opts = &sbi->options; +	struct msdos_dir_slot *ps; +	struct msdos_dir_entry *de; +	unsigned char cksum, lcase; +	unsigned char msdos_name[MSDOS_NAME]; +	wchar_t *uname; +	__le16 time, date; +	u8 time_cs; +	int err, ulen, usize, i; +	loff_t offset; + +	*nr_slots = 0; + +	uname = __getname(); +	if (!uname) +		return -ENOMEM; + +	err = xlate_to_uni(name, len, (unsigned char *)uname, &ulen, &usize, +			   opts->unicode_xlate, opts->utf8, sbi->nls_io); +	if (err) +		goto out_free; + +	err = vfat_is_used_badchars(uname, ulen); +	if (err) +		goto out_free; + +	err = vfat_create_shortname(dir, sbi->nls_disk, uname, ulen, +				    msdos_name, &lcase); +	if (err < 0) +		goto out_free; +	else if (err == 1) { +		de = (struct msdos_dir_entry *)slots; +		err = 0; +		goto shortname; +	} + +	/* build the entry of long file name */ +	cksum = fat_checksum(msdos_name); + +	*nr_slots = usize / 13; +	for (ps = slots, i = *nr_slots; i > 0; i--, ps++) { +		ps->id = i; +		ps->attr = ATTR_EXT; +		ps->reserved = 0; +		ps->alias_checksum = cksum; +		ps->start = 0; +		offset = (i - 1) * 13; +		fatwchar_to16(ps->name0_4, uname + offset, 5); +		fatwchar_to16(ps->name5_10, uname + offset + 5, 6); +		fatwchar_to16(ps->name11_12, uname + offset + 11, 2); +	} +	slots[0].id |= 0x40; +	de = (struct msdos_dir_entry *)ps; + +shortname: +	/* build the entry of 8.3 alias name */ +	(*nr_slots)++; +	memcpy(de->name, msdos_name, MSDOS_NAME); +	de->attr = is_dir ? ATTR_DIR : ATTR_ARCH; +	de->lcase = lcase; +	fat_time_unix2fat(sbi, ts, &time, &date, &time_cs); +	de->time = de->ctime = time; +	de->date = de->cdate = de->adate = date; +	de->ctime_cs = time_cs; +	de->start = cpu_to_le16(cluster); +	de->starthi = cpu_to_le16(cluster >> 16); +	de->size = 0; +out_free: +	__putname(uname); +	return err; +} + +static int vfat_add_entry(struct inode *dir, struct qstr *qname, int is_dir, +			  int cluster, struct timespec *ts, +			  struct fat_slot_info *sinfo) +{ +	struct msdos_dir_slot *slots; +	unsigned int len; +	int err, nr_slots; + +	len = vfat_striptail_len(qname); +	if (len == 0) +		return -ENOENT; + +	slots = kmalloc(sizeof(*slots) * MSDOS_SLOTS, GFP_NOFS); +	if (slots == NULL) +		return -ENOMEM; + +	err = vfat_build_slots(dir, qname->name, len, is_dir, cluster, ts, +			       slots, &nr_slots); +	if (err) +		goto cleanup; + +	err = fat_add_entries(dir, slots, nr_slots, sinfo); +	if (err) +		goto cleanup; + +	/* update timestamp */ +	dir->i_ctime = dir->i_mtime = dir->i_atime = *ts; +	if (IS_DIRSYNC(dir)) +		(void)fat_sync_inode(dir); +	else +		mark_inode_dirty(dir); +cleanup: +	kfree(slots); +	return err; +} + +static int vfat_find(struct inode *dir, struct qstr *qname, +		     struct fat_slot_info *sinfo) +{ +	unsigned int len = vfat_striptail_len(qname); +	if (len == 0) +		return -ENOENT; +	return fat_search_long(dir, qname->name, len, sinfo); +} + +static struct dentry *vfat_lookup(struct inode *dir, struct dentry *dentry, +				  struct nameidata *nd) +{ +	struct super_block *sb = dir->i_sb; +	struct fat_slot_info sinfo; +	struct inode *inode; +	struct dentry *alias; +	int err; + +	lock_super(sb); + +	err = vfat_find(dir, &dentry->d_name, &sinfo); +	if (err) { +		if (err == -ENOENT) { +			inode = NULL; +			goto out; +		} +		goto error; +	} + +	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); +	brelse(sinfo.bh); +	if (IS_ERR(inode)) { +		err = PTR_ERR(inode); +		goto error; +	} + +	alias = d_find_alias(inode); +	if (alias && !(alias->d_flags & DCACHE_DISCONNECTED)) { +		/* +		 * This inode has non DCACHE_DISCONNECTED dentry. This +		 * means, the user did ->lookup() by an another name +		 * (longname vs 8.3 alias of it) in past. +		 * +		 * Switch to new one for reason of locality if possible. +		 */ +		BUG_ON(d_unhashed(alias)); +		if (!S_ISDIR(inode->i_mode)) +			d_move(alias, dentry); +		iput(inode); +		unlock_super(sb); +		return alias; +	} +out: +	unlock_super(sb); +	dentry->d_op = sb->s_root->d_op; +	dentry->d_time = dentry->d_parent->d_inode->i_version; +	dentry = d_splice_alias(inode, dentry); +	if (dentry) { +		dentry->d_op = sb->s_root->d_op; +		dentry->d_time = dentry->d_parent->d_inode->i_version; +	} +	return dentry; + +error: +	unlock_super(sb); +	return ERR_PTR(err); +} + +static int vfat_create(struct inode *dir, struct dentry *dentry, int mode, +		       struct nameidata *nd) +{ +	struct super_block *sb = dir->i_sb; +	struct inode *inode; +	struct fat_slot_info sinfo; +	struct timespec ts; +	int err; + +	lock_super(sb); + +	ts = CURRENT_TIME_SEC; +	err = vfat_add_entry(dir, &dentry->d_name, 0, 0, &ts, &sinfo); +	if (err) +		goto out; +	dir->i_version++; + +	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); +	brelse(sinfo.bh); +	if (IS_ERR(inode)) { +		err = PTR_ERR(inode); +		goto out; +	} +	inode->i_version++; +	inode->i_mtime = inode->i_atime = inode->i_ctime = ts; +	/* timestamp is already written, so mark_inode_dirty() is unneeded. */ + +	dentry->d_time = dentry->d_parent->d_inode->i_version; +	d_instantiate(dentry, inode); +out: +	unlock_super(sb); +	return err; +} + +static int vfat_rmdir(struct inode *dir, struct dentry *dentry) +{ +	struct inode *inode = dentry->d_inode; +	struct super_block *sb = dir->i_sb; +	struct fat_slot_info sinfo; +	int err; + +	lock_super(sb); + +	err = fat_dir_empty(inode); +	if (err) +		goto out; +	err = vfat_find(dir, &dentry->d_name, &sinfo); +	if (err) +		goto out; + +	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */ +	if (err) +		goto out; +	drop_nlink(dir); + +	clear_nlink(inode); +	inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; +	fat_detach(inode); +out: +	unlock_super(sb); + +	return err; +} + +static int vfat_unlink(struct inode *dir, struct dentry *dentry) +{ +	struct inode *inode = dentry->d_inode; +	struct super_block *sb = dir->i_sb; +	struct fat_slot_info sinfo; +	int err; + +	lock_super(sb); + +	err = vfat_find(dir, &dentry->d_name, &sinfo); +	if (err) +		goto out; + +	err = fat_remove_entries(dir, &sinfo);	/* and releases bh */ +	if (err) +		goto out; +	clear_nlink(inode); +	inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; +	fat_detach(inode); +out: +	unlock_super(sb); + +	return err; +} + +static int vfat_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ +	struct super_block *sb = dir->i_sb; +	struct inode *inode; +	struct fat_slot_info sinfo; +	struct timespec ts; +	int err, cluster; + +	lock_super(sb); + +	ts = CURRENT_TIME_SEC; +	cluster = fat_alloc_new_dir(dir, &ts); +	if (cluster < 0) { +		err = cluster; +		goto out; +	} +	err = vfat_add_entry(dir, &dentry->d_name, 1, cluster, &ts, &sinfo); +	if (err) +		goto out_free; +	dir->i_version++; +	inc_nlink(dir); + +	inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); +	brelse(sinfo.bh); +	if (IS_ERR(inode)) { +		err = PTR_ERR(inode); +		/* the directory was completed, just return a error */ +		goto out; +	} +	inode->i_version++; +	inode->i_nlink = 2; +	inode->i_mtime = inode->i_atime = inode->i_ctime = ts; +	/* timestamp is already written, so mark_inode_dirty() is unneeded. */ + +	dentry->d_time = dentry->d_parent->d_inode->i_version; +	d_instantiate(dentry, inode); + +	unlock_super(sb); +	return 0; + +out_free: +	fat_free_clusters(dir, cluster); +out: +	unlock_super(sb); +	return err; +} + +static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry, +		       struct inode *new_dir, struct dentry *new_dentry) +{ +	struct buffer_head *dotdot_bh; +	struct msdos_dir_entry *dotdot_de; +	struct inode *old_inode, *new_inode; +	struct fat_slot_info old_sinfo, sinfo; +	struct timespec ts; +	loff_t dotdot_i_pos, new_i_pos; +	int err, is_dir, update_dotdot, corrupt = 0; +	struct super_block *sb = old_dir->i_sb; + +	old_sinfo.bh = sinfo.bh = dotdot_bh = NULL; +	old_inode = old_dentry->d_inode; +	new_inode = new_dentry->d_inode; +	lock_super(sb); +	err = vfat_find(old_dir, &old_dentry->d_name, &old_sinfo); +	if (err) +		goto out; + +	is_dir = S_ISDIR(old_inode->i_mode); +	update_dotdot = (is_dir && old_dir != new_dir); +	if (update_dotdot) { +		if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de, +					 &dotdot_i_pos) < 0) { +			err = -EIO; +			goto out; +		} +	} + +	ts = CURRENT_TIME_SEC; +	if (new_inode) { +		if (is_dir) { +			err = fat_dir_empty(new_inode); +			if (err) +				goto out; +		} +		new_i_pos = MSDOS_I(new_inode)->i_pos; +		fat_detach(new_inode); +	} else { +		err = vfat_add_entry(new_dir, &new_dentry->d_name, is_dir, 0, +				     &ts, &sinfo); +		if (err) +			goto out; +		new_i_pos = sinfo.i_pos; +	} +	new_dir->i_version++; + +	fat_detach(old_inode); +	fat_attach(old_inode, new_i_pos); +	if (IS_DIRSYNC(new_dir)) { +		err = fat_sync_inode(old_inode); +		if (err) +			goto error_inode; +	} else +		mark_inode_dirty(old_inode); + +	if (update_dotdot) { +		int start = MSDOS_I(new_dir)->i_logstart; +		dotdot_de->start = cpu_to_le16(start); +		dotdot_de->starthi = cpu_to_le16(start >> 16); +		mark_buffer_dirty(dotdot_bh); +		if (IS_DIRSYNC(new_dir)) { +			err = sync_dirty_buffer(dotdot_bh); +			if (err) +				goto error_dotdot; +		} +		drop_nlink(old_dir); +		if (!new_inode) + 			inc_nlink(new_dir); +	} + +	err = fat_remove_entries(old_dir, &old_sinfo);	/* and releases bh */ +	old_sinfo.bh = NULL; +	if (err) +		goto error_dotdot; +	old_dir->i_version++; +	old_dir->i_ctime = old_dir->i_mtime = ts; +	if (IS_DIRSYNC(old_dir)) +		(void)fat_sync_inode(old_dir); +	else +		mark_inode_dirty(old_dir); + +	if (new_inode) { +		drop_nlink(new_inode); +		if (is_dir) +			drop_nlink(new_inode); +		new_inode->i_ctime = ts; +	} +out: +	brelse(sinfo.bh); +	brelse(dotdot_bh); +	brelse(old_sinfo.bh); +	unlock_super(sb); + +	return err; + +error_dotdot: +	/* data cluster is shared, serious corruption */ +	corrupt = 1; + +	if (update_dotdot) { +		int start = MSDOS_I(old_dir)->i_logstart; +		dotdot_de->start = cpu_to_le16(start); +		dotdot_de->starthi = cpu_to_le16(start >> 16); +		mark_buffer_dirty(dotdot_bh); +		corrupt |= sync_dirty_buffer(dotdot_bh); +	} +error_inode: +	fat_detach(old_inode); +	fat_attach(old_inode, old_sinfo.i_pos); +	if (new_inode) { +		fat_attach(new_inode, new_i_pos); +		if (corrupt) +			corrupt |= fat_sync_inode(new_inode); +	} else { +		/* +		 * If new entry was not sharing the data cluster, it +		 * shouldn't be serious corruption. +		 */ +		int err2 = fat_remove_entries(new_dir, &sinfo); +		if (corrupt) +			corrupt |= err2; +		sinfo.bh = NULL; +	} +	if (corrupt < 0) { +		fat_fs_panic(new_dir->i_sb, +			     "%s: Filesystem corrupted (i_pos %lld)", +			     __func__, sinfo.i_pos); +	} +	goto out; +} + +static const struct inode_operations vfat_dir_inode_operations = { +	.create		= vfat_create, +	.lookup		= vfat_lookup, +	.unlink		= vfat_unlink, +	.mkdir		= vfat_mkdir, +	.rmdir		= vfat_rmdir, +	.rename		= vfat_rename, +	.setattr	= fat_setattr, +	.getattr	= fat_getattr, +}; + +static int vfat_fill_super(struct super_block *sb, void *data, int silent) +{ +	int res; + +	res = fat_fill_super(sb, data, silent, &vfat_dir_inode_operations, 1); +	if (res) +		return res; + +	if (MSDOS_SB(sb)->options.name_check != 's') +		sb->s_root->d_op = &vfat_ci_dentry_ops; +	else +		sb->s_root->d_op = &vfat_dentry_ops; + +	return 0; +} + +static int vfat_get_sb(struct file_system_type *fs_type, +		       int flags, const char *dev_name, +		       void *data, struct vfsmount *mnt) +{ +	return get_sb_bdev(fs_type, flags, dev_name, data, vfat_fill_super, +			   mnt); +} + +static struct file_system_type vfat_fs_type = { +	.owner		= THIS_MODULE, +	.name		= "vfat", +	.get_sb		= vfat_get_sb, +	.kill_sb	= kill_block_super, +	.fs_flags	= FS_REQUIRES_DEV, +}; + +static int __init init_vfat_fs(void) +{ +	return register_filesystem(&vfat_fs_type); +} + +static void __exit exit_vfat_fs(void) +{ +	unregister_filesystem(&vfat_fs_type); +} + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VFAT filesystem support"); +MODULE_AUTHOR("Gordon Chaffee"); + +module_init(init_vfat_fs) +module_exit(exit_vfat_fs) |