2020-03-02 09:21:36 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright ( C ) 2012 - 2013 Samsung Electronics Co . , Ltd .
*/
# include <linux/slab.h>
2021-03-04 03:20:35 +03:00
# include <linux/compat.h>
2020-03-02 09:21:36 +03:00
# include <linux/cred.h>
# include <linux/buffer_head.h>
2020-06-18 14:43:26 +03:00
# include <linux/blkdev.h>
2020-03-02 09:21:36 +03:00
# include "exfat_raw.h"
# include "exfat_fs.h"
static int exfat_cont_expand ( struct inode * inode , loff_t size )
{
struct address_space * mapping = inode - > i_mapping ;
loff_t start = i_size_read ( inode ) , count = size - i_size_read ( inode ) ;
int err , err2 ;
err = generic_cont_expand_simple ( inode , size ) ;
if ( err )
return err ;
inode - > i_ctime = inode - > i_mtime = current_time ( inode ) ;
mark_inode_dirty ( inode ) ;
if ( ! IS_SYNC ( inode ) )
return 0 ;
err = filemap_fdatawrite_range ( mapping , start , start + count - 1 ) ;
err2 = sync_mapping_buffers ( mapping ) ;
if ( ! err )
err = err2 ;
err2 = write_inode_now ( inode , 1 ) ;
if ( ! err )
err = err2 ;
if ( err )
return err ;
return filemap_fdatawait_range ( mapping , start , start + count - 1 ) ;
}
static bool exfat_allow_set_time ( struct exfat_sb_info * sbi , struct inode * inode )
{
mode_t allow_utime = sbi - > options . allow_utime ;
if ( ! uid_eq ( current_fsuid ( ) , inode - > i_uid ) ) {
if ( in_group_p ( inode - > i_gid ) )
allow_utime > > = 3 ;
if ( allow_utime & MAY_WRITE )
return true ;
}
/* use a default check */
return false ;
}
static int exfat_sanitize_mode ( const struct exfat_sb_info * sbi ,
struct inode * inode , umode_t * mode_ptr )
{
mode_t i_mode , mask , perm ;
i_mode = inode - > i_mode ;
mask = ( S_ISREG ( i_mode ) | | S_ISLNK ( i_mode ) ) ?
sbi - > options . fs_fmask : sbi - > options . fs_dmask ;
perm = * mode_ptr & ~ ( S_IFMT | mask ) ;
/* Of the r and x bits, all (subject to umask) must be present.*/
if ( ( perm & 0555 ) ! = ( i_mode & 0555 ) )
return - EPERM ;
if ( exfat_mode_can_hold_ro ( inode ) ) {
/*
* Of the w bits , either all ( subject to umask ) or none must
* be present .
*/
if ( ( perm & 0222 ) & & ( ( perm & 0222 ) ! = ( 0222 & ~ mask ) ) )
return - EPERM ;
} else {
/*
* If exfat_mode_can_hold_ro ( inode ) is false , can ' t change
* w bits .
*/
if ( ( perm & 0222 ) ! = ( 0222 & ~ mask ) )
return - EPERM ;
}
* mode_ptr & = S_IFMT | perm ;
return 0 ;
}
/* resize the file length */
int __exfat_truncate ( struct inode * inode , loff_t new_size )
{
unsigned int num_clusters_new , num_clusters_phys ;
unsigned int last_clu = EXFAT_FREE_CLUSTER ;
struct exfat_chain clu ;
struct super_block * sb = inode - > i_sb ;
struct exfat_sb_info * sbi = EXFAT_SB ( sb ) ;
struct exfat_inode_info * ei = EXFAT_I ( inode ) ;
int evict = ( ei - > dir . dir = = DIR_DELETED ) ? 1 : 0 ;
/* check if the given file ID is opened */
if ( ei - > type ! = TYPE_FILE & & ei - > type ! = TYPE_DIR )
return - EPERM ;
2020-07-31 08:58:26 +03:00
exfat_set_volume_dirty ( sb ) ;
2020-03-02 09:21:36 +03:00
num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP ( i_size_read ( inode ) , sbi ) ;
num_clusters_phys =
EXFAT_B_TO_CLU_ROUND_UP ( EXFAT_I ( inode ) - > i_size_ondisk , sbi ) ;
exfat_chain_set ( & clu , ei - > start_clu , num_clusters_phys , ei - > flags ) ;
if ( new_size > 0 ) {
/*
* Truncate FAT chain num_clusters after the first cluster
* num_clusters = min ( new , phys ) ;
*/
unsigned int num_clusters =
min ( num_clusters_new , num_clusters_phys ) ;
/*
* Follow FAT chain
* ( defensive coding - works fine even with corrupted FAT table
*/
if ( clu . flags = = ALLOC_NO_FAT_CHAIN ) {
clu . dir + = num_clusters ;
clu . size - = num_clusters ;
} else {
while ( num_clusters > 0 ) {
last_clu = clu . dir ;
if ( exfat_get_next_cluster ( sb , & ( clu . dir ) ) )
return - EIO ;
num_clusters - - ;
clu . size - - ;
}
}
} else {
ei - > flags = ALLOC_NO_FAT_CHAIN ;
ei - > start_clu = EXFAT_EOF_CLUSTER ;
}
i_size_write ( inode , new_size ) ;
if ( ei - > type = = TYPE_FILE )
ei - > attr | = ATTR_ARCHIVE ;
/* update the directory entry */
if ( ! evict ) {
struct timespec64 ts ;
2020-05-20 10:56:41 +03:00
struct exfat_dentry * ep , * ep2 ;
struct exfat_entry_set_cache * es ;
2020-06-24 03:54:54 +03:00
int err ;
2020-03-02 09:21:36 +03:00
es = exfat_get_dentry_set ( sb , & ( ei - > dir ) , ei - > entry ,
2020-05-20 10:56:41 +03:00
ES_ALL_ENTRIES ) ;
2020-03-02 09:21:36 +03:00
if ( ! es )
return - EIO ;
2020-05-20 10:56:41 +03:00
ep = exfat_get_dentry_cached ( es , 0 ) ;
ep2 = exfat_get_dentry_cached ( es , 1 ) ;
2020-03-02 09:21:36 +03:00
ts = current_time ( inode ) ;
exfat_set_entry_time ( sbi , & ts ,
& ep - > dentry . file . modify_tz ,
& ep - > dentry . file . modify_time ,
& ep - > dentry . file . modify_date ,
2020-04-22 02:30:56 +03:00
& ep - > dentry . file . modify_time_cs ) ;
2020-03-02 09:21:36 +03:00
ep - > dentry . file . attr = cpu_to_le16 ( ei - > attr ) ;
/* File size should be zero if there is no cluster allocated */
if ( ei - > start_clu = = EXFAT_EOF_CLUSTER ) {
2020-06-04 02:05:31 +03:00
ep2 - > dentry . stream . valid_size = 0 ;
ep2 - > dentry . stream . size = 0 ;
2020-03-02 09:21:36 +03:00
} else {
2020-06-04 02:05:31 +03:00
ep2 - > dentry . stream . valid_size = cpu_to_le64 ( new_size ) ;
2020-07-08 12:52:33 +03:00
ep2 - > dentry . stream . size = ep2 - > dentry . stream . valid_size ;
2020-03-02 09:21:36 +03:00
}
if ( new_size = = 0 ) {
/* Any directory can not be truncated to zero */
WARN_ON ( ei - > type ! = TYPE_FILE ) ;
ep2 - > dentry . stream . flags = ALLOC_FAT_CHAIN ;
ep2 - > dentry . stream . start_clu = EXFAT_FREE_CLUSTER ;
}
2020-05-20 10:56:41 +03:00
exfat_update_dir_chksum_with_entry_set ( es ) ;
2020-06-24 03:54:54 +03:00
err = exfat_free_dentry_set ( es , inode_needs_sync ( inode ) ) ;
if ( err )
return err ;
2020-03-02 09:21:36 +03:00
}
/* cut off from the FAT chain */
if ( ei - > flags = = ALLOC_FAT_CHAIN & & last_clu ! = EXFAT_FREE_CLUSTER & &
last_clu ! = EXFAT_EOF_CLUSTER ) {
if ( exfat_ent_set ( sb , last_clu , EXFAT_EOF_CLUSTER ) )
return - EIO ;
}
/* invalidate cache and free the clusters */
/* clear exfat cache */
exfat_cache_inval_inode ( inode ) ;
/* hint information */
ei - > hint_bmap . off = EXFAT_EOF_CLUSTER ;
ei - > hint_bmap . clu = EXFAT_EOF_CLUSTER ;
/* hint_stat will be used if this is directory. */
ei - > hint_stat . eidx = 0 ;
ei - > hint_stat . clu = ei - > start_clu ;
ei - > hint_femp . eidx = EXFAT_HINT_NONE ;
/* free the clusters */
if ( exfat_free_cluster ( inode , & clu ) )
return - EIO ;
2020-07-31 08:58:26 +03:00
exfat_clear_volume_dirty ( sb ) ;
2020-03-02 09:21:36 +03:00
return 0 ;
}
void exfat_truncate ( struct inode * inode , loff_t size )
{
struct super_block * sb = inode - > i_sb ;
struct exfat_sb_info * sbi = EXFAT_SB ( sb ) ;
2020-08-15 13:57:07 +03:00
unsigned int blocksize = i_blocksize ( inode ) ;
2020-03-02 09:21:36 +03:00
loff_t aligned_size ;
int err ;
mutex_lock ( & sbi - > s_lock ) ;
if ( EXFAT_I ( inode ) - > start_clu = = 0 ) {
/*
* Empty start_clu ! = ~ 0 ( not allocated )
*/
exfat_fs_error ( sb , " tried to truncate zeroed cluster. " ) ;
goto write_size ;
}
err = __exfat_truncate ( inode , i_size_read ( inode ) ) ;
if ( err )
goto write_size ;
inode - > i_ctime = inode - > i_mtime = current_time ( inode ) ;
if ( IS_DIRSYNC ( inode ) )
exfat_sync_inode ( inode ) ;
else
mark_inode_dirty ( inode ) ;
inode - > i_blocks = ( ( i_size_read ( inode ) + ( sbi - > cluster_size - 1 ) ) &
~ ( sbi - > cluster_size - 1 ) ) > > inode - > i_blkbits ;
write_size :
aligned_size = i_size_read ( inode ) ;
if ( aligned_size & ( blocksize - 1 ) ) {
aligned_size | = ( blocksize - 1 ) ;
aligned_size + + ;
}
if ( EXFAT_I ( inode ) - > i_size_ondisk > i_size_read ( inode ) )
EXFAT_I ( inode ) - > i_size_ondisk = aligned_size ;
if ( EXFAT_I ( inode ) - > i_size_aligned > i_size_read ( inode ) )
EXFAT_I ( inode ) - > i_size_aligned = aligned_size ;
mutex_unlock ( & sbi - > s_lock ) ;
}
2021-01-21 16:19:43 +03:00
int exfat_getattr ( struct user_namespace * mnt_uerns , const struct path * path ,
struct kstat * stat , unsigned int request_mask ,
unsigned int query_flags )
2020-03-02 09:21:36 +03:00
{
struct inode * inode = d_backing_inode ( path - > dentry ) ;
struct exfat_inode_info * ei = EXFAT_I ( inode ) ;
2021-01-21 16:19:30 +03:00
generic_fillattr ( & init_user_ns , inode , stat ) ;
2020-04-21 05:13:10 +03:00
exfat_truncate_atime ( & stat - > atime ) ;
2020-03-02 09:21:36 +03:00
stat - > result_mask | = STATX_BTIME ;
stat - > btime . tv_sec = ei - > i_crtime . tv_sec ;
stat - > btime . tv_nsec = ei - > i_crtime . tv_nsec ;
stat - > blksize = EXFAT_SB ( inode - > i_sb ) - > cluster_size ;
return 0 ;
}
2021-01-21 16:19:43 +03:00
int exfat_setattr ( struct user_namespace * mnt_userns , struct dentry * dentry ,
struct iattr * attr )
2020-03-02 09:21:36 +03:00
{
struct exfat_sb_info * sbi = EXFAT_SB ( dentry - > d_sb ) ;
struct inode * inode = dentry - > d_inode ;
unsigned int ia_valid ;
int error ;
if ( ( attr - > ia_valid & ATTR_SIZE ) & &
attr - > ia_size > i_size_read ( inode ) ) {
error = exfat_cont_expand ( inode , attr - > ia_size ) ;
if ( error | | attr - > ia_valid = = ATTR_SIZE )
return error ;
attr - > ia_valid & = ~ ATTR_SIZE ;
}
/* Check for setting the inode time. */
ia_valid = attr - > ia_valid ;
if ( ( ia_valid & ( ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET ) ) & &
exfat_allow_set_time ( sbi , inode ) ) {
attr - > ia_valid & = ~ ( ATTR_MTIME_SET | ATTR_ATIME_SET |
ATTR_TIMES_SET ) ;
}
2021-01-21 16:19:26 +03:00
error = setattr_prepare ( & init_user_ns , dentry , attr ) ;
2020-03-02 09:21:36 +03:00
attr - > ia_valid = ia_valid ;
if ( error )
goto out ;
if ( ( ( attr - > ia_valid & ATTR_UID ) & &
! uid_eq ( attr - > ia_uid , sbi - > options . fs_uid ) ) | |
( ( attr - > ia_valid & ATTR_GID ) & &
! gid_eq ( attr - > ia_gid , sbi - > options . fs_gid ) ) | |
( ( attr - > ia_valid & ATTR_MODE ) & &
( attr - > ia_mode & ~ ( S_IFREG | S_IFLNK | S_IFDIR | 0777 ) ) ) ) {
error = - EPERM ;
goto out ;
}
/*
* We don ' t return - EPERM here . Yes , strange , but this is too
* old behavior .
*/
if ( attr - > ia_valid & ATTR_MODE ) {
if ( exfat_sanitize_mode ( sbi , inode , & attr - > ia_mode ) < 0 )
attr - > ia_valid & = ~ ATTR_MODE ;
}
if ( attr - > ia_valid & ATTR_SIZE ) {
error = exfat_block_truncate_page ( inode , attr - > ia_size ) ;
if ( error )
goto out ;
down_write ( & EXFAT_I ( inode ) - > truncate_lock ) ;
truncate_setsize ( inode , attr - > ia_size ) ;
exfat_truncate ( inode , attr - > ia_size ) ;
up_write ( & EXFAT_I ( inode ) - > truncate_lock ) ;
}
2021-01-21 16:19:26 +03:00
setattr_copy ( & init_user_ns , inode , attr ) ;
2020-04-21 05:13:10 +03:00
exfat_truncate_atime ( & inode - > i_atime ) ;
2020-03-02 09:21:36 +03:00
mark_inode_dirty ( inode ) ;
out :
return error ;
}
2021-03-04 03:20:35 +03:00
static int exfat_ioctl_fitrim ( struct inode * inode , unsigned long arg )
{
struct request_queue * q = bdev_get_queue ( inode - > i_sb - > s_bdev ) ;
struct fstrim_range range ;
int ret = 0 ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
if ( ! blk_queue_discard ( q ) )
return - EOPNOTSUPP ;
if ( copy_from_user ( & range , ( struct fstrim_range __user * ) arg , sizeof ( range ) ) )
return - EFAULT ;
range . minlen = max_t ( unsigned int , range . minlen ,
q - > limits . discard_granularity ) ;
ret = exfat_trim_fs ( inode , & range ) ;
if ( ret < 0 )
return ret ;
if ( copy_to_user ( ( struct fstrim_range __user * ) arg , & range , sizeof ( range ) ) )
return - EFAULT ;
return 0 ;
}
long exfat_ioctl ( struct file * filp , unsigned int cmd , unsigned long arg )
{
struct inode * inode = file_inode ( filp ) ;
switch ( cmd ) {
case FITRIM :
return exfat_ioctl_fitrim ( inode , arg ) ;
default :
return - ENOTTY ;
}
}
# ifdef CONFIG_COMPAT
long exfat_compat_ioctl ( struct file * filp , unsigned int cmd ,
unsigned long arg )
{
return exfat_ioctl ( filp , cmd , ( unsigned long ) compat_ptr ( arg ) ) ;
}
# endif
2020-06-18 14:43:26 +03:00
int exfat_file_fsync ( struct file * filp , loff_t start , loff_t end , int datasync )
{
struct inode * inode = filp - > f_mapping - > host ;
int err ;
err = __generic_file_fsync ( filp , start , end , datasync ) ;
if ( err )
return err ;
err = sync_blockdev ( inode - > i_sb - > s_bdev ) ;
if ( err )
return err ;
2021-01-26 17:52:35 +03:00
return blkdev_issue_flush ( inode - > i_sb - > s_bdev ) ;
2020-06-18 14:43:26 +03:00
}
2020-03-02 09:21:36 +03:00
const struct file_operations exfat_file_operations = {
2020-05-02 04:34:25 +03:00
. llseek = generic_file_llseek ,
. read_iter = generic_file_read_iter ,
. write_iter = generic_file_write_iter ,
2021-03-04 03:20:35 +03:00
. unlocked_ioctl = exfat_ioctl ,
# ifdef CONFIG_COMPAT
. compat_ioctl = exfat_compat_ioctl ,
# endif
2020-05-02 04:34:25 +03:00
. mmap = generic_file_mmap ,
2020-06-18 14:43:26 +03:00
. fsync = exfat_file_fsync ,
2020-05-02 04:34:25 +03:00
. splice_read = generic_file_splice_read ,
. splice_write = iter_file_splice_write ,
2020-03-02 09:21:36 +03:00
} ;
const struct inode_operations exfat_file_inode_operations = {
. setattr = exfat_setattr ,
. getattr = exfat_getattr ,
} ;