2005-04-17 02:20:36 +04:00
/*
* linux / fs / fat / file . c
*
* Written 1992 , 1993 by Werner Almesberger
*
* regular file handling primitives for fat - based filesystems
*/
2006-01-11 23:17:46 +03:00
# include <linux/capability.h>
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/time.h>
# include <linux/msdos_fs.h>
# include <linux/smp_lock.h>
# include <linux/buffer_head.h>
2006-01-08 12:02:13 +03:00
# include <linux/writeback.h>
2006-10-20 10:28:16 +04:00
# include <linux/backing-dev.h>
2006-09-29 13:00:03 +04:00
# include <linux/blkdev.h>
2005-04-17 02:20:36 +04:00
int fat_generic_ioctl ( struct inode * inode , struct file * filp ,
unsigned int cmd , unsigned long arg )
{
struct msdos_sb_info * sbi = MSDOS_SB ( inode - > i_sb ) ;
u32 __user * user_attr = ( u32 __user * ) arg ;
switch ( cmd ) {
case FAT_IOCTL_GET_ATTRIBUTES :
{
u32 attr ;
if ( inode - > i_ino = = MSDOS_ROOT_INO )
attr = ATTR_DIR ;
else
attr = fat_attr ( inode ) ;
return put_user ( attr , user_attr ) ;
}
case FAT_IOCTL_SET_ATTRIBUTES :
{
u32 attr , oldattr ;
int err , is_dir = S_ISDIR ( inode - > i_mode ) ;
struct iattr ia ;
err = get_user ( attr , user_attr ) ;
if ( err )
return err ;
2006-01-10 02:59:24 +03:00
mutex_lock ( & inode - > i_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( IS_RDONLY ( inode ) ) {
err = - EROFS ;
goto up ;
}
/*
* ATTR_VOLUME and ATTR_DIR cannot be changed ; this also
* prevents the user from turning us into a VFAT
* longname entry . Also , we obviously can ' t set
* any of the NTFS attributes in the high 24 bits .
*/
attr & = 0xff & ~ ( ATTR_VOLUME | ATTR_DIR ) ;
/* Merge in ATTR_VOLUME and ATTR_DIR */
attr | = ( MSDOS_I ( inode ) - > i_attrs & ATTR_VOLUME ) |
( is_dir ? ATTR_DIR : 0 ) ;
oldattr = fat_attr ( inode ) ;
/* Equivalent to a chmod() */
ia . ia_valid = ATTR_MODE | ATTR_CTIME ;
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 ;
}
/* The root directory has no attributes */
if ( inode - > i_ino = = MSDOS_ROOT_INO & & attr ! = ATTR_DIR ) {
err = - EINVAL ;
goto up ;
}
if ( sbi - > options . sys_immutable ) {
if ( ( attr | oldattr ) & ATTR_SYS ) {
if ( ! capable ( CAP_LINUX_IMMUTABLE ) ) {
err = - EPERM ;
goto up ;
}
}
}
/* This MUST be done before doing anything irreversible... */
2006-12-08 13:36:39 +03:00
err = notify_change ( filp - > f_path . dentry , & ia ) ;
2005-04-17 02:20:36 +04:00
if ( err )
goto up ;
if ( sbi - > options . sys_immutable ) {
if ( attr & ATTR_SYS )
inode - > i_flags | = S_IMMUTABLE ;
else
inode - > i_flags & = S_IMMUTABLE ;
}
MSDOS_I ( inode ) - > i_attrs = attr & ATTR_UNUSED ;
mark_inode_dirty ( inode ) ;
up :
2006-01-10 02:59:24 +03:00
mutex_unlock ( & inode - > i_mutex ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
default :
return - ENOTTY ; /* Inappropriate ioctl for device */
}
}
2006-09-29 13:00:03 +04:00
static int fat_file_release ( struct inode * inode , struct file * filp )
{
if ( ( filp - > f_mode & FMODE_WRITE ) & &
MSDOS_SB ( inode - > i_sb ) - > options . flush ) {
fat_flush_inodes ( inode - > i_sb , inode , NULL ) ;
2006-10-20 10:28:16 +04:00
congestion_wait ( WRITE , HZ / 10 ) ;
2006-09-29 13:00:03 +04:00
}
return 0 ;
}
2006-03-28 13:56:42 +04:00
const struct file_operations fat_file_operations = {
2005-04-17 02:20:36 +04:00
. llseek = generic_file_llseek ,
. read = do_sync_read ,
. write = do_sync_write ,
. aio_read = generic_file_aio_read ,
2005-09-17 06:28:13 +04:00
. aio_write = generic_file_aio_write ,
2005-04-17 02:20:36 +04:00
. mmap = generic_file_mmap ,
2006-09-29 13:00:03 +04:00
. release = fat_file_release ,
2005-04-17 02:20:36 +04:00
. ioctl = fat_generic_ioctl ,
. fsync = file_fsync ,
2007-06-01 13:49:19 +04:00
. splice_read = generic_file_splice_read ,
2005-04-17 02:20:36 +04:00
} ;
2006-01-08 12:02:13 +03:00
static int fat_cont_expand ( struct inode * inode , loff_t size )
{
struct address_space * mapping = inode - > i_mapping ;
loff_t start = inode - > i_size , count = size - inode - > i_size ;
int err ;
err = generic_cont_expand_simple ( inode , size ) ;
if ( err )
goto out ;
inode - > i_ctime = inode - > i_mtime = CURRENT_TIME_SEC ;
mark_inode_dirty ( inode ) ;
if ( IS_SYNC ( inode ) )
err = sync_page_range_nolock ( inode , mapping , start , count ) ;
out :
return err ;
}
2005-04-17 02:20:36 +04:00
int fat_notify_change ( struct dentry * dentry , struct iattr * attr )
{
struct msdos_sb_info * sbi = MSDOS_SB ( dentry - > d_sb ) ;
struct inode * inode = dentry - > d_inode ;
int mask , error = 0 ;
lock_kernel ( ) ;
2006-01-08 12:02:13 +03:00
/*
* Expand the file . Since inode_setattr ( ) updates - > i_size
* before calling the - > truncate ( ) , but FAT needs to fill the
* hole before it .
*/
2005-04-17 02:20:36 +04:00
if ( attr - > ia_valid & ATTR_SIZE ) {
if ( attr - > ia_size > inode - > i_size ) {
2006-01-08 12:02:13 +03:00
error = fat_cont_expand ( inode , attr - > ia_size ) ;
if ( error | | attr - > ia_valid = = ATTR_SIZE )
goto out ;
attr - > ia_valid & = ~ ATTR_SIZE ;
2005-04-17 02:20:36 +04:00
}
}
error = inode_change_ok ( inode , attr ) ;
if ( error ) {
if ( sbi - > options . quiet )
error = 0 ;
goto out ;
}
if ( ( ( attr - > ia_valid & ATTR_UID ) & &
( attr - > ia_uid ! = sbi - > options . fs_uid ) ) | |
( ( attr - > ia_valid & ATTR_GID ) & &
( attr - > ia_gid ! = sbi - > options . fs_gid ) ) | |
( ( attr - > ia_valid & ATTR_MODE ) & &
( attr - > ia_mode & ~ MSDOS_VALID_MODE ) ) )
error = - EPERM ;
if ( error ) {
if ( sbi - > options . quiet )
error = 0 ;
goto out ;
}
error = inode_setattr ( inode , attr ) ;
if ( error )
goto out ;
if ( S_ISDIR ( inode - > i_mode ) )
mask = sbi - > options . fs_dmask ;
else
mask = sbi - > options . fs_fmask ;
inode - > i_mode & = S_IFMT | ( S_IRWXUGO & ~ mask ) ;
out :
unlock_kernel ( ) ;
return error ;
}
2006-01-08 12:02:10 +03:00
EXPORT_SYMBOL_GPL ( fat_notify_change ) ;
2005-04-17 02:20:36 +04:00
/* Free all clusters after the skip'th cluster. */
static int fat_free ( struct inode * inode , int skip )
{
struct super_block * sb = inode - > i_sb ;
int err , wait , free_start , i_start , i_logstart ;
if ( MSDOS_I ( inode ) - > i_start = = 0 )
return 0 ;
2006-02-03 14:04:44 +03:00
fat_cache_inval_inode ( inode ) ;
2005-04-17 02:20:36 +04:00
wait = IS_DIRSYNC ( inode ) ;
2006-02-03 14:04:44 +03:00
i_start = free_start = MSDOS_I ( inode ) - > i_start ;
i_logstart = MSDOS_I ( inode ) - > i_logstart ;
/* First, we write the new file size. */
if ( ! skip ) {
MSDOS_I ( inode ) - > i_start = 0 ;
MSDOS_I ( inode ) - > i_logstart = 0 ;
}
MSDOS_I ( inode ) - > i_attrs | = ATTR_ARCH ;
inode - > i_ctime = inode - > i_mtime = CURRENT_TIME_SEC ;
if ( wait ) {
err = fat_sync_inode ( inode ) ;
if ( err ) {
MSDOS_I ( inode ) - > i_start = i_start ;
MSDOS_I ( inode ) - > i_logstart = i_logstart ;
return err ;
}
} else
mark_inode_dirty ( inode ) ;
/* Write a new EOF, and get the remaining cluster chain for freeing. */
2005-04-17 02:20:36 +04:00
if ( skip ) {
struct fat_entry fatent ;
int ret , fclus , dclus ;
ret = fat_get_cluster ( inode , skip - 1 , & fclus , & dclus ) ;
if ( ret < 0 )
return ret ;
else if ( ret = = FAT_ENT_EOF )
return 0 ;
fatent_init ( & fatent ) ;
ret = fat_ent_read ( inode , & fatent , dclus ) ;
if ( ret = = FAT_ENT_EOF ) {
fatent_brelse ( & fatent ) ;
return 0 ;
} else if ( ret = = FAT_ENT_FREE ) {
fat_fs_panic ( sb ,
" %s: invalid cluster chain (i_pos %lld) " ,
__FUNCTION__ , MSDOS_I ( inode ) - > i_pos ) ;
ret = - EIO ;
} else if ( ret > 0 ) {
err = fat_ent_write ( inode , & fatent , FAT_ENT_EOF , wait ) ;
if ( err )
ret = err ;
}
fatent_brelse ( & fatent ) ;
if ( ret < 0 )
return ret ;
free_start = ret ;
}
inode - > i_blocks = skip < < ( MSDOS_SB ( sb ) - > cluster_bits - 9 ) ;
/* Freeing the remained cluster chain */
return fat_free_clusters ( inode , free_start ) ;
}
void fat_truncate ( struct inode * inode )
{
struct msdos_sb_info * sbi = MSDOS_SB ( inode - > i_sb ) ;
const unsigned int cluster_size = sbi - > cluster_size ;
int nr_clusters ;
/*
* This protects against truncating a file bigger than it was then
* trying to write into the hole .
*/
if ( MSDOS_I ( inode ) - > mmu_private > inode - > i_size )
MSDOS_I ( inode ) - > mmu_private = inode - > i_size ;
nr_clusters = ( inode - > i_size + ( cluster_size - 1 ) ) > > sbi - > cluster_bits ;
lock_kernel ( ) ;
fat_free ( inode , nr_clusters ) ;
unlock_kernel ( ) ;
2006-09-29 13:00:03 +04:00
fat_flush_inodes ( inode - > i_sb , inode , NULL ) ;
2005-04-17 02:20:36 +04:00
}
2006-11-16 12:19:28 +03:00
int fat_getattr ( struct vfsmount * mnt , struct dentry * dentry , struct kstat * stat )
{
struct inode * inode = dentry - > d_inode ;
generic_fillattr ( inode , stat ) ;
stat - > blksize = MSDOS_SB ( inode - > i_sb ) - > cluster_size ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( fat_getattr ) ;
2007-02-12 11:55:38 +03:00
const struct inode_operations fat_file_inode_operations = {
2005-04-17 02:20:36 +04:00
. truncate = fat_truncate ,
. setattr = fat_notify_change ,
2006-11-16 12:19:28 +03:00
. getattr = fat_getattr ,
2005-04-17 02:20:36 +04:00
} ;