Skip to content

Commit f761fcd

Browse files
Dongliang Cuinamjaejeon
authored andcommitted
exfat: Implement sops->shutdown and ioctl
We found that when writing a large file through buffer write, if the disk is inaccessible, exFAT does not return an error normally, which leads to the writing process not stopping properly. To easily reproduce this issue, you can follow the steps below: 1. format a device to exFAT and then mount (with a full disk erase) 2. dd if=/dev/zero of=/exfat_mount/test.img bs=1M count=8192 3. eject the device You may find that the dd process does not stop immediately and may continue for a long time. The root cause of this issue is that during buffer write process, exFAT does not need to access the disk to look up directory entries or the FAT table (whereas FAT would do) every time data is written. Instead, exFAT simply marks the buffer as dirty and returns, delegating the writeback operation to the writeback process. If the disk cannot be accessed at this time, the error will only be returned to the writeback process, and the original process will not receive the error, so it cannot be returned to the user side. When the disk cannot be accessed normally, an error should be returned to stop the writing process. Implement sops->shutdown and ioctl to shut down the file system when underlying block device is marked dead. Signed-off-by: Dongliang Cui <[email protected]> Signed-off-by: Zhiguo Niu <[email protected]> Signed-off-by: Namjae Jeon <[email protected]>
1 parent 231eb76 commit f761fcd

File tree

6 files changed

+121
-0
lines changed

6 files changed

+121
-0
lines changed

fs/exfat/exfat_fs.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <linux/ratelimit.h>
1111
#include <linux/nls.h>
1212
#include <linux/blkdev.h>
13+
#include <uapi/linux/exfat.h>
1314

1415
#define EXFAT_ROOT_INO 1
1516

@@ -148,6 +149,9 @@ enum {
148149
#define DIR_CACHE_SIZE \
149150
(DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1)
150151

152+
/* Superblock flags */
153+
#define EXFAT_FLAGS_SHUTDOWN 1
154+
151155
struct exfat_dentry_namebuf {
152156
char *lfn;
153157
int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */
@@ -267,6 +271,8 @@ struct exfat_sb_info {
267271
unsigned int clu_srch_ptr; /* cluster search pointer */
268272
unsigned int used_clusters; /* number of used clusters */
269273

274+
unsigned long s_exfat_flags; /* Exfat superblock flags */
275+
270276
struct mutex s_lock; /* superblock lock */
271277
struct mutex bitmap_lock; /* bitmap lock */
272278
struct exfat_mount_options options;
@@ -331,6 +337,11 @@ static inline struct exfat_inode_info *EXFAT_I(struct inode *inode)
331337
return container_of(inode, struct exfat_inode_info, vfs_inode);
332338
}
333339

340+
static inline int exfat_forced_shutdown(struct super_block *sb)
341+
{
342+
return test_bit(EXFAT_FLAGS_SHUTDOWN, &EXFAT_SB(sb)->s_exfat_flags);
343+
}
344+
334345
/*
335346
* If ->i_mode can't hold 0222 (i.e. ATTR_RO), we use ->i_attrs to
336347
* save ATTR_RO instead of ->i_mode.
@@ -459,6 +470,7 @@ int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync);
459470
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
460471
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
461472
unsigned long arg);
473+
int exfat_force_shutdown(struct super_block *sb, u32 flags);
462474

463475
/* namei.c */
464476
extern const struct dentry_operations exfat_dentry_ops;

fs/exfat/file.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
287287
unsigned int ia_valid;
288288
int error;
289289

290+
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
291+
return -EIO;
292+
290293
if ((attr->ia_valid & ATTR_SIZE) &&
291294
attr->ia_size > i_size_read(inode)) {
292295
error = exfat_cont_expand(inode, attr->ia_size);
@@ -470,6 +473,19 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
470473
return 0;
471474
}
472475

476+
static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
477+
{
478+
u32 flags;
479+
480+
if (!capable(CAP_SYS_ADMIN))
481+
return -EPERM;
482+
483+
if (get_user(flags, (__u32 __user *)arg))
484+
return -EFAULT;
485+
486+
return exfat_force_shutdown(sb, flags);
487+
}
488+
473489
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
474490
{
475491
struct inode *inode = file_inode(filp);
@@ -480,6 +496,8 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
480496
return exfat_ioctl_get_attributes(inode, user_attr);
481497
case FAT_IOCTL_SET_ATTRIBUTES:
482498
return exfat_ioctl_set_attributes(filp, user_attr);
499+
case EXFAT_IOC_SHUTDOWN:
500+
return exfat_ioctl_shutdown(inode->i_sb, arg);
483501
case FITRIM:
484502
return exfat_ioctl_fitrim(inode, arg);
485503
default:
@@ -500,6 +518,9 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
500518
struct inode *inode = filp->f_mapping->host;
501519
int err;
502520

521+
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
522+
return -EIO;
523+
503524
err = __generic_file_fsync(filp, start, end, datasync);
504525
if (err)
505526
return err;

fs/exfat/inode.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ int exfat_write_inode(struct inode *inode, struct writeback_control *wbc)
102102
{
103103
int ret;
104104

105+
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
106+
return -EIO;
107+
105108
mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
106109
ret = __exfat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
107110
mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
@@ -402,6 +405,9 @@ static void exfat_readahead(struct readahead_control *rac)
402405
static int exfat_writepages(struct address_space *mapping,
403406
struct writeback_control *wbc)
404407
{
408+
if (unlikely(exfat_forced_shutdown(mapping->host->i_sb)))
409+
return -EIO;
410+
405411
return mpage_writepages(mapping, wbc, exfat_get_block);
406412
}
407413

@@ -422,6 +428,9 @@ static int exfat_write_begin(struct file *file, struct address_space *mapping,
422428
{
423429
int ret;
424430

431+
if (unlikely(exfat_forced_shutdown(mapping->host->i_sb)))
432+
return -EIO;
433+
425434
ret = block_write_begin(mapping, pos, len, foliop, exfat_get_block);
426435

427436
if (ret < 0)

fs/exfat/namei.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,9 @@ static int exfat_create(struct mnt_idmap *idmap, struct inode *dir,
547547
int err;
548548
loff_t size = i_size_read(dir);
549549

550+
if (unlikely(exfat_forced_shutdown(sb)))
551+
return -EIO;
552+
550553
mutex_lock(&EXFAT_SB(sb)->s_lock);
551554
exfat_set_volume_dirty(sb);
552555
err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE,
@@ -770,6 +773,9 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
770773
struct exfat_entry_set_cache es;
771774
int entry, err = 0;
772775

776+
if (unlikely(exfat_forced_shutdown(sb)))
777+
return -EIO;
778+
773779
mutex_lock(&EXFAT_SB(sb)->s_lock);
774780
exfat_chain_dup(&cdir, &ei->dir);
775781
entry = ei->entry;
@@ -823,6 +829,9 @@ static int exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir,
823829
int err;
824830
loff_t size = i_size_read(dir);
825831

832+
if (unlikely(exfat_forced_shutdown(sb)))
833+
return -EIO;
834+
826835
mutex_lock(&EXFAT_SB(sb)->s_lock);
827836
exfat_set_volume_dirty(sb);
828837
err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR,
@@ -913,6 +922,9 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
913922
struct exfat_entry_set_cache es;
914923
int entry, err;
915924

925+
if (unlikely(exfat_forced_shutdown(sb)))
926+
return -EIO;
927+
916928
mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
917929

918930
exfat_chain_dup(&cdir, &ei->dir);
@@ -980,6 +992,9 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
980992
struct exfat_entry_set_cache old_es, new_es;
981993
int sync = IS_DIRSYNC(inode);
982994

995+
if (unlikely(exfat_forced_shutdown(sb)))
996+
return -EIO;
997+
983998
num_new_entries = exfat_calc_num_entries(p_uniname);
984999
if (num_new_entries < 0)
9851000
return num_new_entries;

fs/exfat/super.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ static int exfat_sync_fs(struct super_block *sb, int wait)
4646
struct exfat_sb_info *sbi = EXFAT_SB(sb);
4747
int err = 0;
4848

49+
if (unlikely(exfat_forced_shutdown(sb)))
50+
return 0;
51+
4952
if (!wait)
5053
return 0;
5154

@@ -167,6 +170,41 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root)
167170
return 0;
168171
}
169172

173+
int exfat_force_shutdown(struct super_block *sb, u32 flags)
174+
{
175+
int ret;
176+
struct exfat_sb_info *sbi = sb->s_fs_info;
177+
struct exfat_mount_options *opts = &sbi->options;
178+
179+
if (exfat_forced_shutdown(sb))
180+
return 0;
181+
182+
switch (flags) {
183+
case EXFAT_GOING_DOWN_DEFAULT:
184+
case EXFAT_GOING_DOWN_FULLSYNC:
185+
ret = bdev_freeze(sb->s_bdev);
186+
if (ret)
187+
return ret;
188+
bdev_thaw(sb->s_bdev);
189+
set_bit(EXFAT_FLAGS_SHUTDOWN, &sbi->s_exfat_flags);
190+
break;
191+
case EXFAT_GOING_DOWN_NOSYNC:
192+
set_bit(EXFAT_FLAGS_SHUTDOWN, &sbi->s_exfat_flags);
193+
break;
194+
default:
195+
return -EINVAL;
196+
}
197+
198+
if (opts->discard)
199+
opts->discard = 0;
200+
return 0;
201+
}
202+
203+
static void exfat_shutdown(struct super_block *sb)
204+
{
205+
exfat_force_shutdown(sb, EXFAT_GOING_DOWN_NOSYNC);
206+
}
207+
170208
static struct inode *exfat_alloc_inode(struct super_block *sb)
171209
{
172210
struct exfat_inode_info *ei;
@@ -193,6 +231,7 @@ static const struct super_operations exfat_sops = {
193231
.sync_fs = exfat_sync_fs,
194232
.statfs = exfat_statfs,
195233
.show_options = exfat_show_options,
234+
.shutdown = exfat_shutdown,
196235
};
197236

198237
enum {

include/uapi/linux/exfat.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
2+
/*
3+
* Copyright (C) 2024 Unisoc Technologies Co., Ltd.
4+
*/
5+
6+
#ifndef _UAPI_LINUX_EXFAT_H
7+
#define _UAPI_LINUX_EXFAT_H
8+
#include <linux/types.h>
9+
#include <linux/ioctl.h>
10+
11+
/*
12+
* exfat-specific ioctl commands
13+
*/
14+
15+
#define EXFAT_IOC_SHUTDOWN _IOR('X', 125, __u32)
16+
17+
/*
18+
* Flags used by EXFAT_IOC_SHUTDOWN
19+
*/
20+
21+
#define EXFAT_GOING_DOWN_DEFAULT 0x0 /* default with full sync */
22+
#define EXFAT_GOING_DOWN_FULLSYNC 0x1 /* going down with full sync*/
23+
#define EXFAT_GOING_DOWN_NOSYNC 0x2 /* going down */
24+
25+
#endif /* _UAPI_LINUX_EXFAT_H */

0 commit comments

Comments
 (0)