From 531f58fca88fa374c6cbd292ebc4e181e407441a Mon Sep 17 00:00:00 2001 From: Vasant Karasulli Date: Thu, 17 Mar 2022 10:53:15 +0900 Subject: [PATCH 001/141] exfat: allow access to paths with trailing dots exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done intentionally, loosely following Windows behaviour and specifications which state: #exFAT The concatenated file name has the same set of illegal characters as other FAT-based file systems (see Table 31). #FAT ... Leading and trailing spaces in a long name are ignored. Leading and embedded periods are allowed in a name and are stored in the long name. Trailing periods are ignored. Note: Leading and trailing space ' ' characters are currently retained by Linux kernel exfat, in conflict with the above specification. On Windows 10, trailing and leading space ' ' characters are stripped from the filenames . Some implementations, such as fuse-exfat, don't perform path trailer removal. When mounting images which contain trailing-dot paths, these paths are unreachable, e.g.: + mount.exfat-fuse /dev/zram0 /mnt/test/ FUSE exfat 1.3.0 + cd /mnt/test/ + touch fuse_created_dots... ' fuse_created_spaces ' + ls -l total 0 -rwxrwxrwx 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces ' -rwxrwxrwx 1 root 0 0 Aug 18 09:45 fuse_created_dots... + cd / + umount /mnt/test/ + mount -t exfat /dev/zram0 /mnt/test + cd /mnt/test + ls -l ls: cannot access 'fuse_created_dots...': No such file or directory total 0 -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces ' -????????? ? ? ? ? ? fuse_created_dots... + touch kexfat_created_dots... ' kexfat_created_spaces ' + ls -l ls: cannot access 'fuse_created_dots...': No such file or directory total 0 -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces ' -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' kexfat_created_spaces ' -????????? ? ? ? ? ? fuse_created_dots... -rwxr-xr-x 1 root 0 0 Aug 18 09:45 kexfat_created_dots + cd / + umount /mnt/test/ This commit adds "keep_last_dots" mount option that controls whether or not trailing periods '.' are stripped from path components during file lookup or file creation. This mount option can be used to access paths with trailing periods and disallow creating files with names with trailing periods. E.g. continuing from the previous example: + mount -t exfat -o keep_last_dots /dev/zram0 /mnt/test + cd /mnt/test + ls -l total 0 -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' fuse_created_spaces ' -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' kexfat_created_spaces ' -rwxr-xr-x 1 root 0 0 Aug 18 10:32 fuse_created_dots... -rwxr-xr-x 1 root 0 0 Aug 18 10:32 kexfat_created_dots + echo > kexfat_created_dots_again... sh: kexfat_created_dots_again...: Invalid argument Link: https://bugzilla.suse.com/show_bug.cgi?id=1188964 Link: https://lore.kernel.org/linux-fsdevel/003b01d755e4$31fb0d80$95f12880$ @samsung.com/ Link: https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification Suggested-by: Takashi Iwai Signed-off-by: Vasant Karasulli Co-developed-by: David Disseldorp Signed-off-by: David Disseldorp Signed-off-by: Namjae Jeon --- exfat_fs.h | 3 ++- namei.c | 50 ++++++++++++++++++++++++++++++++++++-------------- super.c | 7 +++++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 7572349..deb7755 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -213,7 +213,8 @@ struct exfat_mount_options { /* on error: continue, panic, remount-ro */ enum exfat_error_mode errors; unsigned utf8:1, /* Use of UTF-8 character set */ - discard:1; /* Issue discard requests on deletions */ + discard:1, /* Issue discard requests on deletions */ + keep_last_dots:1; /* Keep trailing periods in paths */ int time_offset; /* Offset of timestamps from UTC (in minutes) */ }; diff --git a/namei.c b/namei.c index 1b8dc78..b5b67e5 100644 --- a/namei.c +++ b/namei.c @@ -73,11 +73,14 @@ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags) return ret; } -/* returns the length of a struct qstr, ignoring trailing dots */ -static unsigned int exfat_striptail_len(unsigned int len, const char *name) +/* returns the length of a struct qstr, ignoring trailing dots if necessary */ +static unsigned int exfat_striptail_len(unsigned int len, const char *name, + bool keep_last_dots) { - while (len && name[len - 1] == '.') - len--; + if (!keep_last_dots) { + while (len && name[len - 1] == '.') + len--; + } return len; } @@ -91,7 +94,8 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr) struct super_block *sb = dentry->d_sb; struct nls_table *t = EXFAT_SB(sb)->nls_io; const unsigned char *name = qstr->name; - unsigned int len = exfat_striptail_len(qstr->len, qstr->name); + unsigned int len = exfat_striptail_len(qstr->len, qstr->name, + EXFAT_SB(sb)->options.keep_last_dots); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) unsigned long hash = init_name_hash(dentry); #else @@ -121,8 +125,10 @@ static int exfat_d_cmp(const struct dentry *parent, const struct dentry *dentry, { struct super_block *sb = dentry->d_sb; struct nls_table *t = EXFAT_SB(sb)->nls_io; - unsigned int alen = exfat_striptail_len(name->len, name->name); - unsigned int blen = exfat_striptail_len(len, str); + unsigned int alen = exfat_striptail_len(name->len, name->name, + EXFAT_SB(sb)->options.keep_last_dots); + unsigned int blen = exfat_striptail_len(len, str, + EXFAT_SB(sb)->options.keep_last_dots); wchar_t c1, c2; int charlen, i; @@ -153,7 +159,8 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr) { struct super_block *sb = dentry->d_sb; const unsigned char *name = qstr->name; - unsigned int len = exfat_striptail_len(qstr->len, qstr->name); + unsigned int len = exfat_striptail_len(qstr->len, qstr->name, + EXFAT_SB(sb)->options.keep_last_dots); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) unsigned long hash = init_name_hash(dentry); #else @@ -188,8 +195,11 @@ static int exfat_utf8_d_cmp(const struct dentry *parent, #endif { struct super_block *sb = dentry->d_sb; - unsigned int alen = exfat_striptail_len(name->len, name->name); - unsigned int blen = exfat_striptail_len(len, str); + unsigned int alen = exfat_striptail_len(name->len, name->name, + EXFAT_SB(sb)->options.keep_last_dots); + unsigned int blen = exfat_striptail_len(len, str, + EXFAT_SB(sb)->options.keep_last_dots); + unicode_t u_a, u_b; int charlen, i; @@ -443,13 +453,25 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); + int pathlen = strlen(path); - /* strip all trailing periods */ - namelen = exfat_striptail_len(strlen(path), path); + /* + * get the length of the pathname excluding + * trailing periods, if any. + */ + namelen = exfat_striptail_len(pathlen, path, false); + if (EXFAT_SB(sb)->options.keep_last_dots) { + /* + * Do not allow the creation of files with names + * ending with period(s). + */ + if (!lookup && (namelen < pathlen)) + return -EINVAL; + namelen = pathlen; + } if (!namelen) return -ENOENT; - - if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE)) + if (pathlen > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE)) return -ENAMETOOLONG; /* diff --git a/super.c b/super.c index 013a392..12f56e8 100644 --- a/super.c +++ b/super.c @@ -191,6 +191,8 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",errors=remount-ro"); if (opts->discard) seq_puts(m, ",discard"); + if (opts->keep_last_dots) + seq_puts(m, ",keep_last_dots"); if (opts->time_offset) seq_printf(m, ",time_offset=%d", opts->time_offset); return 0; @@ -269,6 +271,7 @@ enum { Opt_charset, Opt_errors, Opt_discard, + Opt_keep_last_dots, Opt_time_offset, /* Deprecated options */ @@ -312,6 +315,7 @@ static const struct fs_parameter_spec exfat_param_specs[] = { fsparam_enum("errors", Opt_errors), #endif fsparam_flag("discard", Opt_discard), + fsparam_flag("keep_last_dots", Opt_keep_last_dots), fsparam_s32("time_offset", Opt_time_offset), __fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated, NULL), @@ -378,6 +382,9 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) case Opt_discard: opts->discard = 1; break; + case Opt_keep_last_dots: + opts->keep_last_dots = 1; + break; case Opt_time_offset: /* * Make the limit 24 just in case someone invents something From e92aaf1b8ae65014001ac0dab55ac23353855741 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 29 Mar 2022 16:44:25 +0900 Subject: [PATCH 002/141] exfat: do not clear VolumeDirty in writeback Before this commit, VolumeDirty will be cleared first in writeback if 'dirsync' or 'sync' is not enabled. If the power is suddenly cut off after cleaning VolumeDirty but other updates are not written, the exFAT filesystem will not be able to detect the power failure in the next mount. And VolumeDirty will be set again but not cleared when updating the parent directory. It means that BootSector will be written at least once in each write-back, which will shorten the life of the device. Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- file.c | 2 -- namei.c | 5 ----- super.c | 14 ++++++-------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/file.c b/file.c index 6b45fdb..1b28930 100644 --- a/file.c +++ b/file.c @@ -233,8 +233,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) if (exfat_free_cluster(inode, &clu)) return -EIO; - exfat_clear_volume_dirty(sb); - return 0; } diff --git a/namei.c b/namei.c index b5b67e5..2cc809c 100644 --- a/namei.c +++ b/namei.c @@ -608,7 +608,6 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, exfat_set_volume_dirty(sb); err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE, &info); - exfat_clear_volume_dirty(sb); if (err) goto unlock; @@ -896,7 +895,6 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) /* This doesn't modify ei */ ei->dir.dir = DIR_DELETED; - exfat_clear_volume_dirty(sb); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); @@ -950,7 +948,6 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) exfat_set_volume_dirty(sb); err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR, &info); - exfat_clear_volume_dirty(sb); if (err) goto unlock; @@ -1098,7 +1095,6 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) goto unlock; } ei->dir.dir = DIR_DELETED; - exfat_clear_volume_dirty(sb); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); @@ -1449,7 +1445,6 @@ static int __exfat_rename(struct inode *old_parent_inode, */ new_ei->dir.dir = DIR_DELETED; } - exfat_clear_volume_dirty(sb); out: return ret; } diff --git a/super.c b/super.c index 12f56e8..615e52d 100644 --- a/super.c +++ b/super.c @@ -113,7 +113,6 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags) { struct exfat_sb_info *sbi = EXFAT_SB(sb); struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; - bool sync; /* retain persistent-flags */ new_flags |= sbi->vol_flags_persistent; @@ -136,16 +135,15 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags) p_boot->vol_flags = cpu_to_le16(new_flags); - if ((new_flags & VOLUME_DIRTY) && !buffer_dirty(sbi->boot_bh)) - sync = true; - else - sync = false; - set_buffer_uptodate(sbi->boot_bh); mark_buffer_dirty(sbi->boot_bh); - if (sync) - sync_dirty_buffer(sbi->boot_bh); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + __sync_dirty_buffer(sbi->boot_bh, REQ_SYNC | REQ_FUA | REQ_PREFLUSH); +#else + __sync_dirty_buffer(sbi->boot_bh, REQ_SYNC | REQ_FUA | REQ_FLUSH); +#endif + return 0; } From d99f8f0a5e7e1e7034aaf311101d81f0a281897d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 29 Mar 2022 16:54:07 +0900 Subject: [PATCH 003/141] exfat: remove travis-CI test Signed-off-by: Namjae Jeon --- .travis.yml | 188 ---------------------------------------------------- 1 file changed, 188 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cb25bd0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,188 +0,0 @@ -dist: bionic - -language: c - -notifications: - - email: true - -before_script: - # Download the kernel - - sudo apt-get install libelf-dev wget tar gzip python - - wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz - - tar xf linux-4.1.36.tar.gz - - mv linux-4.1.36 linux-stable - - ./.travis_get_mainline_kernel - - cp ./.travis_cmd_wrapper.pl ~/travis_cmd_wrapper.pl - # Prerequisite for xfstests testing - - sudo apt-get install linux-headers-$(uname -r) - - sudo apt-get install autoconf libtool pkg-config libnl-3-dev libnl-genl-3-dev - - sudo apt-get install xfslibs-dev uuid-dev libtool-bin xfsprogs libgdbm-dev gawk fio attr libattr1-dev libacl1-dev libaio-dev - - git clone --branch=exfat-next https://github.com/exfat-utils/exfat-utils - - git clone https://github.com/namjaejeon/exfat-testsuites - - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - - export PATH=/usr/local/lib:$PATH - - sudo useradd fsgqa - - sudo useradd 123456-fsgqa - -script: - # Copy ksmbd source to kernel - - mv linux-stable ../ - - mv linux ../ - - mkdir ../linux-stable/fs/exfat - - cp -ar * ../linux-stable/fs/exfat/ - - cp -ar * ../linux/fs/exfat/ - - # Compile with 4.1 kernel - - cd ../linux-stable - - yes "" | make oldconfig > /dev/null - - echo 'obj-$(CONFIG_EXFAT_FS) += exfat/' >> fs/Makefile - - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig - - echo 'CONFIG_EXFAT_FS=m' >> .config - - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config - - make -j$((`nproc`+1)) fs/exfat/exfat.ko - - # Compile with latest Torvalds' kernel -# - cd ../linux -# - yes "" | make oldconfig > /dev/null -# - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile -# - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig -# - echo 'CONFIG_EXFAT_FS=m' >> .config -# - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config -# - make -j$((`nproc`+1)) fs/exfat/exfat.ko - - # Run xfstests testsuite - - cd ../linux-exfat-oot - - make > /dev/null - - sudo make install > /dev/null - - sudo modprobe exfat - - cd exfat-utils - - ./autogen.sh > /dev/null - - ./configure > /dev/null - - make -j$((`nproc`+1)) > /dev/null - - sudo make install > /dev/null - - sudo mkdir -p /mnt/scratch - - sudo mkdir -p /mnt/test - - sudo mkdir -p /mnt/full_test - # create file/director test - - truncate -s 10G full_test.img - - sudo losetup /dev/loop22 full_test.img - - sudo mkfs.exfat /dev/loop22 - - sudo mount -t exfat /dev/loop22 /mnt/full_test/ - - cd /mnt/full_test/ - - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - - sync - - sudo fsck.exfat /dev/loop22 - - sudo rm -rf * - - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - - sync - - sudo rm -rf * - - sudo fsck.exfat /dev/loop22 - - cd - - - sudo umount /mnt/full_test/ - - sudo fsck.exfat /dev/loop22 - # run xfstests test - - truncate -s 100G test.img - - truncate -s 100G scratch.img - - sudo losetup /dev/loop20 test.img - - sudo losetup /dev/loop21 scratch.img - - sudo mkfs.exfat /dev/loop20 - - sudo mkfs.exfat /dev/loop21 - - cd .. - - cd exfat-testsuites/ - - tar xzvf xfstests-exfat.tgz > /dev/null - - cd xfstests-exfat - - make -j$((`nproc`+1)) > /dev/null - - sudo ./check generic/001 - - sudo ./check generic/006 - - sudo ./check generic/007 - - sudo ./check generic/011 - - sudo ./check generic/013 - - sudo ./check generic/014 - - sudo ./check generic/028 - - sudo ./check generic/029 - - sudo ./check generic/030 - - sudo ./check generic/034 - - sudo ./check generic/035 - - sudo ./check generic/036 - - sudo ./check generic/069 - - sudo ./check generic/073 - - sudo ./check generic/074 - - sudo ./check generic/075 - - sudo ./check generic/076 - - sudo ./check generic/080 - - sudo ./check generic/084 - - sudo ./check generic/091 - - sudo ./check generic/095 - - sudo ./check generic/098 - - sudo ./check generic/100 - - sudo ./check generic/112 - - sudo ./check generic/113 - - sudo ./check generic/114 - - sudo ./check generic/120 - - sudo ./check generic/123 - - sudo ./check generic/124 - - sudo ./check generic/127 - - sudo ./check generic/129 - - sudo ./check generic/130 - - sudo ./check generic/131 - - sudo ./check generic/132 - - sudo ./check generic/133 - - sudo ./check generic/135 - - sudo ./check generic/141 - - sudo ./check generic/169 - - sudo ./check generic/198 - - sudo ./check generic/207 - - sudo ./check generic/208 - - sudo ./check generic/209 - - sudo ./check generic/210 - - sudo ./check generic/211 - - sudo ./check generic/212 - - sudo ./check generic/215 - - sudo ./check generic/221 - - sudo ./check generic/239 - - sudo ./check generic/240 - - sudo ./check generic/241 - - sudo ./check generic/245 - - sudo ./check generic/246 - - sudo ./check generic/247 - - sudo ./check generic/248 - - sudo ./check generic/249 - - sudo ./check generic/257 - - sudo ./check generic/260 - - sudo ./check generic/263 - - sudo ./check generic/285 - - sudo ./check generic/286 - - sudo ./check generic/288 - - sudo ./check generic/308 - - sudo ./check generic/309 - - sudo ./check generic/310 - - sudo ./check generic/313 - - sudo ./check generic/323 - - sudo ./check generic/325 - - sudo ./check generic/338 - - sudo ./check generic/339 - - sudo ./check generic/340 - - sudo ./check generic/344 - - sudo ./check generic/345 - - sudo ./check generic/346 - - sudo ./check generic/347 - - sudo ./check generic/354 - - sudo ./check generic/376 - - sudo ./check generic/393 - - sudo ./check generic/394 - - sudo ./check generic/405 - - sudo ./check generic/406 - - sudo ./check generic/409 - - sudo ./check generic/410 - - sudo ./check generic/411 - - sudo ./check generic/412 - - sudo ./check generic/418 - - sudo ./check generic/428 - - sudo ./check generic/437 - - sudo ./check generic/438 - - sudo ./check generic/441 - - sudo ./check generic/443 - - sudo ./check generic/448 - - sudo ./check generic/450 - - sudo ./check generic/451 - - sudo ./check generic/452 From 2ae96136ee6ba52d04ab57019791619b2993cc73 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 4 Apr 2022 12:06:20 +0900 Subject: [PATCH 004/141] exfat: fix referencing wrong parent directory information During renaming, the parent directory information maybe updated. But the file/directory still references to the old parent directory information. This bug will cause 2 problems. (1) The renamed file can not be written. [10768.175172] exFAT-fs (sda1): error, failed to bmap (inode : 7afd50e4 iblock : 0, err : -5) [10768.184285] exFAT-fs (sda1): Filesystem has been set read-only ash: write error: Input/output error (2) Some dentries of the renamed file/directory are not set to deleted after removing the file/directory. exfat_update_parent_info() is a workaround for the wrong parent directory information being used after renaming. Now that bug is fixed, this is no longer needed, so remove it. Fixes: 5f2aa075070c ("exfat: add inode operations") Cc: stable@vger.kernel.org # v5.7+ Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/namei.c b/namei.c index 2cc809c..070e4a2 100644 --- a/namei.c +++ b/namei.c @@ -1196,6 +1196,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, exfat_remove_entries(inode, p_dir, oldentry, 0, num_old_entries); + ei->dir = *p_dir; ei->entry = newentry; } else { if (exfat_get_entry_type(epold) == TYPE_FILE) { @@ -1283,28 +1284,6 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, return 0; } -static void exfat_update_parent_info(struct exfat_inode_info *ei, - struct inode *parent_inode) -{ - struct exfat_sb_info *sbi = EXFAT_SB(parent_inode->i_sb); - struct exfat_inode_info *parent_ei = EXFAT_I(parent_inode); - loff_t parent_isize = i_size_read(parent_inode); - - /* - * the problem that struct exfat_inode_info caches wrong parent info. - * - * because of flag-mismatch of ei->dir, - * there is abnormal traversing cluster chain. - */ - if (unlikely(parent_ei->flags != ei->dir.flags || - parent_isize != EXFAT_CLU_TO_B(ei->dir.size, sbi) || - parent_ei->start_clu != ei->dir.dir)) { - exfat_chain_set(&ei->dir, parent_ei->start_clu, - EXFAT_B_TO_CLU_ROUND_UP(parent_isize, sbi), - parent_ei->flags); - } -} - /* rename or move a old file into a new file */ static int __exfat_rename(struct inode *old_parent_inode, struct exfat_inode_info *ei, struct inode *new_parent_inode, @@ -1335,8 +1314,6 @@ static int __exfat_rename(struct inode *old_parent_inode, return -ENOENT; } - exfat_update_parent_info(ei, old_parent_inode); - exfat_chain_dup(&olddir, &ei->dir); dentry = ei->entry; @@ -1357,8 +1334,6 @@ static int __exfat_rename(struct inode *old_parent_inode, goto out; } - exfat_update_parent_info(new_ei, new_parent_inode); - p_dir = &(new_ei->dir); new_entry = new_ei->entry; ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); From 8dbffe2044ab0564bea3e519d5ca83eb0cff426c Mon Sep 17 00:00:00 2001 From: Chung-Chiang Cheng Date: Fri, 8 Apr 2022 22:00:57 +0900 Subject: [PATCH 005/141] exfat: introduce mount option 'sys_tz' EXFAT_TZ_VALID bit in {create,modify,access}_tz is corresponding to OffsetValid field in exfat specification [1]. When this bit isn't set, timestamps should be treated as having the same UTC offset as the current local time. Currently, there is an option 'time_offset' for users to specify the UTC offset for this issue. This patch introduces a new mount option 'sys_tz' to use system timezone as time offset. Link: [1] https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification#74102-offsetvalid-field Signed-off-by: Chung-Chiang Cheng Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 1 + misc.c | 10 ++++++++-- super.c | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index deb7755..e5afb4c 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -213,6 +213,7 @@ struct exfat_mount_options { /* on error: continue, panic, remount-ro */ enum exfat_error_mode errors; unsigned utf8:1, /* Use of UTF-8 character set */ + sys_tz:1, /* Use local timezone */ discard:1, /* Issue discard requests on deletions */ keep_last_dots:1; /* Keep trailing periods in paths */ int time_offset; /* Offset of timestamps from UTC (in minutes) */ diff --git a/misc.c b/misc.c index 47c3682..c03a586 100644 --- a/misc.c +++ b/misc.c @@ -84,6 +84,13 @@ static void exfat_adjust_tz(struct timespec *ts, u8 tz_off) ts->tv_sec += TIMEZONE_SEC(0x80 - tz_off); } +static inline int exfat_tz_offset(struct exfat_sb_info *sbi) +{ + if (sbi->options.sys_tz) + return -sys_tz.tz_minuteswest; + return sbi->options.time_offset; +} + /* Convert a EXFAT time/date pair to a UNIX date (seconds since 1 1 70). */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, @@ -111,8 +118,7 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, /* Adjust timezone to UTC0. */ exfat_adjust_tz(ts, tz & ~EXFAT_TZ_VALID); else - /* Convert from local time to UTC using time_offset. */ - ts->tv_sec -= sbi->options.time_offset * SECS_PER_MIN; + ts->tv_sec -= exfat_tz_offset(sbi) * SECS_PER_MIN; } /* Convert linear UNIX date to a EXFAT time/date pair. */ diff --git a/super.c b/super.c index 615e52d..ec9de28 100644 --- a/super.c +++ b/super.c @@ -191,7 +191,9 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",discard"); if (opts->keep_last_dots) seq_puts(m, ",keep_last_dots"); - if (opts->time_offset) + if (opts->sys_tz) + seq_puts(m, ",sys_tz"); + else if (opts->time_offset) seq_printf(m, ",time_offset=%d", opts->time_offset); return 0; } @@ -270,6 +272,7 @@ enum { Opt_errors, Opt_discard, Opt_keep_last_dots, + Opt_sys_tz, Opt_time_offset, /* Deprecated options */ @@ -314,6 +317,7 @@ static const struct fs_parameter_spec exfat_param_specs[] = { #endif fsparam_flag("discard", Opt_discard), fsparam_flag("keep_last_dots", Opt_keep_last_dots), + fsparam_flag("sys_tz", Opt_sys_tz), fsparam_s32("time_offset", Opt_time_offset), __fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated, NULL), @@ -383,6 +387,9 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) case Opt_keep_last_dots: opts->keep_last_dots = 1; break; + case Opt_sys_tz: + opts->sys_tz = 1; + break; case Opt_time_offset: /* * Make the limit 24 just in case someone invents something From 1d092fc3520025335ae4c7433b637e6000aa9bdd Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 8 Apr 2022 22:14:06 +0900 Subject: [PATCH 006/141] exfat: reduce block requests when zeroing a cluster If 'dirsync' is enabled, when zeroing a cluster, submitting sector by sector will generate many block requests, will cause the block device to not fully perform its performance. This commit makes the sectors in a cluster to be submitted in once, it will reduce the number of block requests. This will make the block device to give full play to its performance. Test create 1000 directories on SD card with: $ time (for ((i=0;i<1000;i++)); do mkdir dir${i}; done) Performance has been improved by more than 73% on imx6q-sabrelite. Cluster size Before After Improvement 64 KBytes 3m34.036s 0m56.052s 73.8% 128 KBytes 6m2.644s 1m13.354s 79.8% 256 KBytes 11m22.202s 1m39.451s 85.4% imx6q-sabrelite: - CPU: 792 MHz x4 - Memory: 1GB DDR3 - SD Card: SanDisk 8GB Class 4 Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Christoph Hellwig Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- fatent.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/fatent.c b/fatent.c index ca72551..61fbf9f 100644 --- a/fatent.c +++ b/fatent.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -278,6 +279,46 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) +int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) +{ + struct super_block *sb = dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct buffer_head *bh; + sector_t blknr, last_blknr; + int i; + + blknr = exfat_cluster_to_sector(sbi, clu); + last_blknr = blknr + sbi->sect_per_clus; + + if (last_blknr > sbi->num_sectors && sbi->num_sectors > 0) { + exfat_fs_error_ratelimit(sb, + "%s: out of range(sect:%llu len:%u)", + __func__, (unsigned long long)blknr, + sbi->sect_per_clus); + return -EIO; + } + + /* Zeroing the unused blocks on this cluster */ + for (i = blknr; i < last_blknr; i++) { + bh = sb_getblk(sb, i); + if (!bh) + return -ENOMEM; + + memset(bh->b_data, 0, sb->s_blocksize); + set_buffer_uptodate(bh); + mark_buffer_dirty(bh); + brelse(bh); + } + + if (IS_DIRSYNC(dir)) + return sync_blockdev_range(sb->s_bdev, + EXFAT_BLK_TO_B(blknr, sb), + EXFAT_BLK_TO_B(last_blknr, sb) - 1); + + return 0; +} +#else int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) { struct super_block *sb = dir->i_sb; @@ -324,6 +365,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) bforget(bhs[i]); return err; } +#endif int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, struct exfat_chain *p_chain, bool sync_bmap) From bf7b4057f4fd3f93726211a6aa112ac792519f09 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 11 May 2022 15:32:40 +0900 Subject: [PATCH 007/141] exfat: mm: require ->set_page_dirty to be explicitly wired up Remove the CONFIG_BLOCK default to __set_page_dirty_buffers and just wire that method up for the missing instances. [hch@lst.de: ecryptfs: add a ->set_page_dirty cludge] Link: https://lkml.kernel.org/r/20210624125250.536369-1-hch@lst.de Link: https://lkml.kernel.org/r/20210614061512.3966143-4-hch@lst.de Signed-off-by: Christoph Hellwig Reviewed-by: Greg Kroah-Hartman Reviewed-by: Jan Kara Cc: Al Viro Cc: Matthew Wilcox (Oracle) Cc: Tyler Hicks Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds Signed-off-by: Namjae Jeon --- inode.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inode.c b/inode.c index 47c6cba..ca0c752 100644 --- a/inode.c +++ b/inode.c @@ -514,6 +514,9 @@ int exfat_block_truncate_page(struct inode *inode, loff_t from) } static const struct address_space_operations exfat_aops = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) + .set_page_dirty = __set_page_dirty_buffers, +#endif .readpage = exfat_readpage, #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) .readahead = exfat_readahead, From 1270e7479b0c38b5b177965fc5900bb55091c1cd Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Wed, 11 May 2022 15:42:18 +0900 Subject: [PATCH 008/141] exfat: fs: Turn block_invalidatepage into block_invalidate_folio Remove special-casing of a NULL invalidatepage, since there is no more block_invalidatepage. Signed-off-by: Matthew Wilcox (Oracle) Tested-by: Damien Le Moal Acked-by: Damien Le Moal Tested-by: Mike Marshall # orangefs Tested-by: David Howells # afs Signed-off-by: Namjae Jeon --- inode.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inode.c b/inode.c index ca0c752..3efb9a6 100644 --- a/inode.c +++ b/inode.c @@ -516,6 +516,9 @@ int exfat_block_truncate_page(struct inode *inode, loff_t from) static const struct address_space_operations exfat_aops = { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) .set_page_dirty = __set_page_dirty_buffers, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + .invalidate_folio = block_invalidate_folio, #endif .readpage = exfat_readpage, #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) From e145a5f7cc7d6658e494762e825cb9eb6255bf40 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Wed, 11 May 2022 15:44:32 +0900 Subject: [PATCH 009/141] exfat: fs: Convert __set_page_dirty_buffers to block_dirty_folio Convert all callers; mostly this is just changing the aops to point at it, but a few implementations need a little more work. Signed-off-by: Matthew Wilcox (Oracle) Tested-by: Damien Le Moal Acked-by: Damien Le Moal Tested-by: Mike Marshall # orangefs Tested-by: David Howells # afs Signed-off-by: Namjae Jeon --- inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inode.c b/inode.c index 3efb9a6..bd04c16 100644 --- a/inode.c +++ b/inode.c @@ -514,7 +514,9 @@ int exfat_block_truncate_page(struct inode *inode, loff_t from) } static const struct address_space_operations exfat_aops = { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + .dirty_folio = block_dirty_folio, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) .set_page_dirty = __set_page_dirty_buffers, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) From 51f491ef2d19573756b4dd7aa095d0e5d7204ba2 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 11 May 2022 15:50:47 +0900 Subject: [PATCH 010/141] exfat: use EXT4_SUPER_MAGIC in magic.h Signed-off-by: Namjae Jeon --- exfat_fs.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index e5afb4c..13d246c 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -11,9 +11,14 @@ #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) +#include +#else +#define EXFAT_SUPER_MAGIC 0x2011BAB0UL +#endif + #define EXFAT_VERSION "5.14.1" -#define EXFAT_SUPER_MAGIC 0x2011BAB0UL #define EXFAT_ROOT_INO 1 #define EXFAT_CLUSTERS_UNTRACKED (~0u) From 2363111bfc27a7d2d06115d8d4744303d799bfec Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Wed, 11 May 2022 15:55:49 +0900 Subject: [PATCH 011/141] exfat: fs: allocate inode by using alloc_inode_sb() The inode allocation is supposed to use alloc_inode_sb(), so convert kmem_cache_alloc() of all filesystems to alloc_inode_sb(). Link: https://lkml.kernel.org/r/20220228122126.37293-5-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Theodore Ts'o [ext4] Acked-by: Roman Gushchin Cc: Alex Shi Cc: Anna Schumaker Cc: Chao Yu Cc: Dave Chinner Cc: Fam Zheng Cc: Jaegeuk Kim Cc: Johannes Weiner Cc: Kari Argillander Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Qi Zheng Cc: Shakeel Butt Cc: Trond Myklebust Cc: Vladimir Davydov Cc: Vlastimil Babka Cc: Wei Yang Cc: Xiongchun Duan Cc: Yang Shi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds Signed-off-by: Namjae Jeon --- super.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/super.c b/super.c index ec9de28..91e4dc7 100644 --- a/super.c +++ b/super.c @@ -202,7 +202,11 @@ static struct inode *exfat_alloc_inode(struct super_block *sb) { struct exfat_inode_info *ei; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + ei = alloc_inode_sb(sb, exfat_inode_cachep, GFP_NOFS); +#else ei = kmem_cache_alloc(exfat_inode_cachep, GFP_NOFS); +#endif if (!ei) return NULL; From fa4f895f67ce805163603d0cbf684df71319c303 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 14 May 2022 08:25:19 +0900 Subject: [PATCH 012/141] exfat: fix write failure on kernels prior to 4.8. rw flags agrument in write_dirty_buffer() and __sync_dirty_buffer() was changed since linux 4.8 kernels. Reported-by: kuehnelth Signed-off-by: Namjae Jeon --- misc.c | 4 ++++ super.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/misc.c b/misc.c index c03a586..1b75c8b 100644 --- a/misc.c +++ b/misc.c @@ -224,7 +224,11 @@ int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync) set_buffer_uptodate(bhs[i]); mark_buffer_dirty(bhs[i]); if (sync) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) write_dirty_buffer(bhs[i], REQ_SYNC); +#else + write_dirty_buffer(bhs[i], WRITE_SYNC); +#endif } for (i = 0; i < nr_bhs && sync; i++) { diff --git a/super.c b/super.c index 91e4dc7..6837e04 100644 --- a/super.c +++ b/super.c @@ -141,7 +141,7 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) __sync_dirty_buffer(sbi->boot_bh, REQ_SYNC | REQ_FUA | REQ_PREFLUSH); #else - __sync_dirty_buffer(sbi->boot_bh, REQ_SYNC | REQ_FUA | REQ_FLUSH); + __sync_dirty_buffer(sbi->boot_bh, WRITE_FLUSH_FUA); #endif return 0; From 54607fbd7010af58b5339b65a90d6305895568d4 Mon Sep 17 00:00:00 2001 From: Tadeusz Struk Date: Mon, 16 May 2022 11:44:03 +0900 Subject: [PATCH 013/141] exfat: check if cluster num is valid Syzbot reported slab-out-of-bounds read in exfat_clear_bitmap. This was triggered by reproducer calling truncute with size 0, which causes the following trace: BUG: KASAN: slab-out-of-bounds in exfat_clear_bitmap+0x147/0x490 fs/exfat/balloc.c:174 Read of size 8 at addr ffff888115aa9508 by task syz-executor251/365 Call Trace: __dump_stack lib/dump_stack.c:77 [inline] dump_stack_lvl+0x1e2/0x24b lib/dump_stack.c:118 print_address_description+0x81/0x3c0 mm/kasan/report.c:233 __kasan_report mm/kasan/report.c:419 [inline] kasan_report+0x1a4/0x1f0 mm/kasan/report.c:436 __asan_report_load8_noabort+0x14/0x20 mm/kasan/report_generic.c:309 exfat_clear_bitmap+0x147/0x490 fs/exfat/balloc.c:174 exfat_free_cluster+0x25a/0x4a0 fs/exfat/fatent.c:181 __exfat_truncate+0x99e/0xe00 fs/exfat/file.c:217 exfat_truncate+0x11b/0x4f0 fs/exfat/file.c:243 exfat_setattr+0xa03/0xd40 fs/exfat/file.c:339 notify_change+0xb76/0xe10 fs/attr.c:336 do_truncate+0x1ea/0x2d0 fs/open.c:65 Move the is_valid_cluster() helper from fatent.c to a common header to make it reusable in other *.c files. And add is_valid_cluster() to validate if cluster number is within valid range in exfat_clear_bitmap() and exfat_set_bitmap(). Link: https://syzkaller.appspot.com/bug?id=50381fc73821ecae743b8cf24b4c9a04776f767c Reported-by: syzbot+a4087e40b9c13aad7892@syzkaller.appspotmail.com Fixes: 1e49a94cf707 ("exfat: add bitmap operations") Cc: stable@vger.kernel.org # v5.7+ Signed-off-by: Tadeusz Struk Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- balloc.c | 8 ++++++-- exfat_fs.h | 6 ++++++ fatent.c | 6 ------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/balloc.c b/balloc.c index 97244d8..4c2766c 100644 --- a/balloc.c +++ b/balloc.c @@ -152,7 +152,9 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync) struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); - WARN_ON(clu < EXFAT_FIRST_CLUSTER); + if (!is_valid_cluster(sbi, clu)) + return -EINVAL; + ent_idx = CLUSTER_TO_BITMAP_ENT(clu); i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); @@ -170,7 +172,9 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync) struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_mount_options *opts = &sbi->options; - WARN_ON(clu < EXFAT_FIRST_CLUSTER); + if (!is_valid_cluster(sbi, clu)) + return; + ent_idx = CLUSTER_TO_BITMAP_ENT(clu); i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); diff --git a/exfat_fs.h b/exfat_fs.h index 13d246c..d974111 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -401,6 +401,12 @@ static inline int exfat_sector_to_cluster(struct exfat_sb_info *sbi, EXFAT_RESERVED_CLUSTERS; } +static inline bool is_valid_cluster(struct exfat_sb_info *sbi, + unsigned int clus) +{ + return clus >= EXFAT_FIRST_CLUSTER && clus < sbi->num_clusters; +} + /* super.c */ int exfat_set_volume_dirty(struct super_block *sb); int exfat_clear_volume_dirty(struct super_block *sb); diff --git a/fatent.c b/fatent.c index 61fbf9f..46eafba 100644 --- a/fatent.c +++ b/fatent.c @@ -90,12 +90,6 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, return 0; } -static inline bool is_valid_cluster(struct exfat_sb_info *sbi, - unsigned int clus) -{ - return clus >= EXFAT_FIRST_CLUSTER && clus < sbi->num_clusters; -} - int exfat_ent_get(struct super_block *sb, unsigned int loc, unsigned int *content) { From 8d7603af6585768e4c8d7a95bd518df77093321c Mon Sep 17 00:00:00 2001 From: Tianling Shen Date: Mon, 28 Feb 2022 14:20:29 +0800 Subject: [PATCH 014/141] exfat: file: fix build with kernel < 4.4.72 `i_blocksize` did not exist on kernel 4.4 until 4.4.72 was released [1]. 1. https://lwn.net/Articles/725371/ Signed-off-by: Tianling Shen Signed-off-by: Namjae Jeon --- file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file.c b/file.c index 1b28930..ebea67c 100644 --- a/file.c +++ b/file.c @@ -241,7 +241,7 @@ void exfat_truncate(struct inode *inode, loff_t size) struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 72) unsigned int blocksize = i_blocksize(inode); #else unsigned int blocksize = 1 << inode->i_blkbits; From e7fd35517c7c06be31edf5992a19f8da55c1b8e9 Mon Sep 17 00:00:00 2001 From: Chukun Pan Date: Wed, 18 May 2022 13:42:36 +0900 Subject: [PATCH 015/141] exfat: further fix 'fatal_signal_pending' undeclared Make sure to include the header file that defines this function. Fixes: 19b7087 ("exfat: fix build on x86 with 4.19 kernel") Signed-off-by: Chukun Pan Signed-off-by: Namjae Jeon --- balloc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/balloc.c b/balloc.c index 4c2766c..30c2414 100644 --- a/balloc.c +++ b/balloc.c @@ -7,8 +7,10 @@ #include #include #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include +#else +#include #endif #include "exfat_raw.h" From 08b714e704c8f919509e44a07e78db5ab9e43f65 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 18 May 2022 18:08:00 +0900 Subject: [PATCH 016/141] exfat: fix uninitialized return value on kernel < 4.16 Reported-by: kuehnelth Signed-off-by: Namjae Jeon --- namei.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namei.c b/namei.c index 070e4a2..cae4ec4 100644 --- a/namei.c +++ b/namei.c @@ -36,7 +36,7 @@ static inline void exfat_d_version_set(struct dentry *dentry, */ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags) { - int ret; + int ret = 1; if (flags & LOOKUP_RCU) return -ECHILD; From 2a594d44a08a2e4e341b543ff58402d027b2b086 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 19 May 2022 22:40:35 +0900 Subject: [PATCH 017/141] exfat: directly call filemap_write_and_wait_range() on < 5.19 kernel sync_blockdev_range will be added to linux 5.19 kernel. So, do directly call filemap_write_and_wait_range() instead of sync_blockdev_range() on lower kernel version than 5.19 kernel. Signed-off-by: Namjae Jeon --- fatent.c | 52 +++++----------------------------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/fatent.c b/fatent.c index 46eafba..b247cbd 100644 --- a/fatent.c +++ b/fatent.c @@ -273,7 +273,6 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, return 0; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) { struct super_block *sb = dir->i_sb; @@ -306,60 +305,19 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) } if (IS_DIRSYNC(dir)) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) return sync_blockdev_range(sb->s_bdev, EXFAT_BLK_TO_B(blknr, sb), EXFAT_BLK_TO_B(last_blknr, sb) - 1); - - return 0; -} #else -int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) -{ - struct super_block *sb = dir->i_sb; - struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct buffer_head *bhs[MAX_BUF_PER_PAGE]; - int nr_bhs = MAX_BUF_PER_PAGE; - sector_t blknr, last_blknr; - int err, i, n; - - blknr = exfat_cluster_to_sector(sbi, clu); - last_blknr = blknr + sbi->sect_per_clus; - - if (last_blknr > sbi->num_sectors && sbi->num_sectors > 0) { - exfat_fs_error_ratelimit(sb, - "%s: out of range(sect:%llu len:%u)", - __func__, (unsigned long long)blknr, - sbi->sect_per_clus); - return -EIO; - } - - /* Zeroing the unused blocks on this cluster */ - while (blknr < last_blknr) { - for (n = 0; n < nr_bhs && blknr < last_blknr; n++, blknr++) { - bhs[n] = sb_getblk(sb, blknr); - if (!bhs[n]) { - err = -ENOMEM; - goto release_bhs; - } - memset(bhs[n]->b_data, 0, sb->s_blocksize); - } + return filemap_write_and_wait_range(sb->s_bdev->bd_inode->i_mapping, + EXFAT_BLK_TO_B(blknr, sb), + EXFAT_BLK_TO_B(last_blknr, sb) - 1); +#endif - err = exfat_update_bhs(bhs, n, IS_DIRSYNC(dir)); - if (err) - goto release_bhs; - for (i = 0; i < n; i++) - brelse(bhs[i]); - } return 0; - -release_bhs: - exfat_err(sb, "failed zeroed sect %llu\n", (unsigned long long)blknr); - for (i = 0; i < n; i++) - bforget(bhs[i]); - return err; } -#endif int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, struct exfat_chain *p_chain, bool sync_bmap) From 89b59ca325ad5f7aa14b2faaea9cc1a0d99f6bbe Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 3 Jun 2022 23:25:14 +0900 Subject: [PATCH 018/141] exfat: release 5.19.1 version Major changes are: - fix referencing wrong parent directory information during rename. - introduce a sys_tz mount option to use system timezone. - improve performance while zeroing a cluster with dirsync mount option. - fix slab-out-bounds in exat_clear_bitmap() reported from syzbot. - Add keep_last_dots mount option to allow access to paths with trailing dots. - Avoid repetitive volume dirty bit set/clear to improve storage life time. - Fix ->i_blocks truncation issue that still exists elsewhere. - 4 cleanups & typos fixes. - Move super block magic number to magic.h - Fix missing REQ_SYNC in exfat_update_bhs(). - Fix ->i_blocks truncation issue caused by wrong 32bit mask. Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index d974111..6b88270 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -17,7 +17,7 @@ #define EXFAT_SUPER_MAGIC 0x2011BAB0UL #endif -#define EXFAT_VERSION "5.14.1" +#define EXFAT_VERSION "5.19.1" #define EXFAT_ROOT_INO 1 From c509458c2757e8f3d42be269b9b0f1b236d31572 Mon Sep 17 00:00:00 2001 From: Sungjong Seo Date: Wed, 8 Jun 2022 06:32:03 +0900 Subject: [PATCH 019/141] exfat: use updated exfat_chain directly during renaming In order for a file to access its own directory entry set, exfat_inode_info(ei) has two copied values. One is ei->dir, which is a snapshot of exfat_chain of the parent directory, and the other is ei->entry, which is the offset of the start of the directory entry set in the parent directory. Since the parent directory can be updated after the snapshot point, it should be used only for accessing one's own directory entry set. However, as of now, during renaming, it could try to traverse or to allocate clusters via snapshot values, it does not make sense. This potential problem has been revealed when exfat_update_parent_info() was removed by commit d8dad2588add ("exfat: fix referencing wrong parent directory information after renaming"). However, I don't think it's good idea to bring exfat_update_parent_info() back. Instead, let's use the updated exfat_chain of parent directory diectly. Signed-off-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/namei.c b/namei.c index cae4ec4..523da22 100644 --- a/namei.c +++ b/namei.c @@ -1314,7 +1314,9 @@ static int __exfat_rename(struct inode *old_parent_inode, return -ENOENT; } - exfat_chain_dup(&olddir, &ei->dir); + exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu, + EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi), + EXFAT_I(old_parent_inode)->flags); dentry = ei->entry; ep = exfat_get_dentry(sb, &olddir, dentry, &old_bh); From 56a5afdffa559f6d095a24a1f502dea481680dcd Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 21 Jul 2022 15:31:14 +0900 Subject: [PATCH 020/141] exfat: reuse __exfat_write_inode() to update directory entry __exfat_write_inode() is used to update file and stream directory entries, except for file->start_clu and stream->flags. This commit moves update file->start_clu and stream->flags to __exfat_write_inode() and reuse __exfat_write_inode() to update directory entries. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Signed-off-by: Namjae Jeon --- exfat_fs.h | 1 + file.c | 53 ++++------------------------------------------------- inode.c | 37 +++++++++++-------------------------- namei.c | 24 ++++-------------------- 4 files changed, 20 insertions(+), 95 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 6b88270..e419db0 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -515,6 +515,7 @@ struct inode *exfat_build_inode(struct super_block *sb, void exfat_hash_inode(struct inode *inode, loff_t i_pos); void exfat_unhash_inode(struct inode *inode); struct inode *exfat_iget(struct super_block *sb, loff_t i_pos); +int __exfat_write_inode(struct inode *inode, int sync); int exfat_write_inode(struct inode *inode, struct writeback_control *wbc); void exfat_evict_inode(struct inode *inode); int exfat_block_truncate_page(struct inode *inode, loff_t from); diff --git a/file.c b/file.c index ebea67c..ad6838a 100644 --- a/file.c +++ b/file.c @@ -108,7 +108,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) 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) @@ -157,57 +156,13 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) ei->attr |= ATTR_ARCHIVE; /* update the directory entry */ - if (!evict) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) - struct timespec64 ts; -#else - struct timespec ts; -#endif - struct exfat_dentry *ep, *ep2; - struct exfat_entry_set_cache *es; - int err; - - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, - ES_ALL_ENTRIES); - if (!es) - return -EIO; - ep = exfat_get_dentry_cached(es, 0); - ep2 = exfat_get_dentry_cached(es, 1); - #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - ts = current_time(inode); + inode->i_mtime = current_time(inode); #else - ts = CURRENT_TIME_SEC; + inode->i_mtime = CURRENT_TIME_SEC; #endif - exfat_set_entry_time(sbi, &ts, - &ep->dentry.file.modify_tz, - &ep->dentry.file.modify_time, - &ep->dentry.file.modify_date, - &ep->dentry.file.modify_time_cs); - 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) { - ep2->dentry.stream.valid_size = 0; - ep2->dentry.stream.size = 0; - } else { - ep2->dentry.stream.valid_size = cpu_to_le64(new_size); - ep2->dentry.stream.size = ep2->dentry.stream.valid_size; - } - - 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; - } - - exfat_update_dir_chksum_with_entry_set(es); - err = exfat_free_dentry_set(es, inode_needs_sync(inode)); - if (err) - return err; - } + if (__exfat_write_inode(inode, inode_needs_sync(inode))) + return -EIO; /* cut off from the FAT chain */ if (ei->flags == ALLOC_FAT_CHAIN && last_clu != EXFAT_FREE_CLUSTER && diff --git a/inode.c b/inode.c index bd04c16..3c68a89 100644 --- a/inode.c +++ b/inode.c @@ -19,7 +19,7 @@ #include "exfat_raw.h" #include "exfat_fs.h" -static int __exfat_write_inode(struct inode *inode, int sync) +int __exfat_write_inode(struct inode *inode, int sync) { unsigned long long on_disk_size; struct exfat_dentry *ep, *ep2; @@ -77,6 +77,13 @@ static int __exfat_write_inode(struct inode *inode, int sync) ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size); ep2->dentry.stream.size = ep2->dentry.stream.valid_size; + if (on_disk_size) { + ep2->dentry.stream.flags = ei->flags; + ep2->dentry.stream.start_clu = cpu_to_le32(ei->start_clu); + } else { + ep2->dentry.stream.flags = ALLOC_FAT_CHAIN; + ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; + } exfat_update_dir_chksum_with_entry_set(es); return exfat_free_dentry_set(es, sync); @@ -218,32 +225,10 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_clusters += num_to_be_allocated; *clu = new_clu.dir; - if (ei->dir.dir != DIR_DELETED && modified) { - struct exfat_dentry *ep; - struct exfat_entry_set_cache *es; - int err; - - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, - ES_ALL_ENTRIES); - if (!es) + if (modified) { + if (__exfat_write_inode(inode, inode_needs_sync(inode))) return -EIO; - /* get stream entry */ - ep = exfat_get_dentry_cached(es, 1); - - /* update directory entry */ - ep->dentry.stream.flags = ei->flags; - ep->dentry.stream.start_clu = - cpu_to_le32(ei->start_clu); - ep->dentry.stream.valid_size = - cpu_to_le64(i_size_read(inode)); - ep->dentry.stream.size = - ep->dentry.stream.valid_size; - - exfat_update_dir_chksum_with_entry_set(es); - err = exfat_free_dentry_set(es, inode_needs_sync(inode)); - if (err) - return err; - } /* end of if != DIR_DELETED */ + } inode->i_blocks += num_to_be_allocated << sbi->sect_per_clus_bits; diff --git a/namei.c b/namei.c index 523da22..2e99c59 100644 --- a/namei.c +++ b/namei.c @@ -345,7 +345,6 @@ static int exfat_find_empty_entry(struct inode *inode, unsigned int ret, last_clu; loff_t size = 0; struct exfat_chain clu; - struct exfat_dentry *ep = NULL; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); @@ -410,31 +409,16 @@ static int exfat_find_empty_entry(struct inode *inode, p_dir->size++; size = EXFAT_CLU_TO_B(p_dir->size, sbi); - /* update the directory entry */ - if (p_dir->dir != sbi->root_dir) { - struct buffer_head *bh; - - ep = exfat_get_dentry(sb, - &(ei->dir), ei->entry + 1, &bh); - if (!ep) - return -EIO; - - ep->dentry.stream.valid_size = cpu_to_le64(size); - ep->dentry.stream.size = ep->dentry.stream.valid_size; - ep->dentry.stream.flags = p_dir->flags; - exfat_update_bh(bh, IS_DIRSYNC(inode)); - brelse(bh); - if (exfat_update_dir_chksum(inode, &(ei->dir), - ei->entry)) - return -EIO; - } - /* directory inode should be updated in here */ i_size_write(inode, size); ei->i_size_ondisk += sbi->cluster_size; ei->i_size_aligned += sbi->cluster_size; ei->flags = p_dir->flags; inode->i_blocks += 1 << sbi->sect_per_clus_bits; + + /* update the directory entry */ + if (__exfat_write_inode(inode, IS_DIRSYNC(inode))) + return -EIO; } return dentry; From 63e1c4f7c9c657c1628d8a9e164efcbb787dfdc2 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 22 Jul 2022 11:43:04 +0900 Subject: [PATCH 021/141] exfat: remove duplicate write inode for truncating file This commit moves updating file attributes and timestamps before calling __exfat_write_inode(), so that all updates of the inode had been written by __exfat_write_inode(), mark_inode_dirty() is unneeded. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Signed-off-by: Namjae Jeon --- file.c | 57 +++++++++++++++++++++++++++++++-------------------------- inode.c | 5 +++++ 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/file.c b/file.c index ad6838a..c03f426 100644 --- a/file.c +++ b/file.c @@ -155,12 +155,17 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) if (ei->type == TYPE_FILE) ei->attr |= ATTR_ARCHIVE; - /* update the directory entry */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_mtime = current_time(inode); -#else - inode->i_mtime = CURRENT_TIME_SEC; -#endif + /* + * update the directory entry + * + * If the directory entry is updated by mark_inode_dirty(), the + * directory entry will be written after a writeback cycle of + * updating the bitmap/FAT, which may result in clusters being + * freed but referenced by the directory entry in the event of a + * sudden power failure. + * __exfat_write_inode() is called for directory entry, bitmap + * and FAT to be written in a same writeback. + */ if (__exfat_write_inode(inode, inode_needs_sync(inode))) return -EIO; @@ -217,16 +222,6 @@ void exfat_truncate(struct inode *inode, loff_t size) if (err) goto write_size; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_ctime = inode->i_mtime = current_time(inode); -#else - inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; -#endif - if (IS_DIRSYNC(inode)) - exfat_sync_inode(inode); - else - mark_inode_dirty(inode); - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; write_size: @@ -342,6 +337,20 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_valid &= ~ATTR_MODE; } + if (attr->ia_valid & ATTR_SIZE) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + inode->i_mtime = inode->i_ctime = current_time(inode); +#else + inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + setattr_copy(&init_user_ns, inode, attr); +#else + setattr_copy(inode, attr); +#endif + exfat_truncate_atime(&inode->i_atime); + if (attr->ia_valid & ATTR_SIZE) { error = exfat_block_truncate_page(inode, attr->ia_size); if (error) @@ -349,18 +358,14 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) down_write(&EXFAT_I(inode)->truncate_lock); truncate_setsize(inode, attr->ia_size); + /* + * __exfat_write_inode() is called from exfat_truncate(), inode + * is already written by it, so mark_inode_dirty() is unneeded. + */ exfat_truncate(inode, attr->ia_size); up_write(&EXFAT_I(inode)->truncate_lock); - } - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) - setattr_copy(&init_user_ns, inode, attr); -#else - setattr_copy(inode, attr); -#endif - exfat_truncate_atime(&inode->i_atime); - mark_inode_dirty(inode); - + } else + mark_inode_dirty(inode); out: return error; } diff --git a/inode.c b/inode.c index 3c68a89..8cbc498 100644 --- a/inode.c +++ b/inode.c @@ -379,6 +379,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) if (to > i_size_read(inode)) { truncate_pagecache(inode, i_size_read(inode)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + inode->i_mtime = inode->i_ctime = current_time(inode); +#else + inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; +#endif exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); } } From 8d627e30015823501705a9b4e130a22037f2328f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 21 Jul 2022 10:38:21 +0900 Subject: [PATCH 022/141] exfat: remove duplicate write inode for extending dir/file Since the timestamps need to be updated, the directory entries will be updated by mark_inode_dirty() whether or not a new cluster is allocated for the file or directory, so there is no need to use __exfat_write_inode() to update the directory entries when allocating a new cluster for a file or directory. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Signed-off-by: Namjae Jeon --- inode.c | 9 +-------- namei.c | 4 ---- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/inode.c b/inode.c index 8cbc498..0a1bd5f 100644 --- a/inode.c +++ b/inode.c @@ -114,7 +114,7 @@ void exfat_sync_inode(struct inode *inode) static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, unsigned int *clu, int create) { - int ret, modified = false; + int ret; unsigned int last_clu; struct exfat_chain new_clu; struct super_block *sb = inode->i_sb; @@ -205,7 +205,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, if (new_clu.flags == ALLOC_FAT_CHAIN) ei->flags = ALLOC_FAT_CHAIN; ei->start_clu = new_clu.dir; - modified = true; } else { if (new_clu.flags != ei->flags) { /* no-fat-chain bit is disabled, @@ -215,7 +214,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, exfat_chain_cont_cluster(sb, ei->start_clu, num_clusters); ei->flags = ALLOC_FAT_CHAIN; - modified = true; } if (new_clu.flags == ALLOC_FAT_CHAIN) if (exfat_ent_set(sb, last_clu, new_clu.dir)) @@ -225,11 +223,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_clusters += num_to_be_allocated; *clu = new_clu.dir; - if (modified) { - if (__exfat_write_inode(inode, inode_needs_sync(inode))) - return -EIO; - } - inode->i_blocks += num_to_be_allocated << sbi->sect_per_clus_bits; diff --git a/namei.c b/namei.c index 2e99c59..c9482b8 100644 --- a/namei.c +++ b/namei.c @@ -415,10 +415,6 @@ static int exfat_find_empty_entry(struct inode *inode, ei->i_size_aligned += sbi->cluster_size; ei->flags = p_dir->flags; inode->i_blocks += 1 << sbi->sect_per_clus_bits; - - /* update the directory entry */ - if (__exfat_write_inode(inode, IS_DIRSYNC(inode))) - return -EIO; } return dentry; From dda72dc5365138180ee7fa16b4a31b9fa460f3d0 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 21 Jul 2022 11:28:46 +0900 Subject: [PATCH 023/141] block: remove QUEUE_FLAG_DISCARD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just use a non-zero max_discard_sectors as an indicator for discard support, similar to what is done for write zeroes. The only places where needs special attention is the RAID5 driver, which must clear discard support for security reasons by default, even if the default stacking rules would allow for it. Signed-off-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Acked-by: Christoph Böhmwalder [drbd] Acked-by: Jan Höppner [s390] Acked-by: Coly Li [bcache] Acked-by: David Sterba [btrfs] Reviewed-by: Chaitanya Kulkarni Link: https://lore.kernel.org/r/20220415045258.199825-25-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- file.c | 4 ++++ super.c | 22 ++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/file.c b/file.c index c03f426..7ccd3fd 100644 --- a/file.c +++ b/file.c @@ -379,7 +379,11 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) if (!capable(CAP_SYS_ADMIN)) return -EPERM; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + if (!bdev_max_discard_sectors(inode->i_sb->s_bdev)) +#else if (!blk_queue_discard(q)) +#endif return -EOPNOTSUPP; if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range))) diff --git a/super.c b/super.c index 6837e04..f672249 100644 --- a/super.c +++ b/super.c @@ -559,14 +559,21 @@ static int parse_options(struct super_block *sb, char *options, int silent, if (opts->allow_utime == -1) opts->allow_utime = ~opts->fs_dmask & (0022); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) { + exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } +#else if (opts->discard) { struct request_queue *q = bdev_get_queue(sb->s_bdev); - if (!blk_queue_discard(q)) - exfat_msg(sb, KERN_WARNING, - "mounting with \"discard\" option, but the device does not support discard"); - opts->discard = 0; + if (!blk_queue_discard(q)) { + exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } } +#endif return 0; } @@ -896,6 +903,12 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) if (opts->allow_utime == (unsigned short)-1) opts->allow_utime = ~opts->fs_dmask & 0022; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) { + exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } +#else if (opts->discard) { struct request_queue *q = bdev_get_queue(sb->s_bdev); @@ -904,6 +917,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) opts->discard = 0; } } +#endif #else struct exfat_sb_info *sbi; From d196c9a44bb77c3808ca665968ec032f6f8fd6c6 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 21 Jul 2022 11:40:30 +0900 Subject: [PATCH 024/141] block: add a bdev_discard_granularity helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Abstract away implementation details from file systems by providing a block_device based helper to retrieve the discard granularity. Signed-off-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Acked-by: Christoph Böhmwalder [drbd] Acked-by: Ryusuke Konishi Acked-by: David Sterba [btrfs] Link: https://lore.kernel.org/r/20220415045258.199825-26-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- file.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/file.c b/file.c index 7ccd3fd..7137351 100644 --- a/file.c +++ b/file.c @@ -372,7 +372,9 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev); +#endif struct fstrim_range range; int ret = 0; @@ -389,8 +391,13 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range))) return -EFAULT; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + range.minlen = max_t(unsigned int, range.minlen, + bdev_discard_granularity(inode->i_sb->s_bdev)); +#else range.minlen = max_t(unsigned int, range.minlen, q->limits.discard_granularity); +#endif ret = exfat_trim_fs(inode, &range); if (ret < 0) From ff027ab15c2615ff13876527e35fd08d562f0891 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 21 Jul 2022 11:43:06 +0900 Subject: [PATCH 025/141] fs: Remove aop flags parameter from cont_write_begin() There are no more aop flags left, so remove the parameter. Signed-off-by: Matthew Wilcox (Oracle) Reviewed-by: Christoph Hellwig Signed-off-by: Namjae Jeon --- inode.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/inode.c b/inode.c index 0a1bd5f..2a50a43 100644 --- a/inode.c +++ b/inode.c @@ -388,10 +388,15 @@ static int exfat_write_begin(struct file *file, struct address_space *mapping, int ret; *pagep = NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + ret = cont_write_begin(file, mapping, pos, len, pagep, fsdata, + exfat_get_block, + &EXFAT_I(mapping->host)->i_size_ondisk); +#else ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata, exfat_get_block, &EXFAT_I(mapping->host)->i_size_ondisk); - +#endif if (ret < 0) exfat_write_failed(mapping, pos+len); From 7bcf79cf5a71426ac950df80ab39162d78c44c22 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 21 Jul 2022 11:44:50 +0900 Subject: [PATCH 026/141] fs: Remove flags parameter from aops->write_begin There are no more aop flags left, so remove the parameter. Signed-off-by: Matthew Wilcox (Oracle) Reviewed-by: Christoph Hellwig Signed-off-by: Namjae Jeon --- inode.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/inode.c b/inode.c index 2a50a43..4acf0aa 100644 --- a/inode.c +++ b/inode.c @@ -381,9 +381,15 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) } } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) +static int exfat_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, + struct page **pagep, void **fsdata) +#else static int exfat_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, struct page **pagep, void **fsdata) +#endif { int ret; From c9d13ea12aecbf43c2d7de8444c882cf55327064 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 21 Jul 2022 12:00:32 +0900 Subject: [PATCH 027/141] fs: Convert mpage_readpage to mpage_read_folio mpage_readpage still works in terms of pages, and has not been audited for correctness with large folios, so include an assertion that the filesystem is not passing it large folios. Convert all the filesystems to call mpage_read_folio() instead of mpage_readpage(). Signed-off-by: Matthew Wilcox (Oracle) Signed-off-by: Namjae Jeon --- inode.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/inode.c b/inode.c index 4acf0aa..28692a0 100644 --- a/inode.c +++ b/inode.c @@ -337,10 +337,17 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, return err; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) +static int exfat_read_folio(struct file *file, struct folio *folio) +{ + return mpage_read_folio(folio, exfat_get_block); +} +#else static int exfat_readpage(struct file *file, struct page *page) { return mpage_readpage(page, exfat_get_block); } +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) static void exfat_readahead(struct readahead_control *rac) @@ -516,7 +523,11 @@ static const struct address_space_operations exfat_aops = { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) .invalidate_folio = block_invalidate_folio, #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + .read_folio = exfat_read_folio, +#else .readpage = exfat_readpage, +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) .readahead = exfat_readahead, #else From 5c189709c987cf505ae2aabdfbfa93d796444c01 Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Thu, 21 Jul 2022 12:08:05 +0900 Subject: [PATCH 028/141] exfat: simplified by using round_up() Signed-off-by: Christophe Vu-Brugier Signed-off-by: Namjae Jeon --- file.c | 4 ++-- inode.c | 4 ++-- super.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/file.c b/file.c index 7137351..6e37277 100644 --- a/file.c +++ b/file.c @@ -222,8 +222,8 @@ void exfat_truncate(struct inode *inode, loff_t size) if (err) goto write_size; - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> + inode->i_blkbits; write_size: aligned_size = i_size_read(inode); if (aligned_size & (blocksize - 1)) { diff --git a/inode.c b/inode.c index 28692a0..f3dbeb3 100644 --- a/inode.c +++ b/inode.c @@ -646,8 +646,8 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) exfat_save_attr(inode, info->attr); - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> + inode->i_blkbits; inode->i_mtime = info->mtime; inode->i_ctime = info->mtime; ei->i_crtime = info->crtime; diff --git a/super.c b/super.c index f672249..cd6a54d 100644 --- a/super.c +++ b/super.c @@ -632,8 +632,8 @@ static int exfat_read_root(struct inode *inode) inode->i_op = &exfat_dir_inode_operations; inode->i_fop = &exfat_dir_operations; - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> + inode->i_blkbits; ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; ei->i_size_aligned = i_size_read(inode); ei->i_size_ondisk = i_size_read(inode); From 3cc661ccd509c9202ebee3781b69c03aef681172 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:19:13 +0900 Subject: [PATCH 029/141] exfat: Return ENAMETOOLONG consistently for oversized paths LTP has a test for oversized file path renames and it expects the return value to be ENAMETOOLONG. However, exfat returns EINVAL unexpectedly in some cases, hence LTP test fails. The further investigation indicated that the problem happens only when iocharset isn't set to utf8. The difference comes from that, in the case of utf8, exfat_utf8_to_utf16() returns the error -ENAMETOOLONG directly and it's treated as the final error code. Meanwhile, on other iocharsets, exfat_nls_to_ucs2() returns the max path size but it sets NLS_NAME_OVERLEN to lossy flag instead; the caller side checks only whether lossy flag is set or not, resulting in always -EINVAL unconditionally. This patch aligns the return code for both cases by checking the lossy flag bit and returning ENAMETOOLONG when NLS_NAME_OVERLEN bit is set. BugLink: https://bugzilla.suse.com/show_bug.cgi?id=1201725 Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- namei.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namei.c b/namei.c index c9482b8..3342f29 100644 --- a/namei.c +++ b/namei.c @@ -469,7 +469,7 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, return namelen; /* return error value */ if ((lossy && !lookup) || !namelen) - return -EINVAL; + return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL; exfat_chain_set(p_dir, ei->start_clu, EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); From 9a903a2731484d34d65d89c593acf27c05761cc3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:19:53 +0900 Subject: [PATCH 030/141] exfat: Define NLS_NAME_* as bit flags explicitly NLS_NAME_* are bit flags although they are currently defined as enum; it's casually working so far (from 0 to 2), but it's error-prone and may bring a problem when we want to add more flag. This patch changes the definitions of NLS_NAME_* explicitly being bit flags. Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- exfat_fs.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index e419db0..90d42a2 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -36,9 +36,9 @@ enum exfat_error_mode { * exfat nls lossy flag */ enum { - NLS_NAME_NO_LOSSY, /* no lossy */ - NLS_NAME_LOSSY, /* just detected incorrect filename(s) */ - NLS_NAME_OVERLEN, /* the length is over than its limit */ + NLS_NAME_NO_LOSSY = 0, /* no lossy */ + NLS_NAME_LOSSY = 1 << 0, /* just detected incorrect filename(s) */ + NLS_NAME_OVERLEN = 1 << 1, /* the length is over than its limit */ }; #define EXFAT_HASH_BITS 8 From 0d2005c28d581fe8f07af3577015baf9c1da6e1b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 17:24:47 +0900 Subject: [PATCH 031/141] exfat: Expand exfat_err() and co directly to pr_*() macro Currently the error and info messages handled by exfat_err() and co are tossed to exfat_msg() function that does nothing but passes the strings with printk() invocation. Not only that this is more overhead by the indirect calls, but also this makes harder to extend for the debug print usage; because of the direct printk() call, you cannot make it for dynamic debug or without debug like the standard helpers such as pr_debug() or dev_dbg(). For addressing the problem, this patch replaces exfat_*() macro to expand to pr_*() directly. Along with it, add the new exfat_debug() macro that is expanded to pr_debug() (which output can be gracefully suppressed via dyndbg). Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- exfat_fs.h | 12 +++++++----- misc.c | 17 ----------------- super.c | 8 ++++---- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 90d42a2..f3b001c 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -541,14 +541,16 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) #define exfat_fs_error_ratelimit(sb, fmt, args...) \ __exfat_fs_error(sb, __ratelimit(&EXFAT_SB(sb)->ratelimit), \ fmt, ## args) -void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) - __printf(3, 4) __cold; + +/* expand to pr_*() with prefix */ #define exfat_err(sb, fmt, ...) \ - exfat_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__) + pr_err("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) #define exfat_warn(sb, fmt, ...) \ - exfat_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__) + pr_warn("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) #define exfat_info(sb, fmt, ...) \ - exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) + pr_info("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) +#define exfat_debug(sb, fmt, ...) \ + pr_debug("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, diff --git a/misc.c b/misc.c index 1b75c8b..e7ae6f6 100644 --- a/misc.c +++ b/misc.c @@ -52,23 +52,6 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) } } -/* - * exfat_msg() - print preformated EXFAT specific messages. - * All logs except what uses exfat_fs_error() should be written by exfat_msg() - */ -void exfat_msg(struct super_block *sb, const char *level, const char *fmt, ...) -{ - struct va_format vaf; - va_list args; - - va_start(args, fmt); - vaf.fmt = fmt; - vaf.va = &args; - /* level means KERN_ pacility level */ - printk("%sexFAT-fs (%s): %pV\n", level, sb->s_id, &vaf); - va_end(args); -} - #define SECS_PER_MIN (60) #define TIMEZONE_SEC(x) ((x) * 15 * SECS_PER_MIN) diff --git a/super.c b/super.c index cd6a54d..f8bce05 100644 --- a/super.c +++ b/super.c @@ -547,9 +547,9 @@ static int parse_options(struct super_block *sb, char *options, int silent, break; default: if (!silent) { - exfat_msg(sb, KERN_ERR, - "unrecognized mount option \"%s\" or missing value", - p); + exfat_err(sb, + "unrecognized mount option \"%s\" or missing value", + p); } return -EINVAL; } @@ -938,7 +938,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) DEFAULT_RATELIMIT_BURST); err = parse_options(sb, data, silent, &sbi->options); if (err) { - exfat_msg(sb, KERN_ERR, "failed to parse options"); + exfat_err(sb, "failed to parse options"); goto check_nls_io; } #endif From 33e7a40648bc7c2cbd4e6ba1d773daf8bd5d464c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:20:59 +0900 Subject: [PATCH 032/141] exfat: Downgrade ENAMETOOLONG error message to debug messages The ENAMETOOLONG error message is printed at each time when user tries to operate with a too long name, and this can flood the kernel logs easily, as every user can trigger this. Let's downgrade this error message level to a debug message for suppressing the superfluous logs. BugLink: https://bugzilla.suse.com/show_bug.cgi?id=1201725 Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- nls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nls.c b/nls.c index ef74f16..5efd2b1 100644 --- a/nls.c +++ b/nls.c @@ -513,7 +513,7 @@ static int exfat_utf8_to_utf16(struct super_block *sb, } if (unilen > MAX_NAME_LENGTH) { - exfat_err(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", + exfat_debug(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", __func__, len, unilen, MAX_NAME_LENGTH); return -ENAMETOOLONG; } From a129bcc6453a78d2d878096ac26ebc4e86238f0b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:21:39 +0900 Subject: [PATCH 033/141] exfat: Drop superfluous new line for error messages exfat_err() adds the new line at the end of the message by itself, hence the passed string shouldn't contain a new line. Drop the superfluous newline letters in the error messages in a few places that have been put mistakenly. Reported-by: Joe Perches Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- fatent.c | 2 +- nls.c | 2 +- super.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fatent.c b/fatent.c index b247cbd..8c073c3 100644 --- a/fatent.c +++ b/fatent.c @@ -346,7 +346,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, /* find new cluster */ if (hint_clu == EXFAT_EOF_CLUSTER) { if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) { - exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)\n", + exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)", sbi->clu_srch_ptr); sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; } diff --git a/nls.c b/nls.c index 5efd2b1..3b9fd46 100644 --- a/nls.c +++ b/nls.c @@ -679,7 +679,7 @@ static int exfat_load_upcase_table(struct super_block *sb, bh = sb_bread(sb, sector); if (!bh) { - exfat_err(sb, "failed to read sector(0x%llx)\n", + exfat_err(sb, "failed to read sector(0x%llx)", (unsigned long long)sector); ret = -EIO; goto free_table; diff --git a/super.c b/super.c index f8bce05..c5d55b0 100644 --- a/super.c +++ b/super.c @@ -728,7 +728,7 @@ static int exfat_read_boot_sector(struct super_block *sb) */ if (p_boot->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS || p_boot->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) { - exfat_err(sb, "bogus sector size bits : %u\n", + exfat_err(sb, "bogus sector size bits : %u", p_boot->sect_size_bits); return -EINVAL; } @@ -737,7 +737,7 @@ static int exfat_read_boot_sector(struct super_block *sb) * sect_per_clus_bits could be at least 0 and at most 25 - sect_size_bits. */ if (p_boot->sect_per_clus_bits > EXFAT_MAX_SECT_PER_CLUS_BITS(p_boot)) { - exfat_err(sb, "bogus sectors bits per cluster : %u\n", + exfat_err(sb, "bogus sectors bits per cluster : %u", p_boot->sect_per_clus_bits); return -EINVAL; } From d7cd524f7fb71a99dec757bd00920a4522c39ce0 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 29 Jul 2022 17:29:01 +0900 Subject: [PATCH 034/141] exfat: add auto build-test and simple stability test using travis-CI When backporting mainline exfat patch, there are build issue from low kernel version. This patch add auto build-test for exfat with 4.1 kernel and simple tests. Signed-off-by: Namjae Jeon --- .travis.yml | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..04cc4a4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,96 @@ +dist: bionic + +language: c + +notifications: + - email: true + +before_script: + # Download the kernel + - sudo apt-get install libelf-dev wget tar gzip python + - wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz + - tar xf linux-4.1.36.tar.gz + - mv linux-4.1.36 linux-stable + - ./.travis_get_mainline_kernel + - cp ./.travis_cmd_wrapper.pl ~/travis_cmd_wrapper.pl + # Prerequisite for xfstests testing + - sudo apt-get install linux-headers-$(uname -r) + - sudo apt-get install autoconf libtool pkg-config libnl-3-dev libnl-genl-3-dev + - sudo apt-get install xfslibs-dev uuid-dev libtool-bin xfsprogs libgdbm-dev gawk fio attr libattr1-dev libacl1-dev libaio-dev + - git clone --branch=exfat-next https://github.com/exfat-utils/exfat-utils + - git clone https://github.com/namjaejeon/exfat-testsuites + - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + - export PATH=/usr/local/lib:$PATH + - sudo useradd fsgqa + - sudo useradd 123456-fsgqa + +script: + # Copy ksmbd source to kernel + - mv linux-stable ../ + - mv linux ../ + - mkdir ../linux-stable/fs/exfat + - cp -ar * ../linux-stable/fs/exfat/ + - cp -ar * ../linux/fs/exfat/ + + # Compile with 4.1 kernel + - cd ../linux-stable + - yes "" | make oldconfig > /dev/null + - echo 'obj-$(CONFIG_EXFAT_FS) += exfat/' >> fs/Makefile + - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig + - echo 'CONFIG_EXFAT_FS=m' >> .config + - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config + - make -j$((`nproc`+1)) fs/exfat/exfat.ko + + # Compile with latest Torvalds' kernel +# - cd ../linux +# - yes "" | make oldconfig > /dev/null +# - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile +# - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig +# - echo 'CONFIG_EXFAT_FS=m' >> .config +# - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config +# - make -j$((`nproc`+1)) fs/exfat/exfat.ko + + # Run xfstests testsuite + - cd ../linux-exfat-oot + - make > /dev/null + - sudo make install > /dev/null + - sudo modprobe exfat + - cd exfat-utils + - ./autogen.sh > /dev/null + - ./configure > /dev/null + - make -j$((`nproc`+1)) > /dev/null + - sudo make install > /dev/null + - sudo mkdir -p /mnt/scratch + - sudo mkdir -p /mnt/test + - sudo mkdir -p /mnt/full_test + # create file/director test + - truncate -s 10G full_test.img + - sudo losetup /dev/loop22 full_test.img + - sudo mkfs.exfat /dev/loop22 + - sudo mount -t exfat /dev/loop22 /mnt/full_test/ + - cd /mnt/full_test/ + - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - sync + - sudo fsck.exfat /dev/loop22 + - sudo rm -rf * + - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - sync + - sudo rm -rf * + - sudo fsck.exfat /dev/loop22 + - cd - + - sudo umount /mnt/full_test/ + - sudo fsck.exfat /dev/loop22 + # run xfstests test + - truncate -s 100G test.img + - truncate -s 100G scratch.img + - sudo losetup /dev/loop20 test.img + - sudo losetup /dev/loop21 scratch.img + - sudo mkfs.exfat /dev/loop20 + - sudo mkfs.exfat /dev/loop21 + - cd .. + - cd exfat-testsuites/ + - tar xzvf xfstests-exfat.tgz > /dev/null + - cd xfstests-exfat + - make -j$((`nproc`+1)) > /dev/null + - sudo ./check generic/001 + - sudo ./check generic/006 From 03aa12a9fdba0ba94e6c18386d203471a5f23514 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 24 Aug 2022 08:20:34 +0900 Subject: [PATCH 035/141] exfat: fix overflow for large capacity partition Using int type for sector index, there will be overflow in a large capacity partition. For example, if storage with sector size of 512 bytes and partition capacity is larger than 2TB, there will be overflow. Fixes: 1b6138385499 ("exfat: reduce block requests when zeroing a cluster") Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- fatent.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fatent.c b/fatent.c index 8c073c3..ded465b 100644 --- a/fatent.c +++ b/fatent.c @@ -278,8 +278,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) struct super_block *sb = dir->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct buffer_head *bh; - sector_t blknr, last_blknr; - int i; + sector_t blknr, last_blknr, i; blknr = exfat_cluster_to_sector(sbi, clu); last_blknr = blknr + sbi->sect_per_clus; From bed6b023a135dcecd07cfac21ccfa4ef7b3ef7b6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 17 Oct 2022 16:35:31 +0900 Subject: [PATCH 036/141] exfat: release 6.0.0 version This version number is sync with linux mainline. Major changes are: - Use updated exfat_chain directly instead of snapshot values in rename. - fix the error code of rename syscall. - cleanup and suppress the superfluous error messages. - remove duplicate directory entry update. - fix integer overflow on large partition. Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index f3b001c..0db1f25 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -17,7 +17,7 @@ #define EXFAT_SUPER_MAGIC 0x2011BAB0UL #endif -#define EXFAT_VERSION "5.19.1" +#define EXFAT_VERSION "6.0.0" #define EXFAT_ROOT_INO 1 From 9466ec40d94f31159710ef0529cef8247853c0f6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 20 Oct 2022 22:47:32 +0900 Subject: [PATCH 037/141] exfat: remove travis-CI test We replace auto test of travis-CI with github action. Signed-off-by: Namjae Jeon --- .travis.yml | 96 ------------------------------------- .travis_cmd_wrapper.pl | 65 ------------------------- .travis_get_mainline_kernel | 37 -------------- 3 files changed, 198 deletions(-) delete mode 100644 .travis.yml delete mode 100755 .travis_cmd_wrapper.pl delete mode 100755 .travis_get_mainline_kernel diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 04cc4a4..0000000 --- a/.travis.yml +++ /dev/null @@ -1,96 +0,0 @@ -dist: bionic - -language: c - -notifications: - - email: true - -before_script: - # Download the kernel - - sudo apt-get install libelf-dev wget tar gzip python - - wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz - - tar xf linux-4.1.36.tar.gz - - mv linux-4.1.36 linux-stable - - ./.travis_get_mainline_kernel - - cp ./.travis_cmd_wrapper.pl ~/travis_cmd_wrapper.pl - # Prerequisite for xfstests testing - - sudo apt-get install linux-headers-$(uname -r) - - sudo apt-get install autoconf libtool pkg-config libnl-3-dev libnl-genl-3-dev - - sudo apt-get install xfslibs-dev uuid-dev libtool-bin xfsprogs libgdbm-dev gawk fio attr libattr1-dev libacl1-dev libaio-dev - - git clone --branch=exfat-next https://github.com/exfat-utils/exfat-utils - - git clone https://github.com/namjaejeon/exfat-testsuites - - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - - export PATH=/usr/local/lib:$PATH - - sudo useradd fsgqa - - sudo useradd 123456-fsgqa - -script: - # Copy ksmbd source to kernel - - mv linux-stable ../ - - mv linux ../ - - mkdir ../linux-stable/fs/exfat - - cp -ar * ../linux-stable/fs/exfat/ - - cp -ar * ../linux/fs/exfat/ - - # Compile with 4.1 kernel - - cd ../linux-stable - - yes "" | make oldconfig > /dev/null - - echo 'obj-$(CONFIG_EXFAT_FS) += exfat/' >> fs/Makefile - - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig - - echo 'CONFIG_EXFAT_FS=m' >> .config - - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config - - make -j$((`nproc`+1)) fs/exfat/exfat.ko - - # Compile with latest Torvalds' kernel -# - cd ../linux -# - yes "" | make oldconfig > /dev/null -# - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile -# - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig -# - echo 'CONFIG_EXFAT_FS=m' >> .config -# - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config -# - make -j$((`nproc`+1)) fs/exfat/exfat.ko - - # Run xfstests testsuite - - cd ../linux-exfat-oot - - make > /dev/null - - sudo make install > /dev/null - - sudo modprobe exfat - - cd exfat-utils - - ./autogen.sh > /dev/null - - ./configure > /dev/null - - make -j$((`nproc`+1)) > /dev/null - - sudo make install > /dev/null - - sudo mkdir -p /mnt/scratch - - sudo mkdir -p /mnt/test - - sudo mkdir -p /mnt/full_test - # create file/director test - - truncate -s 10G full_test.img - - sudo losetup /dev/loop22 full_test.img - - sudo mkfs.exfat /dev/loop22 - - sudo mount -t exfat /dev/loop22 /mnt/full_test/ - - cd /mnt/full_test/ - - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - - sync - - sudo fsck.exfat /dev/loop22 - - sudo rm -rf * - - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - - sync - - sudo rm -rf * - - sudo fsck.exfat /dev/loop22 - - cd - - - sudo umount /mnt/full_test/ - - sudo fsck.exfat /dev/loop22 - # run xfstests test - - truncate -s 100G test.img - - truncate -s 100G scratch.img - - sudo losetup /dev/loop20 test.img - - sudo losetup /dev/loop21 scratch.img - - sudo mkfs.exfat /dev/loop20 - - sudo mkfs.exfat /dev/loop21 - - cd .. - - cd exfat-testsuites/ - - tar xzvf xfstests-exfat.tgz > /dev/null - - cd xfstests-exfat - - make -j$((`nproc`+1)) > /dev/null - - sudo ./check generic/001 - - sudo ./check generic/006 diff --git a/.travis_cmd_wrapper.pl b/.travis_cmd_wrapper.pl deleted file mode 100755 index 0cbaea4..0000000 --- a/.travis_cmd_wrapper.pl +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/perl - -# -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Copyright (C) 2019 Samsung Electronics Co., Ltd. -# - -use strict; - -sub tweak_sysctl() -{ - `sudo sysctl kernel.hardlockup_panic=0`; - `sudo sysctl kernel.hung_task_panic=0`; - `sudo sysctl kernel.panic=128`; - `sudo sysctl kernel.panic_on_io_nmi=0`; - `sudo sysctl kernel.panic_on_oops=0`; - `sudo sysctl kernel.panic_on_rcu_stall=0`; - `sudo sysctl kernel.panic_on_unrecovered_nmi=0`; - `sudo sysctl kernel.panic_on_warn=0`; - `sudo sysctl kernel.softlockup_panic=0`; - `sudo sysctl kernel.unknown_nmi_panic=0`; -} - -sub execute($$) -{ - my $cmd = shift; - my $timeout = shift; - my $output = "Timeout"; - my $status = 1; - - $timeout = 8 * 60 if (!defined $timeout); - - tweak_sysctl(); - - eval { - local $SIG{ALRM} = sub { - print "TIMEOUT:\n"; - system("top -n 1"), print "top\n"; - system("free"), print "free\n"; - system("dmesg"), print "dmesg\n"; - die "Timeout\n"; - }; - - print "Executing $cmd with timeout $timeout\n"; - - alarm $timeout; - $output = `$cmd`; - $status = $?; - alarm 0; - print $output."\n"; - print "Finished: status $status\n"; - }; - - if ($@) { - die unless $@ eq "Timeout\n"; - } -} - -if (! defined $ARGV[0]) { - print "Usage:\n\t./.travis_cmd_wrapper.pl command [timeout seconds]\n"; - exit 1; -} - -execute($ARGV[0], $ARGV[1]); diff --git a/.travis_get_mainline_kernel b/.travis_get_mainline_kernel deleted file mode 100755 index 3356d15..0000000 --- a/.travis_get_mainline_kernel +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh - -# -# A simple script we are using to get the latest mainline kernel -# tar ball -# - -wget https://www.kernel.org/releases.json -if [ $? -ne 0 ]; then - echo "Could not download kernel.org/releases.json" - exit 1 -fi - -VER=$(cat releases.json | python2.7 -c "import sys, json; print json.load(sys.stdin)['latest_stable']['version']") -if [ $? -ne 0 ]; then - echo "Could not parse release.json" - exit 1 -fi - -if [ "z$VER" = "z" ]; then - echo "Could not determine latest release version" - exit 1 -fi - -wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-"$VER".tar.gz -if [ $? -ne 0 ]; then - echo "Could not download $VER kernel version" - exit 1 -fi - -tar xf linux-"$VER".tar.gz -if [ $? -ne 0 ]; then - echo "Could not untar kernel tar ball" - exit 1 -fi - -mv linux-"$VER" linux From c39d9e5ed35bbe4d66077adf870b1b65a5a25c42 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 21 Oct 2022 00:09:23 +0900 Subject: [PATCH 038/141] exfat: add auto-test using github action add auto-test using github action. --- .github/workflows/c-cpp.yml | 184 ++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 .github/workflows/c-cpp.yml diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml new file mode 100644 index 0000000..60eb840 --- /dev/null +++ b/.github/workflows/c-cpp.yml @@ -0,0 +1,184 @@ +name: linux-exfat-oot CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Download the kernel + run: | + sudo apt-get install libelf-dev wget tar gzip python + wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz + tar xf linux-4.1.36.tar.gz + mv linux-4.1.36 linux-stable + - name: Prerequisite for xfstests testing + run: | + sudo apt-get install linux-headers-$(uname -r) + sudo apt-get install autoconf libtool pkg-config libnl-3-dev libnl-genl-3-dev + sudo apt-get install xfslibs-dev uuid-dev libtool-bin xfsprogs libgdbm-dev gawk fio attr libattr1-dev libacl1-dev libaio-dev + git clone --branch=exfat-next https://github.com/exfat-utils/exfat-utils + git clone https://github.com/namjaejeon/exfat-testsuites + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + export PATH=/usr/local/lib:$PATH + sudo useradd fsgqa + sudo useradd 123456-fsgqa + - name: Copy exfat source to kernel + run: | + mv linux-stable ../ + mkdir ../linux-stable/fs/exfat + cp -ar * ../linux-stable/fs/exfat/ + - name: Compile with 4.1 kernel + run: | + cd ../linux-stable + yes "" | make oldconfig > /dev/null + echo 'obj-$(CONFIG_EXFAT_FS) += exfat/' >> fs/Makefile + echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig + echo 'CONFIG_EXFAT_FS=m' >> .config + echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config + make -j$((`nproc`+1)) fs/exfat/exfat.ko + - name: Run xfstests testsuite + run: | + cd ../linux-exfat-oot + make > /dev/null + sudo make install > /dev/null + sudo insmod exfat.ko + cd exfat-utils + ./autogen.sh > /dev/null + ./configure > /dev/null + make -j$((`nproc`+1)) > /dev/null + sudo make install > /dev/null + cd .. + sudo mkdir -p /mnt/scratch + sudo mkdir -p /mnt/test + sudo mkdir -p full_test + - name: create file/director test + run: | + truncate -s 10G full_test.img + sudo losetup /dev/loop22 full_test.img + sudo mkfs.exfat /dev/loop22 + sudo mount -t exfat /dev/loop22 ./full_test/ + cd full_test/ + i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + sync + sudo fsck.exfat /dev/loop22 + sudo rm -rf * + i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + sync + sudo rm -rf * + sudo fsck.exfat /dev/loop22 + cd .. + sudo umount ./full_test/ + sudo fsck.exfat /dev/loop22 + - name: xfstest tests + run: | + truncate -s 100G test.img + truncate -s 100G scratch.img + sudo losetup /dev/loop20 test.img + sudo losetup /dev/loop21 scratch.img + sudo mkfs.exfat /dev/loop20 + sudo mkfs.exfat /dev/loop21 + cd exfat-testsuites/ + tar xzvf xfstests-exfat.tgz > /dev/null + cd xfstests-exfat + make -j$((`nproc`+1)) > /dev/null + sudo ./check generic/001 + sudo ./check generic/006 + sudo ./check generic/007 + sudo ./check generic/011 + sudo ./check generic/013 + sudo ./check generic/014 + sudo ./check generic/028 + sudo ./check generic/029 + sudo ./check generic/030 + sudo ./check generic/034 + sudo ./check generic/035 + sudo ./check generic/036 + sudo ./check generic/069 + sudo ./check generic/073 + sudo ./check generic/074 + sudo ./check generic/075 + sudo ./check generic/076 + sudo ./check generic/080 + sudo ./check generic/084 + sudo ./check generic/091 + sudo ./check generic/095 + sudo ./check generic/098 + sudo ./check generic/100 + sudo ./check generic/112 + sudo ./check generic/113 + sudo ./check generic/114 + sudo ./check generic/120 + sudo ./check generic/123 + sudo ./check generic/124 + sudo ./check generic/127 + sudo ./check generic/129 + sudo ./check generic/130 + sudo ./check generic/131 + sudo ./check generic/132 + sudo ./check generic/133 + sudo ./check generic/135 + sudo ./check generic/141 + sudo ./check generic/169 + sudo ./check generic/198 + sudo ./check generic/207 + sudo ./check generic/208 + sudo ./check generic/209 + sudo ./check generic/210 + sudo ./check generic/211 + sudo ./check generic/212 + sudo ./check generic/215 + sudo ./check generic/221 + sudo ./check generic/239 + sudo ./check generic/240 + sudo ./check generic/241 + sudo ./check generic/245 + sudo ./check generic/246 + sudo ./check generic/247 + sudo ./check generic/248 + sudo ./check generic/249 + sudo ./check generic/257 + sudo ./check generic/260 + sudo ./check generic/263 + sudo ./check generic/285 + sudo ./check generic/286 + sudo ./check generic/288 + sudo ./check generic/308 + sudo ./check generic/309 + sudo ./check generic/310 + sudo ./check generic/313 + sudo ./check generic/323 + sudo ./check generic/325 + sudo ./check generic/338 + sudo ./check generic/339 + sudo ./check generic/340 + sudo ./check generic/344 + sudo ./check generic/345 + sudo ./check generic/346 + sudo ./check generic/354 + sudo ./check generic/376 + sudo ./check generic/393 + sudo ./check generic/394 + sudo ./check generic/405 + sudo ./check generic/406 + sudo ./check generic/409 + sudo ./check generic/410 + sudo ./check generic/411 + sudo ./check generic/412 + sudo ./check generic/418 + sudo ./check generic/428 + sudo ./check generic/437 + sudo ./check generic/438 + sudo ./check generic/441 + sudo ./check generic/443 + sudo ./check generic/448 + sudo ./check generic/450 + sudo ./check generic/451 + sudo ./check generic/452 From 1050b8d94b11679c7eccd731c566a737038904fe Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 4 Nov 2022 08:18:57 +0900 Subject: [PATCH 039/141] exfat: simplify empty entry hint This commit adds exfat_set_empty_hint()/exfat_reset_empty_hint() to reduce code complexity and make code more readable. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 58 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/dir.c b/dir.c index 07765a7..b56ea40 100644 --- a/dir.c +++ b/dir.c @@ -908,6 +908,29 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, return NULL; } +static inline void exfat_reset_empty_hint(struct exfat_hint_femp *hint_femp) +{ + hint_femp->eidx = EXFAT_HINT_NONE; + hint_femp->count = 0; +} + +static inline void exfat_set_empty_hint(struct exfat_inode_info *ei, + struct exfat_hint_femp *candi_empty, struct exfat_chain *clu, + int dentry, int num_entries) +{ + if (ei->hint_femp.eidx == EXFAT_HINT_NONE || + ei->hint_femp.eidx > dentry) { + if (candi_empty->count == 0) { + candi_empty->cur = *clu; + candi_empty->eidx = dentry; + } + + candi_empty->count++; + if (candi_empty->count == num_entries) + ei->hint_femp = *candi_empty; + } +} + enum { DIRENT_STEP_FILE, DIRENT_STEP_STRM, @@ -932,7 +955,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, { int i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; int order, step, name_len = 0; - int dentries_per_clu, num_empty = 0; + int dentries_per_clu; unsigned int entry_type; unsigned short *uniname = NULL; struct exfat_chain clu; @@ -950,10 +973,13 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, end_eidx = dentry; } - candi_empty.eidx = EXFAT_HINT_NONE; + exfat_reset_empty_hint(&ei->hint_femp); + rewind: order = 0; step = DIRENT_STEP_FILE; + exfat_reset_empty_hint(&candi_empty); + while (clu.dir != EXFAT_EOF_CLUSTER) { i = dentry & (dentries_per_clu - 1); for (; i < dentries_per_clu; i++, dentry++) { @@ -973,26 +999,8 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, entry_type == TYPE_DELETED) { step = DIRENT_STEP_FILE; - num_empty++; - if (candi_empty.eidx == EXFAT_HINT_NONE && - num_empty == 1) { - exfat_chain_set(&candi_empty.cur, - clu.dir, clu.size, clu.flags); - } - - if (candi_empty.eidx == EXFAT_HINT_NONE && - num_empty >= num_entries) { - candi_empty.eidx = - dentry - (num_empty - 1); - WARN_ON(candi_empty.eidx < 0); - candi_empty.count = num_empty; - - if (ei->hint_femp.eidx == - EXFAT_HINT_NONE || - candi_empty.eidx <= - ei->hint_femp.eidx) - ei->hint_femp = candi_empty; - } + exfat_set_empty_hint(ei, &candi_empty, &clu, + dentry, num_entries); brelse(bh); if (entry_type == TYPE_UNUSED) @@ -1000,8 +1008,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, continue; } - num_empty = 0; - candi_empty.eidx = EXFAT_HINT_NONE; + exfat_reset_empty_hint(&candi_empty); if (entry_type == TYPE_FILE || entry_type == TYPE_DIR) { step = DIRENT_STEP_FILE; @@ -1101,9 +1108,6 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, rewind = 1; dentry = 0; clu.dir = p_dir->dir; - /* reset empty hint */ - num_empty = 0; - candi_empty.eidx = EXFAT_HINT_NONE; goto rewind; } From 5c4fd784c142b4b06b2ba1c4a559b249aa2dae81 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 4 Nov 2022 08:20:09 +0900 Subject: [PATCH 040/141] exfat: hint the empty entry which at the end of cluster chain After traversing all directory entries, hint the empty directory entry no matter whether or not there are enough empty directory entries. After this commit, hint the empty directory entries like this: 1. Hint the deleted directory entries if enough; 2. Hint the deleted and unused directory entries which at the end of the cluster chain no matter whether enough or not(Add by this commit); 3. If no any empty directory entries, hint the empty directory entries in the new cluster(Add by this commit). This avoids repeated traversal of directory entries, reduces CPU usage, and improves the performance of creating files and directories(especially on low-performance CPUs). Test create 5000 files in a class 4 SD card on imx6q-sabrelite with: for ((i=0;i<5;i++)); do sync time (for ((j=1;j<=1000;j++)); do touch file$((i*1000+j)); done) done The more files, the more performance improvements. Before After Improvement 1~1000 25.360s 22.168s 14.40% 1001~2000 38.242s 28.72ss 33.15% 2001~3000 49.134s 35.037s 40.23% 3001~4000 62.042s 41.624s 49.05% 4001~5000 73.629s 46.772s 57.42% Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 26 ++++++++++++++++++++++---- namei.c | 33 +++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/dir.c b/dir.c index b56ea40..beb7f2c 100644 --- a/dir.c +++ b/dir.c @@ -916,17 +916,24 @@ static inline void exfat_reset_empty_hint(struct exfat_hint_femp *hint_femp) static inline void exfat_set_empty_hint(struct exfat_inode_info *ei, struct exfat_hint_femp *candi_empty, struct exfat_chain *clu, - int dentry, int num_entries) + int dentry, int num_entries, int entry_type) { if (ei->hint_femp.eidx == EXFAT_HINT_NONE || ei->hint_femp.eidx > dentry) { + int total_entries = EXFAT_B_TO_DEN(i_size_read(&ei->vfs_inode)); + if (candi_empty->count == 0) { candi_empty->cur = *clu; candi_empty->eidx = dentry; } - candi_empty->count++; - if (candi_empty->count == num_entries) + if (entry_type == TYPE_UNUSED) + candi_empty->count += total_entries - dentry; + else + candi_empty->count++; + + if (candi_empty->count == num_entries || + candi_empty->count + candi_empty->eidx == total_entries) ei->hint_femp = *candi_empty; } } @@ -1000,7 +1007,8 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, step = DIRENT_STEP_FILE; exfat_set_empty_hint(ei, &candi_empty, &clu, - dentry, num_entries); + dentry, num_entries, + entry_type); brelse(bh); if (entry_type == TYPE_UNUSED) @@ -1111,6 +1119,16 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, goto rewind; } + /* + * set the EXFAT_EOF_CLUSTER flag to avoid search + * from the beginning again when allocated a new cluster + */ + if (ei->hint_femp.eidx == EXFAT_HINT_NONE) { + ei->hint_femp.cur.dir = EXFAT_EOF_CLUSTER; + ei->hint_femp.eidx = p_dir->size * dentries_per_clu; + ei->hint_femp.count = 0; + } + /* initialized hint_stat */ hint_stat->clu = p_dir->dir; hint_stat->eidx = 0; diff --git a/namei.c b/namei.c index 3342f29..23ff0c9 100644 --- a/namei.c +++ b/namei.c @@ -251,11 +251,18 @@ static int exfat_search_empty_slot(struct super_block *sb, if (hint_femp->eidx != EXFAT_HINT_NONE) { dentry = hint_femp->eidx; - if (num_entries <= hint_femp->count) { - hint_femp->eidx = EXFAT_HINT_NONE; - return dentry; - } + /* + * If hint_femp->count is enough, it is needed to check if + * there are actual empty entries. + * Otherwise, and if "dentry + hint_famp->count" is also equal + * to "p_dir->size * dentries_per_clu", it means ENOSPC. + */ + if (dentry + hint_femp->count == p_dir->size * dentries_per_clu && + num_entries > hint_femp->count) + return -ENOSPC; + + hint_femp->eidx = EXFAT_HINT_NONE; exfat_chain_dup(&clu, &hint_femp->cur); } else { exfat_chain_dup(&clu, p_dir); @@ -320,6 +327,12 @@ static int exfat_search_empty_slot(struct super_block *sb, } } + hint_femp->eidx = p_dir->size * dentries_per_clu - num_empty; + hint_femp->count = num_empty; + if (num_empty == 0) + exfat_chain_set(&hint_femp->cur, EXFAT_EOF_CLUSTER, 0, + clu.flags); + return -ENOSPC; } @@ -396,15 +409,11 @@ static int exfat_find_empty_entry(struct inode *inode, if (exfat_ent_set(sb, last_clu, clu.dir)) return -EIO; - if (hint_femp.eidx == EXFAT_HINT_NONE) { - /* the special case that new dentry - * should be allocated from the start of new cluster - */ - hint_femp.eidx = EXFAT_B_TO_DEN_IDX(p_dir->size, sbi); - hint_femp.count = sbi->dentries_per_clu; - + if (hint_femp.cur.dir == EXFAT_EOF_CLUSTER) exfat_chain_set(&hint_femp.cur, clu.dir, 0, clu.flags); - } + + hint_femp.count += sbi->dentries_per_clu; + hint_femp.cur.size++; p_dir->size++; size = EXFAT_CLU_TO_B(p_dir->size, sbi); From 54b4c1b26020a3b17bca453c269ed2d6208b4ae4 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 19 Nov 2022 15:23:40 +0900 Subject: [PATCH 041/141] exfat: add SECTOR_SIZE macro SECTOR_SIZE macro is not define in low kernel version. This patch add SECTOR_SIZE macro. Signed-off-by: Namjae Jeon --- exfat_fs.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index 0db1f25..486132b 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -11,6 +11,10 @@ #include #include +#ifndef SECTOR_SIZE +#define SECTOR_SIZE 512 +#endif + #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) #include #else From 48f7c74a4cfa819184af055c90f31f4975341184 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:39:47 +0900 Subject: [PATCH 042/141] exfat: reduce the size of exfat_entry_set_cache In normal, there are 19 directory entries at most for a file or a directory. - A file directory entry - A stream extension directory entry - 1~17 file name directory entry So the directory entries are in 3 sectors at most, it is enough for struct exfat_entry_set_cache to pre-allocate 3 bh. This commit changes the size of struct exfat_entry_set_cache as: Before After 32-bit system 88 32 bytes 64-bit system 168 48 bytes Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- exfat_fs.h | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 486132b..6b035c0 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -10,6 +10,7 @@ #include #include #include +#include #ifndef SECTOR_SIZE #define SECTOR_SIZE 512 @@ -54,6 +55,14 @@ enum { #define ES_2_ENTRIES 2 #define ES_ALL_ENTRIES 0 +#define ES_IDX_FILE 0 +#define ES_IDX_STREAM 1 +#define ES_IDX_FIRST_FILENAME 2 +#define EXFAT_FILENAME_ENTRY_NUM(name_len) \ + DIV_ROUND_UP(name_len, EXFAT_FILE_NAME_LEN) +#define ES_IDX_LAST_FILENAME(name_len) \ + (ES_IDX_FIRST_FILENAME + EXFAT_FILENAME_ENTRY_NUM(name_len) - 1) + #define DIR_DELETED 0xFFFF0321 /* type values */ @@ -81,9 +90,6 @@ enum { #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ #define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH + 1) * MAX_CHARSET_SIZE) -/* Enough size to hold 256 dentry (even 512 Byte sector) */ -#define DIR_CACHE_SIZE (256*sizeof(struct exfat_dentry)/512+1) - #define EXFAT_HINT_NONE -1 #define EXFAT_MIN_SUBDIR 2 @@ -138,6 +144,17 @@ enum { #define BITS_PER_BYTE_MASK 0x7 #define IGNORED_BITS_REMAINED(clu, clu_base) ((1 << ((clu) - (clu_base))) - 1) +#define ES_ENTRY_NUM(name_len) (ES_IDX_LAST_FILENAME(name_len) + 1) +/* 19 entries = 1 file entry + 1 stream entry + 17 filename entries */ +#define ES_MAX_ENTRY_NUM ES_ENTRY_NUM(MAX_NAME_LENGTH) + +/* + * 19 entries x 32 bytes/entry = 608 bytes. + * The 608 bytes are in 3 sectors at most (even 512 Byte sector). + */ +#define DIR_CACHE_SIZE \ + (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) + struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ @@ -179,11 +196,11 @@ struct exfat_hint { struct exfat_entry_set_cache { struct super_block *sb; - bool modified; unsigned int start_off; int num_bh; struct buffer_head *bh[DIR_CACHE_SIZE]; unsigned int num_entries; + bool modified; }; struct exfat_dir_entry { From 5d4023f0718d36c71a92e0f7709b7d3150aad2a9 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:24:14 +0900 Subject: [PATCH 043/141] exfat: support dynamic allocate bh for exfat_entry_set_cache In special cases, a file or a directory may occupied more than 19 directory entries, pre-allocating 3 bh is not enough. Such as - Support vendor secondary directory entry in the future. - Since file directory entry is damaged, the SecondaryCount field is bigger than 18. So this commit supports dynamic allocation of bh. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 15 +++++++++++++++ exfat_fs.h | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dir.c b/dir.c index beb7f2c..46df6e6 100644 --- a/dir.c +++ b/dir.c @@ -626,6 +626,10 @@ int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) bforget(es->bh[i]); else brelse(es->bh[i]); + + if (IS_DYNAMIC_ES(es)) + kfree(es->bh); + kfree(es); return err; } @@ -858,6 +862,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, /* byte offset in sector */ off = EXFAT_BLK_OFFSET(byte_offset, sb); es->start_off = off; + es->bh = es->__bh; /* sector offset in cluster */ sec = EXFAT_B_TO_BLK(byte_offset, sb); @@ -877,6 +882,16 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, es->num_entries = num_entries; num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); + if (num_bh > ARRAY_SIZE(es->__bh)) { + es->bh = kmalloc_array(num_bh, sizeof(*es->bh), GFP_KERNEL); + if (!es->bh) { + brelse(bh); + kfree(es); + return NULL; + } + es->bh[0] = bh; + } + for (i = 1; i < num_bh; i++) { /* get the next sector */ if (exfat_is_last_sector_in_cluster(sbi, sec)) { diff --git a/exfat_fs.h b/exfat_fs.h index 6b035c0..e10d005 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -198,11 +198,14 @@ struct exfat_entry_set_cache { struct super_block *sb; unsigned int start_off; int num_bh; - struct buffer_head *bh[DIR_CACHE_SIZE]; + struct buffer_head *__bh[DIR_CACHE_SIZE]; + struct buffer_head **bh; unsigned int num_entries; bool modified; }; +#define IS_DYNAMIC_ES(es) ((es)->__bh != (es)->bh) + struct exfat_dir_entry { struct exfat_chain dir; int entry; From 7771b09c4dc7ef8be49159ff84418558b84caa3e Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:25:14 +0900 Subject: [PATCH 044/141] exfat: move exfat_entry_set_cache from heap to stack The size of struct exfat_entry_set_cache is only 56 bytes on 64-bit system, and allocating from stack is more efficient than allocating from heap. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 35 +++++++++++++++-------------------- exfat_fs.h | 5 +++-- inode.c | 13 ++++++------- namei.c | 11 +++++------ 4 files changed, 29 insertions(+), 35 deletions(-) diff --git a/dir.c b/dir.c index 46df6e6..f663cb1 100644 --- a/dir.c +++ b/dir.c @@ -34,10 +34,9 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned short *uniname) { int i; - struct exfat_entry_set_cache *es; + struct exfat_entry_set_cache es; - es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES); - if (!es) + if (exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES)) return; /* @@ -46,8 +45,8 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, * Third entry : first file-name entry * So, the index of first file-name dentry should start from 2. */ - for (i = 2; i < es->num_entries; i++) { - struct exfat_dentry *ep = exfat_get_dentry_cached(es, i); + for (i = 2; i < es.num_entries; i++) { + struct exfat_dentry *ep = exfat_get_dentry_cached(&es, i); /* end of name entry */ if (exfat_get_entry_type(ep) != TYPE_EXTEND) @@ -57,7 +56,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, uniname += EXFAT_FILE_NAME_LEN; } - exfat_free_dentry_set(es, false); + exfat_free_dentry_set(&es, false); } /* read a directory entry from the opened directory */ @@ -630,7 +629,6 @@ int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) if (IS_DYNAMIC_ES(es)) kfree(es->bh); - kfree(es); return err; } @@ -827,14 +825,14 @@ struct exfat_dentry *exfat_get_dentry_cached( * pointer of entry set on success, * NULL on failure. */ -struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, - struct exfat_chain *p_dir, int entry, unsigned int type) +int exfat_get_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, int entry, + unsigned int type) { int ret, i, num_bh; unsigned int off, byte_offset, clu = 0; sector_t sec; struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct exfat_entry_set_cache *es; struct exfat_dentry *ep; int num_entries; enum exfat_validate_dentry_mode mode = ES_MODE_STARTED; @@ -842,17 +840,15 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, if (p_dir->dir == DIR_DELETED) { exfat_err(sb, "access to deleted dentry"); - return NULL; + return -EIO; } byte_offset = EXFAT_DEN_TO_B(entry); ret = exfat_walk_fat_chain(sb, p_dir, byte_offset, &clu); if (ret) - return NULL; + return ret; - es = kzalloc(sizeof(*es), GFP_KERNEL); - if (!es) - return NULL; + memset(es, 0, sizeof(*es)); es->sb = sb; es->modified = false; @@ -870,7 +866,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, bh = sb_bread(sb, sec); if (!bh) - goto free_es; + return -EIO; es->bh[es->num_bh++] = bh; ep = exfat_get_dentry_cached(es, 0); @@ -886,8 +882,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, es->bh = kmalloc_array(num_bh, sizeof(*es->bh), GFP_KERNEL); if (!es->bh) { brelse(bh); - kfree(es); - return NULL; + return -ENOMEM; } es->bh[0] = bh; } @@ -916,11 +911,11 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) goto free_es; } - return es; + return 0; free_es: exfat_free_dentry_set(es, false); - return NULL; + return -EIO; } static inline void exfat_reset_empty_hint(struct exfat_hint_femp *hint_femp) diff --git a/exfat_fs.h b/exfat_fs.h index e10d005..a64b687 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -526,8 +526,9 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb, struct exfat_chain *p_dir, int entry, struct buffer_head **bh); struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, int num); -struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, - struct exfat_chain *p_dir, int entry, unsigned int type); +int exfat_get_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, int entry, + unsigned int type); int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); diff --git a/inode.c b/inode.c index f3dbeb3..c66276e 100644 --- a/inode.c +++ b/inode.c @@ -23,7 +23,7 @@ int __exfat_write_inode(struct inode *inode, int sync) { unsigned long long on_disk_size; struct exfat_dentry *ep, *ep2; - struct exfat_entry_set_cache *es = NULL; + struct exfat_entry_set_cache es; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); @@ -44,11 +44,10 @@ int __exfat_write_inode(struct inode *inode, int sync) exfat_set_volume_dirty(sb); /* get the directory entry of given file or directory */ - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES); - if (!es) + if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(es, 0); - ep2 = exfat_get_dentry_cached(es, 1); + ep = exfat_get_dentry_cached(&es, 0); + ep2 = exfat_get_dentry_cached(&es, 1); ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); @@ -85,8 +84,8 @@ int __exfat_write_inode(struct inode *inode, int sync) ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; } - exfat_update_dir_chksum_with_entry_set(es); - return exfat_free_dentry_set(es, sync); + exfat_update_dir_chksum_with_entry_set(&es); + return exfat_free_dentry_set(&es, sync); } int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) diff --git a/namei.c b/namei.c index 23ff0c9..2bb79d2 100644 --- a/namei.c +++ b/namei.c @@ -654,7 +654,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(dir); struct exfat_dentry *ep, *ep2; - struct exfat_entry_set_cache *es; + struct exfat_entry_set_cache es; /* for optimized dir & entry to prevent long traverse of cluster chain */ struct exfat_hint hint_opt; @@ -702,11 +702,10 @@ static int exfat_find(struct inode *dir, struct qstr *qname, if (cdir.flags & ALLOC_NO_FAT_CHAIN) cdir.size -= dentry / sbi->dentries_per_clu; dentry = hint_opt.eidx; - es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); - if (!es) + if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(es, 0); - ep2 = exfat_get_dentry_cached(es, 1); + ep = exfat_get_dentry_cached(&es, 0); + ep2 = exfat_get_dentry_cached(&es, 1); info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); @@ -735,7 +734,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, ep->dentry.file.access_time, ep->dentry.file.access_date, 0); - exfat_free_dentry_set(es, false); + exfat_free_dentry_set(&es, false); if (ei->start_clu == EXFAT_FREE_CLUSTER) { exfat_fs_error(sb, From 7cab729caa62089ba5fbb7fee65e54c1fed7175a Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:27:59 +0900 Subject: [PATCH 045/141] exfat: rename exfat_free_dentry_set() to exfat_put_dentry_set() Since struct exfat_entry_set_cache is allocated from stack, no need to free, so rename exfat_free_dentry_set() to exfat_put_dentry_set(). After renaming, the new function pair is exfat_get_dentry_set()/exfat_put_dentry_set(). Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 16 ++++++++-------- exfat_fs.h | 2 +- inode.c | 2 +- namei.c | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dir.c b/dir.c index f663cb1..32ebb2a 100644 --- a/dir.c +++ b/dir.c @@ -56,7 +56,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, uniname += EXFAT_FILE_NAME_LEN; } - exfat_free_dentry_set(&es, false); + exfat_put_dentry_set(&es, false); } /* read a directory entry from the opened directory */ @@ -613,7 +613,7 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) es->modified = true; } -int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) +int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync) { int i, err = 0; @@ -871,7 +871,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, ep = exfat_get_dentry_cached(es, 0); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) - goto free_es; + goto put_es; num_entries = type == ES_ALL_ENTRIES ? ep->dentry.file.num_ext + 1 : type; @@ -893,7 +893,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, if (p_dir->flags == ALLOC_NO_FAT_CHAIN) clu++; else if (exfat_get_next_cluster(sb, &clu)) - goto free_es; + goto put_es; sec = exfat_cluster_to_sector(sbi, clu); } else { sec++; @@ -901,7 +901,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, bh = sb_bread(sb, sec); if (!bh) - goto free_es; + goto put_es; es->bh[es->num_bh++] = bh; } @@ -909,12 +909,12 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, for (i = 1; i < num_entries; i++) { ep = exfat_get_dentry_cached(es, i); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) - goto free_es; + goto put_es; } return 0; -free_es: - exfat_free_dentry_set(es, false); +put_es: + exfat_put_dentry_set(es, false); return -EIO; } diff --git a/exfat_fs.h b/exfat_fs.h index a64b687..b51e72c 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -529,7 +529,7 @@ struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, int exfat_get_dentry_set(struct exfat_entry_set_cache *es, struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned int type); -int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); +int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); /* inode.c */ diff --git a/inode.c b/inode.c index c66276e..87e679d 100644 --- a/inode.c +++ b/inode.c @@ -85,7 +85,7 @@ int __exfat_write_inode(struct inode *inode, int sync) } exfat_update_dir_chksum_with_entry_set(&es); - return exfat_free_dentry_set(&es, sync); + return exfat_put_dentry_set(&es, sync); } int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) diff --git a/namei.c b/namei.c index 2bb79d2..27251b3 100644 --- a/namei.c +++ b/namei.c @@ -734,7 +734,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, ep->dentry.file.access_time, ep->dentry.file.access_date, 0); - exfat_free_dentry_set(&es, false); + exfat_put_dentry_set(&es, false); if (ei->start_clu == EXFAT_FREE_CLUSTER) { exfat_fs_error(sb, From 15a51d81f19d305be246e715059ef1c00518604d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:28:34 +0900 Subject: [PATCH 046/141] exfat: replace magic numbers with Macros Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru --- dir.c | 12 ++++++------ inode.c | 4 ++-- namei.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dir.c b/dir.c index 32ebb2a..b359087 100644 --- a/dir.c +++ b/dir.c @@ -45,7 +45,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, * Third entry : first file-name entry * So, the index of first file-name dentry should start from 2. */ - for (i = 2; i < es.num_entries; i++) { + for (i = ES_IDX_FIRST_FILENAME; i < es.num_entries; i++) { struct exfat_dentry *ep = exfat_get_dentry_cached(&es, i); /* end of name entry */ @@ -337,7 +337,7 @@ int exfat_calc_num_entries(struct exfat_uni_name *p_uniname) return -EINVAL; /* 1 file entry + 1 stream entry + name entries */ - return ((len - 1) / EXFAT_FILE_NAME_LEN + 3); + return ES_ENTRY_NUM(len); } unsigned int exfat_get_entry_type(struct exfat_dentry *ep) @@ -602,13 +602,13 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) unsigned short chksum = 0; struct exfat_dentry *ep; - for (i = 0; i < es->num_entries; i++) { + for (i = ES_IDX_FILE; i < es->num_entries; i++) { ep = exfat_get_dentry_cached(es, i); chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, chksum_type); chksum_type = CS_DEFAULT; } - ep = exfat_get_dentry_cached(es, 0); + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); ep->dentry.file.checksum = cpu_to_le16(chksum); es->modified = true; } @@ -869,7 +869,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, return -EIO; es->bh[es->num_bh++] = bh; - ep = exfat_get_dentry_cached(es, 0); + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) goto put_es; @@ -906,7 +906,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, } /* validate cached dentries */ - for (i = 1; i < num_entries; i++) { + for (i = ES_IDX_STREAM; i < num_entries; i++) { ep = exfat_get_dentry_cached(es, i); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) goto put_es; diff --git a/inode.c b/inode.c index 87e679d..93dc313 100644 --- a/inode.c +++ b/inode.c @@ -46,8 +46,8 @@ int __exfat_write_inode(struct inode *inode, int sync) /* get the directory entry of given file or directory */ if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(&es, 0); - ep2 = exfat_get_dentry_cached(&es, 1); + ep = exfat_get_dentry_cached(&es, ES_IDX_FILE); + ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM); ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); diff --git a/namei.c b/namei.c index 27251b3..f887570 100644 --- a/namei.c +++ b/namei.c @@ -704,8 +704,8 @@ static int exfat_find(struct inode *dir, struct qstr *qname, dentry = hint_opt.eidx; if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(&es, 0); - ep2 = exfat_get_dentry_cached(&es, 1); + ep = exfat_get_dentry_cached(&es, ES_IDX_FILE); + ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM); info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); From b2d2ce0b02aa1dcc25e8ca5ad2e42610ad5637e8 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sat, 19 Nov 2022 14:58:44 +0900 Subject: [PATCH 047/141] exfat: treewide: use get_random_u32() when possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prandom_u32() function has been a deprecated inline wrapper around get_random_u32() for several releases now, and compiles down to the exact same code. Replace the deprecated wrapper with a direct call to the real function. The same also applies to get_random_int(), which is just a wrapper around get_random_u32(). This was done as a basic find and replace. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Kees Cook Reviewed-by: Yury Norov Reviewed-by: Jan Kara # for ext4 Acked-by: Toke Høiland-Jørgensen # for sch_cake Acked-by: Chuck Lever # for nfsd Acked-by: Jakub Kicinski Acked-by: Mika Westerberg # for thunderbolt Acked-by: Darrick J. Wong # for xfs Acked-by: Helge Deller # for parisc Acked-by: Heiko Carstens # for s390 Signed-off-by: Jason A. Donenfeld Signed-off-by: Namjae Jeon --- inode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inode.c b/inode.c index 93dc313..d0bfa26 100644 --- a/inode.c +++ b/inode.c @@ -615,7 +615,11 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) #else inode->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + inode->i_generation = get_random_u32(); +#else inode->i_generation = prandom_u32(); +#endif if (info->attr & ATTR_SUBDIR) { /* directory */ inode->i_generation &= ~1; From 3a977dbbe0fc62137bb037cd82b9a0dd9ea27fd3 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 26 Nov 2022 15:03:32 +0900 Subject: [PATCH 048/141] exfat: github actions: add apt-get update command --- .github/workflows/c-cpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 60eb840..362f95a 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -15,6 +15,7 @@ jobs: - uses: actions/checkout@v3 - name: Download the kernel run: | + sudo apt-get update sudo apt-get install libelf-dev wget tar gzip python wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz tar xf linux-4.1.36.tar.gz From 1813cc2e9f486a5ebc2c731d5440d1d2a5ab2bb1 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 9 Dec 2022 22:56:26 +0900 Subject: [PATCH 049/141] exfat: fix python package installation failure --- .github/workflows/c-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 362f95a..0cb70a7 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -16,7 +16,7 @@ jobs: - name: Download the kernel run: | sudo apt-get update - sudo apt-get install libelf-dev wget tar gzip python + sudo apt-get install libelf-dev wget tar gzip python2.7 wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz tar xf linux-4.1.36.tar.gz mv linux-4.1.36 linux-stable From 2bb461ce26f78b538c856e4cab11733aa3b4022e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 9 Dec 2022 23:20:49 +0900 Subject: [PATCH 050/141] exfat: remove generic/286 --- .github/workflows/c-cpp.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 0cb70a7..476c746 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -149,7 +149,6 @@ jobs: sudo ./check generic/260 sudo ./check generic/263 sudo ./check generic/285 - sudo ./check generic/286 sudo ./check generic/288 sudo ./check generic/308 sudo ./check generic/309 From e112dbc4664b9bf128729302c8d535eb426cbe19 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:32:02 +0900 Subject: [PATCH 051/141] exfat: remove call ilog2() from exfat_readdir() There is no need to call ilog2() for the conversions between cluster and dentry in exfat_readdir(), because these conversions can be replaced with EXFAT_DEN_TO_CLU()/EXFAT_CLU_TO_DEN(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 9 ++++----- exfat_fs.h | 10 ++++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/dir.c b/dir.c index b359087..546a31c 100644 --- a/dir.c +++ b/dir.c @@ -62,7 +62,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, /* read a directory entry from the opened directory */ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_entry *dir_entry) { - int i, dentries_per_clu, dentries_per_clu_bits = 0, num_ext; + int i, dentries_per_clu, num_ext; unsigned int type, clu_offset, max_dentries; struct exfat_chain dir, clu; struct exfat_uni_name uni_name; @@ -84,11 +84,10 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); dentries_per_clu = sbi->dentries_per_clu; - dentries_per_clu_bits = ilog2(dentries_per_clu); max_dentries = (unsigned int)min_t(u64, MAX_EXFAT_DENTRIES, - (u64)sbi->num_clusters << dentries_per_clu_bits); + (u64)EXFAT_CLU_TO_DEN(sbi->num_clusters, sbi)); - clu_offset = dentry >> dentries_per_clu_bits; + clu_offset = EXFAT_DEN_TO_CLU(dentry, sbi); exfat_chain_dup(&clu, &dir); if (clu.flags == ALLOC_NO_FAT_CHAIN) { @@ -163,7 +162,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent dir_entry->entry = dentry; brelse(bh); - ei->hint_bmap.off = dentry >> dentries_per_clu_bits; + ei->hint_bmap.off = EXFAT_DEN_TO_CLU(dentry, sbi); ei->hint_bmap.clu = clu.dir; *cpos = EXFAT_DEN_TO_B(dentry + 1 + num_ext); diff --git a/exfat_fs.h b/exfat_fs.h index b51e72c..b3d6fe6 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -114,11 +114,17 @@ enum { /* * helpers for block size to dentry size conversion. */ -#define EXFAT_B_TO_DEN_IDX(b, sbi) \ - ((b) << ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS)) #define EXFAT_B_TO_DEN(b) ((b) >> DENTRY_SIZE_BITS) #define EXFAT_DEN_TO_B(b) ((b) << DENTRY_SIZE_BITS) +/* + * helpers for cluster size to dentry size conversion. + */ +#define EXFAT_CLU_TO_DEN(clu, sbi) \ + ((clu) << ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS)) +#define EXFAT_DEN_TO_CLU(dentry, sbi) \ + ((dentry) >> ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS)) + /* * helpers for fat entry. */ From 9ba14ac941cc1861bd638f3f9fa1acefbb0a0dc4 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:32:48 +0900 Subject: [PATCH 052/141] exfat: remove unneeded codes from __exfat_rename() The code gets the dentry, but the dentry is not used, remove the code. Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/namei.c b/namei.c index f887570..aef8007 100644 --- a/namei.c +++ b/namei.c @@ -1291,7 +1291,7 @@ static int __exfat_rename(struct inode *old_parent_inode, struct exfat_inode_info *new_ei = NULL; unsigned int new_entry_type = TYPE_UNUSED; int new_entry = 0; - struct buffer_head *old_bh, *new_bh = NULL; + struct buffer_head *new_bh = NULL; /* check the validity of pointer parameters */ if (new_path == NULL || strlen(new_path) == 0) @@ -1307,13 +1307,6 @@ static int __exfat_rename(struct inode *old_parent_inode, EXFAT_I(old_parent_inode)->flags); dentry = ei->entry; - ep = exfat_get_dentry(sb, &olddir, dentry, &old_bh); - if (!ep) { - ret = -EIO; - goto out; - } - brelse(old_bh); - /* check whether new dir is existing directory and empty */ if (new_inode) { ret = -EIO; From 5098d1ebfcdaec9c7b3e6fd44582cc07b2750c21 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:33:36 +0900 Subject: [PATCH 053/141] exfat: remove unnecessary arguments from exfat_find_dir_entry() This commit removes argument 'num_entries' and 'type' from exfat_find_dir_entry(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 12 +++++++----- exfat_fs.h | 3 +-- namei.c | 10 ++-------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/dir.c b/dir.c index 546a31c..8cbff27 100644 --- a/dir.c +++ b/dir.c @@ -967,7 +967,7 @@ enum { */ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, - int num_entries, unsigned int type, struct exfat_hint *hint_opt) + struct exfat_hint *hint_opt) { int i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; int order, step, name_len = 0; @@ -978,6 +978,10 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_hint *hint_stat = &ei->hint_stat; struct exfat_hint_femp candi_empty; struct exfat_sb_info *sbi = EXFAT_SB(sb); + int num_entries = exfat_calc_num_entries(p_uniname); + + if (num_entries < 0) + return num_entries; dentries_per_clu = sbi->dentries_per_clu; @@ -1031,10 +1035,8 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, step = DIRENT_STEP_FILE; hint_opt->clu = clu.dir; hint_opt->eidx = i; - if (type == TYPE_ALL || type == entry_type) { - num_ext = ep->dentry.file.num_ext; - step = DIRENT_STEP_STRM; - } + num_ext = ep->dentry.file.num_ext; + step = DIRENT_STEP_STRM; brelse(bh); continue; } diff --git a/exfat_fs.h b/exfat_fs.h index b3d6fe6..10621f1 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -84,7 +84,6 @@ enum { #define TYPE_PADDING 0x0402 #define TYPE_ACLTAB 0x0403 #define TYPE_BENIGN_SEC 0x0800 -#define TYPE_ALL 0x0FFF #define MAX_CHARSET_SIZE 6 /* max size of multi-byte character */ #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ @@ -526,7 +525,7 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es); int exfat_calc_num_entries(struct exfat_uni_name *p_uniname); int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, - int num_entries, unsigned int type, struct exfat_hint *hint_opt); + struct exfat_hint *hint_opt); int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu); struct exfat_dentry *exfat_get_dentry(struct super_block *sb, struct exfat_chain *p_dir, int entry, struct buffer_head **bh); diff --git a/namei.c b/namei.c index aef8007..8814f76 100644 --- a/namei.c +++ b/namei.c @@ -647,7 +647,7 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, static int exfat_find(struct inode *dir, struct qstr *qname, struct exfat_dir_entry *info) { - int ret, dentry, num_entries, count; + int ret, dentry, count; struct exfat_chain cdir; struct exfat_uni_name uni_name; struct super_block *sb = dir->i_sb; @@ -666,10 +666,6 @@ static int exfat_find(struct inode *dir, struct qstr *qname, if (ret) return ret; - num_entries = exfat_calc_num_entries(&uni_name); - if (num_entries < 0) - return num_entries; - /* check the validation of hint_stat and initialize it if required */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { @@ -687,9 +683,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, } /* search the file name for directories */ - dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, - num_entries, TYPE_ALL, &hint_opt); - + dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, &hint_opt); if (dentry < 0) return dentry; /* -error value */ From bf6c668d6ed22c7871c1125daf4139c00c8a8c5f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:37:17 +0900 Subject: [PATCH 054/141] exfat: remove argument 'size' from exfat_truncate() argument 'size' is not used in exfat_truncate(), remove it. Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- file.c | 4 ++-- inode.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 10621f1..fcfddcd 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -472,7 +472,7 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); /* file.c */ extern const struct file_operations exfat_file_operations; int __exfat_truncate(struct inode *inode, loff_t new_size); -void exfat_truncate(struct inode *inode, loff_t size); +void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, diff --git a/file.c b/file.c index 6e37277..efef7a9 100644 --- a/file.c +++ b/file.c @@ -196,7 +196,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) return 0; } -void exfat_truncate(struct inode *inode, loff_t size) +void exfat_truncate(struct inode *inode) { struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -362,7 +362,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) * __exfat_write_inode() is called from exfat_truncate(), inode * is already written by it, so mark_inode_dirty() is unneeded. */ - exfat_truncate(inode, attr->ia_size); + exfat_truncate(inode); up_write(&EXFAT_I(inode)->truncate_lock); } else mark_inode_dirty(inode); diff --git a/inode.c b/inode.c index d0bfa26..4bab1e4 100644 --- a/inode.c +++ b/inode.c @@ -383,7 +383,7 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif - exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); + exfat_truncate(inode); } } From ba9c0dc4c0eadef390d64ae495a2279592d93ec6 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:39:56 +0900 Subject: [PATCH 055/141] exfat: remove i_size_write() from __exfat_truncate() The file/directory size is updated into inode by i_size_write() before __exfat_truncate() is called, so it is redundant to re-update by i_size_write() in __exfat_truncate(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- file.c | 8 +++----- inode.c | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index fcfddcd..42a9a46 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -471,7 +471,7 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); /* file.c */ extern const struct file_operations exfat_file_operations; -int __exfat_truncate(struct inode *inode, loff_t new_size); +int __exfat_truncate(struct inode *inode); void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) diff --git a/file.c b/file.c index efef7a9..51de3b3 100644 --- a/file.c +++ b/file.c @@ -100,7 +100,7 @@ static int exfat_sanitize_mode(const struct exfat_sb_info *sbi, } /* resize the file length */ -int __exfat_truncate(struct inode *inode, loff_t new_size) +int __exfat_truncate(struct inode *inode) { unsigned int num_clusters_new, num_clusters_phys; unsigned int last_clu = EXFAT_FREE_CLUSTER; @@ -120,7 +120,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags); - if (new_size > 0) { + if (i_size_read(inode) > 0) { /* * Truncate FAT chain num_clusters after the first cluster * num_clusters = min(new, phys); @@ -150,8 +150,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) ei->start_clu = EXFAT_EOF_CLUSTER; } - i_size_write(inode, new_size); - if (ei->type == TYPE_FILE) ei->attr |= ATTR_ARCHIVE; @@ -218,7 +216,7 @@ void exfat_truncate(struct inode *inode) goto write_size; } - err = __exfat_truncate(inode, i_size_read(inode)); + err = __exfat_truncate(inode); if (err) goto write_size; diff --git a/inode.c b/inode.c index 4bab1e4..af305df 100644 --- a/inode.c +++ b/inode.c @@ -698,7 +698,7 @@ void exfat_evict_inode(struct inode *inode) if (!inode->i_nlink) { i_size_write(inode, 0); mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); - __exfat_truncate(inode, 0); + __exfat_truncate(inode); mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); } From eb7de3a45bed80ba39026f9f28c23557f893887e Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 15 Dec 2022 08:20:23 +0900 Subject: [PATCH 056/141] exfat: fix overflow in sector and cluster conversion According to the exFAT specification, there are at most 2^32-11 clusters in a volume. so using 'int' is not enough for cluster index, the return value type of exfat_sector_to_cluster() should be 'unsigned int'. Signed-off-by: Yuezhang Mo Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index 42a9a46..02f9529 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -423,7 +423,7 @@ static inline sector_t exfat_cluster_to_sector(struct exfat_sb_info *sbi, sbi->data_start_sector; } -static inline int exfat_sector_to_cluster(struct exfat_sb_info *sbi, +static inline unsigned int exfat_sector_to_cluster(struct exfat_sb_info *sbi, sector_t sec) { return ((sec - sbi->data_start_sector) >> sbi->sect_per_clus_bits) + From c0bbea4d949af7465901bf5c8d8964aaa6ba8479 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 15 Dec 2022 08:21:14 +0900 Subject: [PATCH 057/141] exfat: reuse exfat_find_location() to simplify exfat_get_dentry_set() In exfat_get_dentry_set(), part of the code is the same as exfat_find_location(), reuse exfat_find_location() to simplify exfat_get_dentry_set(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/dir.c b/dir.c index 8cbff27..a324d2b 100644 --- a/dir.c +++ b/dir.c @@ -829,7 +829,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, unsigned int type) { int ret, i, num_bh; - unsigned int off, byte_offset, clu = 0; + unsigned int off; sector_t sec; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_dentry *ep; @@ -842,27 +842,16 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, return -EIO; } - byte_offset = EXFAT_DEN_TO_B(entry); - ret = exfat_walk_fat_chain(sb, p_dir, byte_offset, &clu); + ret = exfat_find_location(sb, p_dir, entry, &sec, &off); if (ret) return ret; memset(es, 0, sizeof(*es)); es->sb = sb; es->modified = false; - - /* byte offset in cluster */ - byte_offset = EXFAT_CLU_OFFSET(byte_offset, sbi); - - /* byte offset in sector */ - off = EXFAT_BLK_OFFSET(byte_offset, sb); es->start_off = off; es->bh = es->__bh; - /* sector offset in cluster */ - sec = EXFAT_B_TO_BLK(byte_offset, sb); - sec += exfat_cluster_to_sector(sbi, clu); - bh = sb_bread(sb, sec); if (!bh) return -EIO; @@ -889,6 +878,8 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, for (i = 1; i < num_bh; i++) { /* get the next sector */ if (exfat_is_last_sector_in_cluster(sbi, sec)) { + unsigned int clu = exfat_sector_to_cluster(sbi, sec); + if (p_dir->flags == ALLOC_NO_FAT_CHAIN) clu++; else if (exfat_get_next_cluster(sb, &clu)) From bd80b9acb6f240fc046b58defd23f25397fd7651 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 26 Dec 2022 21:24:51 +0900 Subject: [PATCH 058/141] exfat: fix unexpected EOF while reading dir If the position is not aligned with the dentry size, the return value of readdir() will be NULL and errno is 0, which means the end of the directory stream is reached. If the position is aligned with dentry size, but there is no file or directory at the position, exfat_readdir() will continue to get dentry from the next dentry. So the dentry gotten by readdir() may not be at the position. After this commit, if the position is not aligned with the dentry size, round the position up to the dentry size and continue to get the dentry. Cc: stable@vger.kernel.org # v5.7+ Reported-by: Wang Yugui Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dir.c b/dir.c index a324d2b..e24a0a5 100644 --- a/dir.c +++ b/dir.c @@ -235,10 +235,7 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) fake_offset = 1; } - if (cpos & (DENTRY_SIZE - 1)) { - err = -ENOENT; - goto unlock; - } + cpos = round_up(cpos, DENTRY_SIZE); /* name buffer should be allocated before use */ err = exfat_alloc_namebuf(nb); From 118b6d539df2e5c46a642583149d43fcc69f996d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 26 Dec 2022 21:25:26 +0900 Subject: [PATCH 059/141] exfat: fix reporting fs error when reading dir beyond EOF Since seekdir() does not check whether the position is valid, the position may exceed the size of the directory. We found that for a directory with discontinuous clusters, if the position exceeds the size of the directory and the excess size is greater than or equal to the cluster size, exfat_readdir() will return -EIO, causing a file system error and making the file system unavailable. Reproduce this bug by: seekdir(dir, dir_size + cluster_size); dirent = readdir(dir); The following log will be printed if mount with 'errors=remount-ro'. [11166.712896] exFAT-fs (sdb1): error, invalid access to FAT (entry 0xffffffff) [11166.712905] exFAT-fs (sdb1): Filesystem has been set read-only Fixes: 1e5654de0f51 ("exfat: handle wrong stream entry size in exfat_readdir()") Cc: stable@vger.kernel.org # v5.7+ Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dir.c b/dir.c index e24a0a5..a73cb34 100644 --- a/dir.c +++ b/dir.c @@ -101,7 +101,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent clu.dir = ei->hint_bmap.clu; } - while (clu_offset > 0) { + while (clu_offset > 0 && clu.dir != EXFAT_EOF_CLUSTER) { if (exfat_get_next_cluster(sb, &(clu.dir))) return -EIO; From 17d8731ce12502b50b468d443ed5fa2094a80132 Mon Sep 17 00:00:00 2001 From: Sungjong Seo Date: Fri, 30 Dec 2022 10:22:05 +0900 Subject: [PATCH 060/141] exfat: redefine DIR_DELETED as the bad cluster number When a file or a directory is deleted, the hint for the cluster of its parent directory in its in-memory inode is set as DIR_DELETED. Therefore, DIR_DELETED must be one of invalid cluster numbers. According to the exFAT specification, a volume can have at most 2^32-11 clusters. However, DIR_DELETED is wrongly defined as 0xFFFF0321, which could be a valid cluster number. To fix it, let's redefine DIR_DELETED as 0xFFFFFFF7, the bad cluster number. Fixes: 1acf1a564b60 ("exfat: add in-memory and on-disk structures and headers") Cc: stable@vger.kernel.org # v5.7+ Reported-by: Yuezhang Mo Signed-off-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index 02f9529..1bea841 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -63,7 +63,7 @@ enum { #define ES_IDX_LAST_FILENAME(name_len) \ (ES_IDX_FIRST_FILENAME + EXFAT_FILENAME_ENTRY_NUM(name_len) - 1) -#define DIR_DELETED 0xFFFF0321 +#define DIR_DELETED 0xFFFFFFF7 /* type values */ #define TYPE_UNUSED 0x0000 From 3eef540417547ae7eea6d8bcae97b797e8fb2cbb Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 8 Jan 2023 18:14:43 +0900 Subject: [PATCH 061/141] exfat: fix inode->i_blocks for non-512 byte sector size device inode->i_blocks is not real number of blocks, but 512 byte ones. Fixes: 98d917047e8b ("exfat: add file operations") Cc: stable@vger.kernel.org # v5.7+ Reported-by: Wang Yugui Tested-by: Wang Yugui Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- file.c | 3 +-- inode.c | 6 ++---- namei.c | 2 +- super.c | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/file.c b/file.c index 51de3b3..a038853 100644 --- a/file.c +++ b/file.c @@ -220,8 +220,7 @@ void exfat_truncate(struct inode *inode) if (err) goto write_size; - inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> - inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; write_size: aligned_size = i_size_read(inode); if (aligned_size & (blocksize - 1)) { diff --git a/inode.c b/inode.c index af305df..bce152c 100644 --- a/inode.c +++ b/inode.c @@ -222,8 +222,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_clusters += num_to_be_allocated; *clu = new_clu.dir; - inode->i_blocks += - num_to_be_allocated << sbi->sect_per_clus_bits; + inode->i_blocks += EXFAT_CLU_TO_B(num_to_be_allocated, sbi) >> 9; /* * Move *clu pointer along FAT chains (hole care) because the @@ -649,8 +648,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) exfat_save_attr(inode, info->attr); - inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> - inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; inode->i_mtime = info->mtime; inode->i_ctime = info->mtime; ei->i_crtime = info->crtime; diff --git a/namei.c b/namei.c index 8814f76..a08278e 100644 --- a/namei.c +++ b/namei.c @@ -423,7 +423,7 @@ static int exfat_find_empty_entry(struct inode *inode, ei->i_size_ondisk += sbi->cluster_size; ei->i_size_aligned += sbi->cluster_size; ei->flags = p_dir->flags; - inode->i_blocks += 1 << sbi->sect_per_clus_bits; + inode->i_blocks += sbi->cluster_size >> 9; } return dentry; diff --git a/super.c b/super.c index c5d55b0..665c296 100644 --- a/super.c +++ b/super.c @@ -632,8 +632,7 @@ static int exfat_read_root(struct inode *inode) inode->i_op = &exfat_dir_inode_operations; inode->i_fop = &exfat_dir_operations; - inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> - inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; ei->i_size_aligned = i_size_read(inode); ei->i_size_ondisk = i_size_read(inode); From 47fb2035ceca46dc4ac79368e030ccb2f5a270cc Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 13 Jan 2023 22:51:20 +0900 Subject: [PATCH 062/141] exfat: handle unreconized benign secondary entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sony PXW-Z280 camera add vendor allocation entries to directory of pictures. Currently, linux exfat does not support it and the file is not visible. This patch handle vendor extension and allocation entries as unreconized benign secondary entries. As described in the specification, it is recognized but ignored, and when deleting directory entry set, the associated clusters allocation are removed as well as benign secondary directory entries. Reported-by: Barócsi Dénes Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 83 +++++++++++++++++++++++++++++++++++++---------------- exfat_fs.h | 2 ++ exfat_raw.h | 21 ++++++++++++++ 3 files changed, 81 insertions(+), 25 deletions(-) diff --git a/dir.c b/dir.c index a73cb34..f167f68 100644 --- a/dir.c +++ b/dir.c @@ -30,14 +30,15 @@ static int exfat_extract_uni_name(struct exfat_dentry *ep, } -static void exfat_get_uniname_from_ext_entry(struct super_block *sb, +static int exfat_get_uniname_from_ext_entry(struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned short *uniname) { - int i; + int i, err; struct exfat_entry_set_cache es; - if (exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES)) - return; + err = exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES); + if (err) + return err; /* * First entry : file entry @@ -57,12 +58,13 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, } exfat_put_dentry_set(&es, false); + return 0; } /* read a directory entry from the opened directory */ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_entry *dir_entry) { - int i, dentries_per_clu, num_ext; + int i, dentries_per_clu, num_ext, err; unsigned int type, clu_offset, max_dentries; struct exfat_chain dir, clu; struct exfat_uni_name uni_name; @@ -147,8 +149,12 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent 0); *uni_name.name = 0x0; - exfat_get_uniname_from_ext_entry(sb, &clu, i, + err = exfat_get_uniname_from_ext_entry(sb, &clu, i, uni_name.name); + if (err) { + brelse(bh); + continue; + } exfat_utf16_to_nls(sb, &uni_name, dir_entry->namebuf.lfn, dir_entry->namebuf.lfnbuf_len); @@ -376,6 +382,12 @@ unsigned int exfat_get_entry_type(struct exfat_dentry *ep) return TYPE_ACL; return TYPE_CRITICAL_SEC; } + + if (ep->type == EXFAT_VENDOR_EXT) + return TYPE_VENDOR_EXT; + if (ep->type == EXFAT_VENDOR_ALLOC) + return TYPE_VENDOR_ALLOC; + return TYPE_BENIGN_SEC; } @@ -529,6 +541,25 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, return ret; } +static void exfat_free_benign_secondary_clusters(struct inode *inode, + struct exfat_dentry *ep) +{ + struct super_block *sb = inode->i_sb; + struct exfat_chain dir; + unsigned int start_clu = + le32_to_cpu(ep->dentry.generic_secondary.start_clu); + u64 size = le64_to_cpu(ep->dentry.generic_secondary.size); + unsigned char flags = ep->dentry.generic_secondary.flags; + + if (!(flags & ALLOC_POSSIBLE) || !start_clu || !size) + return; + + exfat_chain_set(&dir, start_clu, + EXFAT_B_TO_CLU_ROUND_UP(size, EXFAT_SB(sb)), + flags); + exfat_free_cluster(inode, &dir); +} + int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, int entry, int num_entries, struct exfat_uni_name *p_uniname) { @@ -561,6 +592,9 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, if (!ep) return -EIO; + if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) + exfat_free_benign_secondary_clusters(inode, ep); + exfat_init_name_entry(ep, uniname); exfat_update_bh(bh, sync); brelse(bh); @@ -584,6 +618,9 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, if (!ep) return -EIO; + if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) + exfat_free_benign_secondary_clusters(inode, ep); + exfat_set_entry_type(ep, TYPE_DELETED); exfat_update_bh(bh, IS_DIRSYNC(inode)); brelse(bh); @@ -752,6 +789,7 @@ enum exfat_validate_dentry_mode { ES_MODE_GET_STRM_ENTRY, ES_MODE_GET_NAME_ENTRY, ES_MODE_GET_CRITICAL_SEC_ENTRY, + ES_MODE_GET_BENIGN_SEC_ENTRY, }; static bool exfat_validate_entry(unsigned int type, @@ -765,36 +803,33 @@ static bool exfat_validate_entry(unsigned int type, if (type != TYPE_FILE && type != TYPE_DIR) return false; *mode = ES_MODE_GET_FILE_ENTRY; - return true; + break; case ES_MODE_GET_FILE_ENTRY: if (type != TYPE_STREAM) return false; *mode = ES_MODE_GET_STRM_ENTRY; - return true; + break; case ES_MODE_GET_STRM_ENTRY: if (type != TYPE_EXTEND) return false; *mode = ES_MODE_GET_NAME_ENTRY; - return true; + break; case ES_MODE_GET_NAME_ENTRY: - if (type == TYPE_STREAM) + if (type & TYPE_BENIGN_SEC) + *mode = ES_MODE_GET_BENIGN_SEC_ENTRY; + else if (type != TYPE_EXTEND) return false; - if (type != TYPE_EXTEND) { - if (!(type & TYPE_CRITICAL_SEC)) - return false; - *mode = ES_MODE_GET_CRITICAL_SEC_ENTRY; - } - return true; - case ES_MODE_GET_CRITICAL_SEC_ENTRY: - if (type == TYPE_EXTEND || type == TYPE_STREAM) - return false; - if ((type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC) + break; + case ES_MODE_GET_BENIGN_SEC_ENTRY: + /* Assume unreconized benign secondary entry */ + if (!(type & TYPE_BENIGN_SEC)) return false; - return true; + break; default: - WARN_ON_ONCE(1); return false; } + + return true; } struct exfat_dentry *exfat_get_dentry_cached( @@ -1175,10 +1210,8 @@ int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir, type = exfat_get_entry_type(ext_ep); brelse(bh); - if (type == TYPE_EXTEND || type == TYPE_STREAM) + if (type & TYPE_CRITICAL_SEC || type & TYPE_BENIGN_SEC) count++; - else - break; } return count; } diff --git a/exfat_fs.h b/exfat_fs.h index 1bea841..1b3bfd7 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -84,6 +84,8 @@ enum { #define TYPE_PADDING 0x0402 #define TYPE_ACLTAB 0x0403 #define TYPE_BENIGN_SEC 0x0800 +#define TYPE_VENDOR_EXT 0x0801 +#define TYPE_VENDOR_ALLOC 0x0802 #define MAX_CHARSET_SIZE 6 /* max size of multi-byte character */ #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ diff --git a/exfat_raw.h b/exfat_raw.h index 7f39b1c..0ece2e4 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -27,6 +27,7 @@ ((sbi)->num_clusters - EXFAT_RESERVED_CLUSTERS) /* AllocationPossible and NoFatChain field in GeneralSecondaryFlags Field */ +#define ALLOC_POSSIBLE 0x01 #define ALLOC_FAT_CHAIN 0x01 #define ALLOC_NO_FAT_CHAIN 0x03 @@ -50,6 +51,8 @@ #define EXFAT_STREAM 0xC0 /* stream entry */ #define EXFAT_NAME 0xC1 /* file name entry */ #define EXFAT_ACL 0xC2 /* stream entry */ +#define EXFAT_VENDOR_EXT 0xE0 /* vendor extension entry */ +#define EXFAT_VENDOR_ALLOC 0xE1 /* vendor allocation entry */ #define IS_EXFAT_CRITICAL_PRI(x) (x < 0xA0) #define IS_EXFAT_BENIGN_PRI(x) (x < 0xC0) @@ -155,6 +158,24 @@ struct exfat_dentry { __le32 start_clu; __le64 size; } __packed upcase; /* up-case table directory entry */ + struct { + __u8 flags; + __u8 vendor_guid[16]; + __u8 vendor_defined[14]; + } __packed vendor_ext; /* vendor extension directory entry */ + struct { + __u8 flags; + __u8 vendor_guid[16]; + __u8 vendor_defined[2]; + __le32 start_clu; + __le64 size; + } __packed vendor_alloc; /* vendor allocation directory entry */ + struct { + __u8 flags; + __u8 custom_defined[18]; + __le32 start_clu; + __le64 size; + } __packed generic_secondary; /* generic secondary directory entry */ } __packed dentry; } __packed; From 0e44fbe12d5545cef539f30651c11e583786f59b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 13 Jan 2023 23:05:15 +0900 Subject: [PATCH 063/141] exfat: remove ->writepage Patch series "start removing writepage instances v2". The VM doesn't need or want ->writepage for writeback and is fine with just having ->writepages as long as ->migrate_folio is implemented. This series removes all ->writepage instances that use block_write_full_page directly and also have a plain mpage_writepages based ->writepages. This patch (of 7): ->writepage is a very inefficient method to write back data, and only used through write_cache_pages or a a fallback when no ->migrate_folio method is present. Set ->migrate_folio to the generic buffer_head based helper, and remove the ->writepage implementation. Link: https://lkml.kernel.org/r/20221202102644.770505-1-hch@lst.de Link: https://lkml.kernel.org/r/20221202102644.770505-2-hch@lst.de Signed-off-by: Christoph Hellwig Acked-by: Namjae Jeon Acked-by: Johannes Weiner Cc: Bob Copeland Cc: Dave Kleikamp Cc: Jan Kara Cc: Mikulas Patocka Cc: OGAWA Hirofumi Cc: Sungjong Seo Signed-off-by: Andrew Morton Signed-off-by: Namjae Jeon --- inode.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/inode.c b/inode.c index bce152c..4d4449e 100644 --- a/inode.c +++ b/inode.c @@ -360,10 +360,12 @@ static int exfat_readpages(struct file *file, struct address_space *mapping, } #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) static int exfat_writepage(struct page *page, struct writeback_control *wbc) { return block_write_full_page(page, exfat_get_block, wbc); } +#endif static int exfat_writepages(struct address_space *mapping, struct writeback_control *wbc) @@ -531,12 +533,19 @@ static const struct address_space_operations exfat_aops = { #else .readpages = exfat_readpages, #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) .writepage = exfat_writepage, +#endif .writepages = exfat_writepages, .write_begin = exfat_write_begin, .write_end = exfat_write_end, .direct_IO = exfat_direct_IO, +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) .bmap = exfat_aop_bmap +#else + .bmap = exfat_aop_bmap, + .migrate_folio = buffer_migrate_folio, +#endif }; static inline unsigned long exfat_hash(loff_t i_pos) From cd17de5006fa514dff6d25d1de8caff9f0c4723f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 23 Feb 2023 23:08:08 +0900 Subject: [PATCH 064/141] exfat: remove unneeded code from exfat_alloc_cluster() In the removed code, num_clusters is 0, nothing is done in exfat_chain_cont_cluster(), so it is unneeded, remove it. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- fatent.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/fatent.c b/fatent.c index ded465b..e22a77b 100644 --- a/fatent.c +++ b/fatent.c @@ -362,14 +362,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, exfat_err(sb, "hint_cluster is invalid (%u)", hint_clu); hint_clu = EXFAT_FIRST_CLUSTER; - if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { - if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) { - ret = -EIO; - goto unlock; - } - p_chain->flags = ALLOC_FAT_CHAIN; - } + p_chain->flags = ALLOC_FAT_CHAIN; } p_chain->dir = EXFAT_EOF_CLUSTER; From 7a55bf46a5e4d10662cb75ca54b88b59e5621dc9 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Tue, 28 Feb 2023 15:30:06 +0900 Subject: [PATCH 065/141] exfat: don't print error log in normal case When allocating a new cluster, exFAT first allocates from the next cluster of the last cluster of the file. If the last cluster of the file is the last cluster of the volume, allocate from the first cluster. This is a normal case, but the following error log will be printed. It makes users confused, so this commit removes the error log. [1960905.181545] exFAT-fs (sdb1): hint_cluster is invalid (262130) Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- fatent.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fatent.c b/fatent.c index e22a77b..f46bbce 100644 --- a/fatent.c +++ b/fatent.c @@ -359,8 +359,9 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, /* check cluster validation */ if (!is_valid_cluster(sbi, hint_clu)) { - exfat_err(sb, "hint_cluster is invalid (%u)", - hint_clu); + if (hint_clu != sbi->num_clusters) + exfat_err(sb, "hint_cluster is invalid (%u), rewind to the first cluster", + hint_clu); hint_clu = EXFAT_FIRST_CLUSTER; p_chain->flags = ALLOC_FAT_CHAIN; } From 03ab6855b1c9a69df02efdab9df8d0570a933b70 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 23 Feb 2023 23:09:50 +0900 Subject: [PATCH 066/141] exfat: fix the newly allocated clusters are not freed in error handling In error handling 'free_cluster', before num_alloc clusters allocated, p_chain->size will not updated and always 0, thus the newly allocated clusters are not freed. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- fatent.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/fatent.c b/fatent.c index f46bbce..c13a04e 100644 --- a/fatent.c +++ b/fatent.c @@ -322,7 +322,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, struct exfat_chain *p_chain, bool sync_bmap) { int ret = -ENOSPC; - unsigned int num_clusters = 0, total_cnt; + unsigned int total_cnt; unsigned int hint_clu, new_clu, last_clu = EXFAT_EOF_CLUSTER; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -373,7 +373,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, if (new_clu != hint_clu && p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) { + p_chain->size)) { ret = -EIO; goto free_cluster; } @@ -386,8 +386,6 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, goto free_cluster; } - num_clusters++; - /* update FAT table */ if (p_chain->flags == ALLOC_FAT_CHAIN) { if (exfat_ent_set(sb, new_clu, EXFAT_EOF_CLUSTER)) { @@ -404,13 +402,14 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, goto free_cluster; } } + p_chain->size++; + last_clu = new_clu; - if (--num_alloc == 0) { + if (p_chain->size == num_alloc) { sbi->clu_srch_ptr = hint_clu; - sbi->used_clusters += num_clusters; + sbi->used_clusters += num_alloc; - p_chain->size += num_clusters; mutex_unlock(&sbi->bitmap_lock); return 0; } @@ -421,7 +420,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) { + p_chain->size)) { ret = -EIO; goto free_cluster; } @@ -430,8 +429,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } } free_cluster: - if (num_clusters) - __exfat_free_cluster(inode, p_chain); + __exfat_free_cluster(inode, p_chain); unlock: mutex_unlock(&sbi->bitmap_lock); return ret; From f4f94fa7203c929b81767023ba25a7d8c6c3fe51 Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:04:35 +0900 Subject: [PATCH 067/141] exfat: fs: port ->setattr() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- exfat_fs.h | 5 +++++ file.c | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index 1b3bfd7..54d5314 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -477,8 +477,13 @@ int __exfat_truncate(struct inode *inode); void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr); +#else int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, struct iattr *attr); +#endif int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); diff --git a/file.c b/file.c index a038853..8a04b03 100644 --- a/file.c +++ b/file.c @@ -273,8 +273,13 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) +#else int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, struct iattr *attr) +#endif #else int exfat_setattr(struct dentry *dentry, struct iattr *attr) #endif @@ -304,7 +309,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37))) || \ (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + error = setattr_prepare(&nop_mnt_idmap, dentry, attr); +#else error = setattr_prepare(&init_user_ns, dentry, attr); +#endif #else error = setattr_prepare(dentry, attr); #endif @@ -342,7 +351,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + setattr_copy(&nop_mnt_idmap, inode, attr); +#else setattr_copy(&init_user_ns, inode, attr); +#endif #else setattr_copy(inode, attr); #endif From eb2bd5c43a370cf581f151a7e838930b53723f79 Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:08:25 +0900 Subject: [PATCH 068/141] exfat: fs: port ->getattr() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- exfat_fs.h | 5 ++++- file.c | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index 54d5314..b79f2fe 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -480,13 +480,16 @@ void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); +int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags); #else int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, struct iattr *attr); -#endif int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); +#endif #else int exfat_setattr(struct dentry *dentry, struct iattr *attr); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) diff --git a/file.c b/file.c index 8a04b03..a08076f 100644 --- a/file.c +++ b/file.c @@ -237,9 +237,15 @@ void exfat_truncate(struct inode *inode) } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags) +#else int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags) +#endif #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) int exfat_getattr(const struct path *path, struct kstat *stat, @@ -258,7 +264,11 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + generic_fillattr(&nop_mnt_idmap, inode, stat); +#else generic_fillattr(&init_user_ns, inode, stat); +#endif #else generic_fillattr(inode, stat); #endif From 1610136b5648f95c3d94319206e91fb95117748b Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:10:21 +0900 Subject: [PATCH 069/141] exfat: fs: port ->create() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- namei.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/namei.c b/namei.c index a08278e..c766273 100644 --- a/namei.c +++ b/namei.c @@ -579,8 +579,13 @@ static int exfat_add_entry(struct inode *inode, const char *path, } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +#else static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) +#endif #else static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) From 88c55a85d0b9f07266804a02522d0843ef10eed4 Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:12:01 +0900 Subject: [PATCH 070/141] exfat: fs: port ->mkdir() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- namei.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/namei.c b/namei.c index c766273..f590de3 100644 --- a/namei.c +++ b/namei.c @@ -918,8 +918,13 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static int exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +#else static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, struct dentry *dentry, umode_t mode) +#endif #else static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #endif From d8b0edab69ad7938fa5f664f82228eb0b63ee70c Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:33:46 +0900 Subject: [PATCH 071/141] exfat: fs: port ->rename() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- namei.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/namei.c b/namei.c index f590de3..e6971c7 100644 --- a/namei.c +++ b/namei.c @@ -1412,10 +1412,17 @@ static int __exfat_rename(struct inode *old_parent_inode, } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static int exfat_rename(struct mnt_idmap *idmap, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +#else static int exfat_rename(struct user_namespace *mnt_userns, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) +#endif #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, From 796bbd07266efa296683a29b31a7f8a2625de00d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Jul 2023 22:35:23 +0900 Subject: [PATCH 072/141] exfat: fs: build the legacy direct I/O code conditionally Add a new LEGACY_DIRECT_IO config symbol that is only selected by the file systems that still use the legacy blockdev_direct_IO code, so that kernels without support for those file systems don't need to build the code. Signed-off-by: Christoph Hellwig Reviewed-by: Jan Kara Reviewed-by: Eric Biggers Link: https://lore.kernel.org/r/20230125065839.191256-3-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/Kconfig b/Kconfig index 2d3636d..b3e5e9e 100644 --- a/Kconfig +++ b/Kconfig @@ -3,6 +3,7 @@ config EXFAT_FS tristate "exFAT filesystem support" select NLS + select LEGACY_DIRECT_IO help This allows you to mount devices formatted with the exFAT file system. exFAT is typically used on SD-Cards or USB sticks. From 331d3f5c425717085a825253f6aeb99af6848099 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 11 Jul 2023 22:27:53 +0900 Subject: [PATCH 073/141] exfat: splice: Use filemap_splice_read() instead of generic_file_splice_read() Replace pointers to generic_file_splice_read() with calls to filemap_splice_read(). Signed-off-by: David Howells Reviewed-by: Christoph Hellwig Reviewed-by: Christian Brauner cc: Jens Axboe cc: Al Viro cc: David Hildenbrand cc: John Hubbard cc: linux-mm@kvack.org cc: linux-block@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Link: https://lore.kernel.org/r/20230522135018.2742245-29-dhowells@redhat.com Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/file.c b/file.c index a08076f..e35998f 100644 --- a/file.c +++ b/file.c @@ -483,7 +483,11 @@ const struct file_operations exfat_file_operations = { #endif .mmap = generic_file_mmap, .fsync = exfat_file_fsync, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) + .splice_read = filemap_splice_read, +#else .splice_read = generic_file_splice_read, +#endif .splice_write = iter_file_splice_write, }; From 7d8b1148d8d3cfb68eee602f1db4966673d89d20 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 12 Jul 2023 23:31:31 +0900 Subject: [PATCH 074/141] exfat: use kvmalloc_array/kvfree instead of kmalloc_array/kfree The call stack shown below is a scenario in the Linux 4.19 kernel. Allocating memory failed where exfat fs use kmalloc_array due to system memory fragmentation, while the u-disk was inserted without recognition. Devices such as u-disk using the exfat file system are pluggable and may be insert into the system at any time. However, long-term running systems cannot guarantee the continuity of physical memory. Therefore, it's necessary to address this issue. Binder:2632_6: page allocation failure: order:4, mode:0x6040c0(GFP_KERNEL|__GFP_COMP), nodemask=(null) Call trace: [242178.097582] dump_backtrace+0x0/0x4 [242178.097589] dump_stack+0xf4/0x134 [242178.097598] warn_alloc+0xd8/0x144 [242178.097603] __alloc_pages_nodemask+0x1364/0x1384 [242178.097608] kmalloc_order+0x2c/0x510 [242178.097612] kmalloc_order_trace+0x40/0x16c [242178.097618] __kmalloc+0x360/0x408 [242178.097624] load_alloc_bitmap+0x160/0x284 [242178.097628] exfat_fill_super+0xa3c/0xe7c [242178.097635] mount_bdev+0x2e8/0x3a0 [242178.097638] exfat_fs_mount+0x40/0x50 [242178.097643] mount_fs+0x138/0x2e8 [242178.097649] vfs_kern_mount+0x90/0x270 [242178.097655] do_mount+0x798/0x173c [242178.097659] ksys_mount+0x114/0x1ac [242178.097665] __arm64_sys_mount+0x24/0x34 [242178.097671] el0_svc_common+0xb8/0x1b8 [242178.097676] el0_svc_handler+0x74/0x90 [242178.097681] el0_svc+0x8/0x340 By analyzing the exfat code,we found that continuous physical memory is not required here,so kvmalloc_array is used can solve this problem. Signed-off-by: gaoming Signed-off-by: Namjae Jeon --- balloc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/balloc.c b/balloc.c index 30c2414..21a865e 100644 --- a/balloc.c +++ b/balloc.c @@ -75,8 +75,12 @@ static int exfat_allocate_bitmap(struct super_block *sb, } sbi->map_sectors = ((need_map_size - 1) >> (sb->s_blocksize_bits)) + 1; - sbi->vol_amap = kmalloc_array(sbi->map_sectors, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + sbi->vol_amap = kvmalloc_array(sbi->map_sectors, sizeof(struct buffer_head *), GFP_KERNEL); +#else + sbi->vol_amap = vmalloc(sbi->map_sectors * sizeof(struct buffer_head *)); +#endif if (!sbi->vol_amap) return -ENOMEM; @@ -90,7 +94,7 @@ static int exfat_allocate_bitmap(struct super_block *sb, while (j < i) brelse(sbi->vol_amap[j++]); - kfree(sbi->vol_amap); + kvfree(sbi->vol_amap); sbi->vol_amap = NULL; return -EIO; } @@ -144,7 +148,7 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) for (i = 0; i < sbi->map_sectors; i++) __brelse(sbi->vol_amap[i]); - kfree(sbi->vol_amap); + kvfree(sbi->vol_amap); } int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync) From 17c7881225a35acdea24e3ca72ed94ae2918c7d1 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 14 Jul 2023 17:08:12 +0900 Subject: [PATCH 075/141] exfat: github action: make space for running xfstests github action seems to decrease disk space of each users. This patch try to remove file creation test and reset test images in the middle of testing xfstests Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 476c746..375c800 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -78,18 +78,20 @@ jobs: cd .. sudo umount ./full_test/ sudo fsck.exfat /dev/loop22 + sudo losetup -d /dev/loop22 + rm full_test.img - name: xfstest tests run: | + cd exfat-testsuites/ + tar xzvf xfstests-exfat.tgz > /dev/null + cd xfstests-exfat + make -j$((`nproc`+1)) > /dev/null truncate -s 100G test.img truncate -s 100G scratch.img sudo losetup /dev/loop20 test.img sudo losetup /dev/loop21 scratch.img sudo mkfs.exfat /dev/loop20 sudo mkfs.exfat /dev/loop21 - cd exfat-testsuites/ - tar xzvf xfstests-exfat.tgz > /dev/null - cd xfstests-exfat - make -j$((`nproc`+1)) > /dev/null sudo ./check generic/001 sudo ./check generic/006 sudo ./check generic/007 @@ -136,6 +138,16 @@ jobs: sudo ./check generic/211 sudo ./check generic/212 sudo ./check generic/215 + sudo losetup -d /dev/loop20 + sudo losetup -d /dev/loop21 + rm test.img + rm scratch.img + truncate -s 100G test.img + truncate -s 100G scratch.img + sudo losetup /dev/loop20 test.img + sudo losetup /dev/loop21 scratch.img + sudo mkfs.exfat /dev/loop20 + sudo mkfs.exfat /dev/loop21 sudo ./check generic/221 sudo ./check generic/239 sudo ./check generic/240 From 0bf9ccffd2f90dfdd6a26814e77750c68d4598e6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 13 Jul 2023 16:40:35 +0900 Subject: [PATCH 076/141] exfat: check if filename entries exceeds max filename length exfat_extract_uni_name copies characters from a given file name entry into the 'uniname' variable. This variable is actually defined on the stack of the exfat_readdir() function. According to the definition of the 'exfat_uni_name' type, the file name should be limited 255 characters (+ null teminator space), but the exfat_get_uniname_from_ext_entry() function can write more characters because there is no check if filename entries exceeds max filename length. This patch add the check not to copy filename characters when exceeding max filename length. Cc: stable@vger.kernel.org Cc: Yuezhang Mo Reported-by: Maxim Suhanov Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dir.c b/dir.c index f167f68..fd721ed 100644 --- a/dir.c +++ b/dir.c @@ -35,6 +35,7 @@ static int exfat_get_uniname_from_ext_entry(struct super_block *sb, { int i, err; struct exfat_entry_set_cache es; + unsigned int uni_len = 0, len; err = exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES); if (err) @@ -53,7 +54,10 @@ static int exfat_get_uniname_from_ext_entry(struct super_block *sb, if (exfat_get_entry_type(ep) != TYPE_EXTEND) break; - exfat_extract_uni_name(ep, uniname); + len = exfat_extract_uni_name(ep, uniname); + uni_len += len; + if (len != EXFAT_FILE_NAME_LEN || uni_len >= MAX_NAME_LENGTH) + break; uniname += EXFAT_FILE_NAME_LEN; } @@ -1090,7 +1094,8 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, if (entry_type == TYPE_EXTEND) { unsigned short entry_uniname[16], unichar; - if (step != DIRENT_STEP_NAME) { + if (step != DIRENT_STEP_NAME || + name_len >= MAX_NAME_LENGTH) { step = DIRENT_STEP_FILE; continue; } From 803d7143748976653baf72be77420234d6aba4e4 Mon Sep 17 00:00:00 2001 From: Sungjong Seo Date: Fri, 14 Jul 2023 23:45:15 +0900 Subject: [PATCH 077/141] exfat: release s_lock before calling dir_emit() There is a potential deadlock reported by syzbot as below: ====================================================== WARNING: possible circular locking dependency detected 6.4.0-next-20230707-syzkaller #0 Not tainted ------------------------------------------------------ syz-executor330/5073 is trying to acquire lock: ffff8880218527a0 (&mm->mmap_lock){++++}-{3:3}, at: mmap_read_lock_killable include/linux/mmap_lock.h:151 [inline] ffff8880218527a0 (&mm->mmap_lock){++++}-{3:3}, at: get_mmap_lock_carefully mm/memory.c:5293 [inline] ffff8880218527a0 (&mm->mmap_lock){++++}-{3:3}, at: lock_mm_and_find_vma+0x369/0x510 mm/memory.c:5344 but task is already holding lock: ffff888019f760e0 (&sbi->s_lock){+.+.}-{3:3}, at: exfat_iterate+0x117/0xb50 fs/exfat/dir.c:232 which lock already depends on the new lock. Chain exists of: &mm->mmap_lock --> mapping.invalidate_lock#3 --> &sbi->s_lock Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&sbi->s_lock); lock(mapping.invalidate_lock#3); lock(&sbi->s_lock); rlock(&mm->mmap_lock); Let's try to avoid above potential deadlock condition by moving dir_emit*() out of sbi->s_lock coverage. Fixes: ca06197382bd ("exfat: add directory operations") Cc: stable@vger.kernel.org #v5.7+ Reported-by: syzbot+1741a5d9b79989c10bdc@syzkaller.appspotmail.com Link: https://lore.kernel.org/lkml/00000000000078ee7e060066270b@google.com/T/#u Signed-off-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/dir.c b/dir.c index fd721ed..bf8eb5d 100644 --- a/dir.c +++ b/dir.c @@ -219,7 +219,10 @@ static void exfat_free_namebuf(struct exfat_dentry_namebuf *nb) exfat_init_namebuf(nb); } -/* skip iterating emit_dots when dir is empty */ +/* + * Before calling dir_emit*(), sbi->s_lock should be released + * because page fault can occur in dir_emit*(). + */ #define ITER_POS_FILLED_DOTS (2) static int exfat_iterate(struct file *filp, struct dir_context *ctx) { @@ -234,11 +237,10 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) int err = 0, fake_offset = 0; exfat_init_namebuf(nb); - mutex_lock(&EXFAT_SB(sb)->s_lock); cpos = ctx->pos; if (!dir_emit_dots(filp, ctx)) - goto unlock; + goto out; if (ctx->pos == ITER_POS_FILLED_DOTS) { cpos = 0; @@ -250,16 +252,18 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) /* name buffer should be allocated before use */ err = exfat_alloc_namebuf(nb); if (err) - goto unlock; + goto out; get_new: + mutex_lock(&EXFAT_SB(sb)->s_lock); + if (ei->flags == ALLOC_NO_FAT_CHAIN && cpos >= i_size_read(inode)) goto end_of_dir; err = exfat_readdir(inode, &cpos, &de); if (err) { /* - * At least we tried to read a sector. Move cpos to next sector - * position (should be aligned). + * At least we tried to read a sector. + * Move cpos to next sector position (should be aligned). */ if (err == -EIO) { cpos += 1 << (sb->s_blocksize_bits); @@ -282,16 +286,10 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) inum = iunique(sb, EXFAT_ROOT_INO); } - /* - * Before calling dir_emit(), sb_lock should be released. - * Because page fault can occur in dir_emit() when the size - * of buffer given from user is larger than one page size. - */ mutex_unlock(&EXFAT_SB(sb)->s_lock); if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, (de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) - goto out_unlocked; - mutex_lock(&EXFAT_SB(sb)->s_lock); + goto out; ctx->pos = cpos; goto get_new; @@ -299,9 +297,8 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) if (!cpos && fake_offset) cpos = ITER_POS_FILLED_DOTS; ctx->pos = cpos; -unlock: mutex_unlock(&EXFAT_SB(sb)->s_lock); -out_unlocked: +out: /* * To improve performance, free namebuf after unlock sb_lock. * If namebuf is not allocated, this function do nothing From 2a0023bc28e080f2317a4eb293cf28c3126948de Mon Sep 17 00:00:00 2001 From: HiGarfield Date: Sat, 15 Jul 2023 23:11:12 +0800 Subject: [PATCH 078/141] exfat: add necessary header for vmalloc This fixes building on Linux 4.4. Signed-off-by: HiGarfield Signed-off-by: Namjae Jeon --- balloc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/balloc.c b/balloc.c index 21a865e..c226d5d 100644 --- a/balloc.c +++ b/balloc.c @@ -12,6 +12,7 @@ #else #include #endif +#include #include "exfat_raw.h" #include "exfat_fs.h" From 967fa6b9d1a96b37836e11908b7379867810953b Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 30 Aug 2023 22:20:14 +0900 Subject: [PATCH 079/141] exfat: vfs: get rid of old '->iterate' directory operation All users now just use '->iterate_shared()', which only takes the directory inode lock for reading. Filesystems that never got convered to shared mode now instead use a wrapper that drops the lock, re-takes it in write mode, calls the old function, and then downgrades the lock back to read mode. This way the VFS layer and other callers no longer need to care about filesystems that never got converted to the modern era. The filesystems that use the new wrapper are ceph, coda, exfat, jfs, ntfs, ocfs2, overlayfs, and vboxsf. Honestly, several of them look like they really could just iterate their directories in shared mode and skip the wrapper entirely, but the point of this change is to not change semantics or fix filesystems that haven't been fixed in the last 7+ years, but to finally get rid of the dual iterators. Signed-off-by: Linus Torvalds Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- dir.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dir.c b/dir.c index bf8eb5d..a9dc79e 100644 --- a/dir.c +++ b/dir.c @@ -307,10 +307,17 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) return err; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) +WRAP_DIR_ITER(exfat_iterate) // FIXME! +#endif const struct file_operations exfat_dir_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) + .iterate_shared = shared_exfat_iterate, +#else .iterate = exfat_iterate, +#endif .unlocked_ioctl = exfat_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = exfat_compat_ioctl, From cc9879a6ce62c90737c19635bfc74c3c93e2b597 Mon Sep 17 00:00:00 2001 From: Jan Cincera Date: Mon, 4 Sep 2023 11:05:23 +0900 Subject: [PATCH 080/141] exfat: add ioctls for accessing attributes Add GET and SET attributes ioctls to enable attribute modification. We already do this in FAT and a few userspace utils made for it would benefit from this also working on exFAT, namely fatattr. Signed-off-by: Jan Cincera Signed-off-by: Namjae Jeon --- exfat_fs.h | 6 +++ file.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index b79f2fe..97ee684 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -162,6 +162,12 @@ enum { #define DIR_CACHE_SIZE \ (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) +/* + * attribute ioctls, same as their FAT equivalents. + */ +#define EXFAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) +#define EXFAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32) + struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ diff --git a/file.c b/file.c index e35998f..216c38b 100644 --- a/file.c +++ b/file.c @@ -9,8 +9,11 @@ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include #endif +#include #include #include +#include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -390,6 +393,130 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) return error; } +/* + * modified ioctls from fat/file.c by Welmer Almesberger + */ +static int exfat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr) +{ + u32 attr; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + inode_lock_shared(inode); +#else + mutex_lock(&inode->i_mutex); +#endif + attr = exfat_make_attr(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + inode_unlock_shared(inode); +#else + mutex_unlock(&inode->i_mutex); +#endif + + return put_user(attr, user_attr); +} + +static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) +{ + struct inode *inode = file_inode(file); + struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); + int is_dir = S_ISDIR(inode->i_mode); + u32 attr, oldattr; + struct iattr ia; + int err; + + err = get_user(attr, user_attr); + if (err) + goto out; + + err = mnt_want_write_file(file); + if (err) + goto out; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) + inode_lock(inode); +#else + mutex_lock(&inode->i_mutex); +#endif + + oldattr = exfat_make_attr(inode); + + /* + * Mask attributes so we don't set reserved fields. + */ + attr &= (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE); + attr |= (is_dir ? ATTR_SUBDIR : 0); + + /* Equivalent to a chmod() */ + ia.ia_valid = ATTR_MODE | ATTR_CTIME; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + ia.ia_ctime = current_time(inode); +#else + ia.ia_ctime = current_fs_time(inode->i_sb); +#endif + if (is_dir) + ia.ia_mode = exfat_make_mode(sbi, attr, 0777); + else + ia.ia_mode = exfat_make_mode(sbi, attr, 0666 | (inode->i_mode & 0111)); + + /* The root directory has no attributes */ + if (inode->i_ino == EXFAT_ROOT_INO && attr != ATTR_SUBDIR) { + err = -EINVAL; + goto out_unlock_inode; + } + + if (((attr | oldattr) & ATTR_SYSTEM) && + !capable(CAP_LINUX_IMMUTABLE)) { + err = -EPERM; + goto out_unlock_inode; + } + + /* + * The security check is questionable... We single + * out the RO attribute for checking by the security + * module, just because it maps to a file mode. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + err = security_inode_setattr(file_mnt_idmap(file), + file->f_path.dentry, &ia); +#else + err = security_inode_setattr(file_mnt_user_ns(file), + file->f_path.dentry, &ia); +#endif +#else + err = security_inode_setattr(file->f_path.dentry, &ia); +#endif + if (err) + goto out_unlock_inode; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + /* This MUST be done before doing anything irreversible... */ + err = exfat_setattr(file_mnt_idmap(file), file->f_path.dentry, &ia); +#else + err = exfat_setattr(file_mnt_user_ns(file), file->f_path.dentry, &ia); +#endif +#else + err = exfat_setattr(file->f_path.dentry, &ia); +#endif + if (err) + goto out_unlock_inode; + + fsnotify_change(file->f_path.dentry, ia.ia_valid); + + exfat_save_attr(inode, attr); + mark_inode_dirty(inode); +out_unlock_inode: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) + inode_unlock(inode); +#else + mutex_unlock(&inode->i_mutex); +#endif + mnt_drop_write_file(file); +out: + return err; +} + static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) { #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) @@ -432,8 +559,13 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(filp); + u32 __user *user_attr = (u32 __user *)arg; switch (cmd) { + case EXFAT_IOCTL_GET_ATTRIBUTES: + return exfat_ioctl_get_attributes(inode, user_attr); + case EXFAT_IOCTL_SET_ATTRIBUTES: + return exfat_ioctl_set_attributes(filp, user_attr); case FITRIM: return exfat_ioctl_fitrim(inode, arg); default: From f6c705d1d91cc6624b70864a32ab55f09bdf5798 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 4 Sep 2023 11:08:43 +0900 Subject: [PATCH 081/141] exfat: support handle zero-size directory After repairing a corrupted file system with exfatprogs' fsck.exfat, zero-size directories may result. It is also possible to create zero-size directories in other exFAT implementation, such as Paragon ufsd dirver. As described in the specification, the lower directory size limits is 0 bytes. Without this commit, sub-directories and files cannot be created under a zero-size directory, and it cannot be removed. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- namei.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/namei.c b/namei.c index e6971c7..3429a8e 100644 --- a/namei.c +++ b/namei.c @@ -378,14 +378,20 @@ static int exfat_find_empty_entry(struct inode *inode, if (exfat_check_max_dentries(inode)) return -ENOSPC; - /* we trust p_dir->size regardless of FAT type */ - if (exfat_find_last_cluster(sb, p_dir, &last_clu)) - return -EIO; - /* * Allocate new cluster to this directory */ - exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags); + if (ei->start_clu != EXFAT_EOF_CLUSTER) { + /* we trust p_dir->size regardless of FAT type */ + if (exfat_find_last_cluster(sb, p_dir, &last_clu)) + return -EIO; + + exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags); + } else { + /* This directory is empty */ + exfat_chain_set(&clu, EXFAT_EOF_CLUSTER, 0, + ALLOC_NO_FAT_CHAIN); + } /* allocate a cluster */ ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode)); @@ -395,6 +401,11 @@ static int exfat_find_empty_entry(struct inode *inode, if (exfat_zeroed_cluster(inode, clu.dir)) return -EIO; + if (ei->start_clu == EXFAT_EOF_CLUSTER) { + ei->start_clu = clu.dir; + p_dir->dir = clu.dir; + } + /* append to the FAT chain */ if (clu.flags != p_dir->flags) { /* no-fat-chain bit is disabled, @@ -709,7 +720,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); info->size = le64_to_cpu(ep2->dentry.stream.valid_size); - if ((info->type == TYPE_FILE) && (info->size == 0)) { + if (info->size == 0) { info->flags = ALLOC_NO_FAT_CHAIN; info->start_clu = EXFAT_EOF_CLUSTER; } else { @@ -1000,6 +1011,9 @@ static int exfat_check_dir_empty(struct super_block *sb, dentries_per_clu = sbi->dentries_per_clu; + if (p_dir->dir == EXFAT_EOF_CLUSTER) + return 0; + exfat_chain_dup(&clu, p_dir); while (clu.dir != EXFAT_EOF_CLUSTER) { @@ -1383,7 +1397,8 @@ static int __exfat_rename(struct inode *old_parent_inode, } /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */ - if (new_entry_type == TYPE_DIR) { + if (new_entry_type == TYPE_DIR && + new_ei->start_clu != EXFAT_EOF_CLUSTER) { /* new_ei, new_clu_to_free */ struct exfat_chain new_clu_to_free; From 5bb0e9f3ec0983e2dc77e8d4bb8b43b1bcfe280c Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Tue, 5 Sep 2023 11:26:20 +0900 Subject: [PATCH 082/141] exfat: support create zero-size directory This commit adds mount option 'zero_size_dir'. If this option enabled, don't allocate a cluster to directory when creating it, and set the directory size to 0. On Windows, a cluster is allocated for a directory when it is created, so the mount option is disabled by default. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 12 ++++++------ exfat_fs.h | 2 ++ namei.c | 7 +++++-- super.c | 12 ++++++++++++ 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/dir.c b/dir.c index a9dc79e..f8a04e1 100644 --- a/dir.c +++ b/dir.c @@ -425,11 +425,13 @@ static void exfat_set_entry_type(struct exfat_dentry *ep, unsigned int type) } static void exfat_init_stream_entry(struct exfat_dentry *ep, - unsigned char flags, unsigned int start_clu, - unsigned long long size) + unsigned int start_clu, unsigned long long size) { exfat_set_entry_type(ep, TYPE_STREAM); - ep->dentry.stream.flags = flags; + if (size == 0) + ep->dentry.stream.flags = ALLOC_FAT_CHAIN; + else + ep->dentry.stream.flags = ALLOC_NO_FAT_CHAIN; ep->dentry.stream.start_clu = cpu_to_le32(start_clu); ep->dentry.stream.valid_size = cpu_to_le64(size); ep->dentry.stream.size = cpu_to_le64(size); @@ -505,9 +507,7 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, if (!ep) return -EIO; - exfat_init_stream_entry(ep, - (type == TYPE_FILE) ? ALLOC_FAT_CHAIN : ALLOC_NO_FAT_CHAIN, - start_clu, size); + exfat_init_stream_entry(ep, start_clu, size); exfat_update_bh(bh, IS_DIRSYNC(inode)); brelse(bh); diff --git a/exfat_fs.h b/exfat_fs.h index 97ee684..311d9f2 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -259,6 +259,8 @@ struct exfat_mount_options { discard:1, /* Issue discard requests on deletions */ keep_last_dots:1; /* Keep trailing periods in paths */ int time_offset; /* Offset of timestamps from UTC (in minutes) */ + /* Support creating zero-size directory, default: false */ + bool zero_size_dir; }; /* diff --git a/namei.c b/namei.c index 3429a8e..d43af64 100644 --- a/namei.c +++ b/namei.c @@ -545,7 +545,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, goto out; } - if (type == TYPE_DIR) { + if (type == TYPE_DIR && !sbi->options.zero_size_dir) { ret = exfat_alloc_new_dir(inode, &clu); if (ret) goto out; @@ -578,7 +578,10 @@ static int exfat_add_entry(struct inode *inode, const char *path, info->num_subdirs = 0; } else { info->attr = ATTR_SUBDIR; - info->start_clu = start_clu; + if (sbi->options.zero_size_dir) + info->start_clu = EXFAT_EOF_CLUSTER; + else + info->start_clu = start_clu; info->size = clu_size; info->num_subdirs = EXFAT_MIN_SUBDIR; } diff --git a/super.c b/super.c index 665c296..89f60d7 100644 --- a/super.c +++ b/super.c @@ -195,6 +195,8 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",sys_tz"); else if (opts->time_offset) seq_printf(m, ",time_offset=%d", opts->time_offset); + if (opts->zero_size_dir) + seq_puts(m, ",zero_size_dir"); return 0; } @@ -278,6 +280,7 @@ enum { Opt_keep_last_dots, Opt_sys_tz, Opt_time_offset, + Opt_zero_size_dir, /* Deprecated options */ Opt_utf8, @@ -323,6 +326,7 @@ static const struct fs_parameter_spec exfat_param_specs[] = { fsparam_flag("keep_last_dots", Opt_keep_last_dots), fsparam_flag("sys_tz", Opt_sys_tz), fsparam_s32("time_offset", Opt_time_offset), + fsparam_flag("zero_size_dir", Opt_zero_size_dir), __fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated, NULL), __fsparam(NULL, "debug", Opt_debug, fs_param_deprecated, @@ -403,6 +407,9 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) return -EINVAL; opts->time_offset = result.int_32; break; + case Opt_zero_size_dir: + opts->zero_size_dir = true; + break; case Opt_utf8: case Opt_debug: case Opt_namecase: @@ -435,6 +442,7 @@ enum { Opt_debug, Opt_namecase, Opt_codepage, + Opt_zero_size_dir, Opt_fs, }; @@ -454,6 +462,7 @@ static const match_table_t exfat_tokens = { {Opt_namecase, "namecase=%u"}, {Opt_debug, "debug"}, {Opt_utf8, "utf8"}, + {Opt_zero_size_dir, "zero_size_dir"}, {Opt_err, NULL} }; @@ -545,6 +554,9 @@ static int parse_options(struct super_block *sb, char *options, int silent, case Opt_namecase: case Opt_codepage: break; + case Opt_zero_size_dir: + opts->zero_size_dir = true; + break; default: if (!silent) { exfat_err(sb, From 8279a1a0bc27b03565da6775454e09a520b13a09 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 11 Sep 2023 16:59:54 +0900 Subject: [PATCH 083/141] exfat: github action: remove liunx-4.1 source to get more disk space Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 375c800..6587fd9 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -20,6 +20,7 @@ jobs: wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz tar xf linux-4.1.36.tar.gz mv linux-4.1.36 linux-stable + rm -rf linux-4.1.36.tar.gz - name: Prerequisite for xfstests testing run: | sudo apt-get install linux-headers-$(uname -r) @@ -47,7 +48,9 @@ jobs: make -j$((`nproc`+1)) fs/exfat/exfat.ko - name: Run xfstests testsuite run: | - cd ../linux-exfat-oot + cd .. + rm -rf linux-stable + cd linux-exfat-oot make > /dev/null sudo make install > /dev/null sudo insmod exfat.ko From 950e271397ef414c402e653e0117e1f884e06fe1 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 30 Oct 2023 21:08:39 +0900 Subject: [PATCH 084/141] exfat: use fat ioctls definitions from include/uapi/linux/msdos_fs.h Signed-off-by: Namjae Jeon --- dir.c | 8 ++++---- exfat_fs.h | 18 ++++++------------ exfat_raw.h | 19 ++++++++++--------- file.c | 16 +++++++++------- inode.c | 6 +++--- namei.c | 16 ++++++++-------- super.c | 4 ++-- 7 files changed, 42 insertions(+), 45 deletions(-) diff --git a/dir.c b/dir.c index f8a04e1..4d5b863 100644 --- a/dir.c +++ b/dir.c @@ -288,7 +288,7 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) mutex_unlock(&EXFAT_SB(sb)->s_lock); if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, - (de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) + (de.attr & EXFAT_ATTR_SUBDIR) ? DT_DIR : DT_REG)) goto out; ctx->pos = cpos; goto get_new; @@ -366,7 +366,7 @@ unsigned int exfat_get_entry_type(struct exfat_dentry *ep) if (ep->type == EXFAT_VOLUME) return TYPE_VOLUME; if (ep->type == EXFAT_FILE) { - if (le16_to_cpu(ep->dentry.file.attr) & ATTR_SUBDIR) + if (le16_to_cpu(ep->dentry.file.attr) & EXFAT_ATTR_SUBDIR) return TYPE_DIR; return TYPE_FILE; } @@ -417,10 +417,10 @@ static void exfat_set_entry_type(struct exfat_dentry *ep, unsigned int type) ep->type = EXFAT_VOLUME; } else if (type == TYPE_DIR) { ep->type = EXFAT_FILE; - ep->dentry.file.attr = cpu_to_le16(ATTR_SUBDIR); + ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_SUBDIR); } else if (type == TYPE_FILE) { ep->type = EXFAT_FILE; - ep->dentry.file.attr = cpu_to_le16(ATTR_ARCHIVE); + ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_ARCHIVE); } } diff --git a/exfat_fs.h b/exfat_fs.h index 311d9f2..9c2266a 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -162,12 +162,6 @@ enum { #define DIR_CACHE_SIZE \ (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) -/* - * attribute ioctls, same as their FAT equivalents. - */ -#define EXFAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) -#define EXFAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32) - struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ @@ -390,10 +384,10 @@ static inline int exfat_mode_can_hold_ro(struct inode *inode) static inline mode_t exfat_make_mode(struct exfat_sb_info *sbi, unsigned short attr, mode_t mode) { - if ((attr & ATTR_READONLY) && !(attr & ATTR_SUBDIR)) + if ((attr & EXFAT_ATTR_READONLY) && !(attr & EXFAT_ATTR_SUBDIR)) mode &= ~0222; - if (attr & ATTR_SUBDIR) + if (attr & EXFAT_ATTR_SUBDIR) return (mode & ~sbi->options.fs_dmask) | S_IFDIR; return (mode & ~sbi->options.fs_fmask) | S_IFREG; @@ -405,18 +399,18 @@ static inline unsigned short exfat_make_attr(struct inode *inode) unsigned short attr = EXFAT_I(inode)->attr; if (S_ISDIR(inode->i_mode)) - attr |= ATTR_SUBDIR; + attr |= EXFAT_ATTR_SUBDIR; if (exfat_mode_can_hold_ro(inode) && !(inode->i_mode & 0222)) - attr |= ATTR_READONLY; + attr |= EXFAT_ATTR_READONLY; return attr; } static inline void exfat_save_attr(struct inode *inode, unsigned short attr) { if (exfat_mode_can_hold_ro(inode)) - EXFAT_I(inode)->attr = attr & (ATTR_RWMASK | ATTR_READONLY); + EXFAT_I(inode)->attr = attr & (EXFAT_ATTR_RWMASK | EXFAT_ATTR_READONLY); else - EXFAT_I(inode)->attr = attr & ATTR_RWMASK; + EXFAT_I(inode)->attr = attr & EXFAT_ATTR_RWMASK; } static inline bool exfat_is_last_sector_in_cluster(struct exfat_sb_info *sbi, diff --git a/exfat_raw.h b/exfat_raw.h index 0ece2e4..971a1cc 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -64,15 +64,16 @@ #define CS_DEFAULT 2 /* file attributes */ -#define ATTR_READONLY 0x0001 -#define ATTR_HIDDEN 0x0002 -#define ATTR_SYSTEM 0x0004 -#define ATTR_VOLUME 0x0008 -#define ATTR_SUBDIR 0x0010 -#define ATTR_ARCHIVE 0x0020 - -#define ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ - ATTR_SUBDIR | ATTR_ARCHIVE) +#define EXFAT_ATTR_READONLY 0x0001 +#define EXFAT_ATTR_HIDDEN 0x0002 +#define EXFAT_ATTR_SYSTEM 0x0004 +#define EXFAT_ATTR_VOLUME 0x0008 +#define EXFAT_ATTR_SUBDIR 0x0010 +#define EXFAT_ATTR_ARCHIVE 0x0020 + +#define EXFAT_ATTR_RWMASK (EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM | \ + EXFAT_ATTR_VOLUME | EXFAT_ATTR_SUBDIR | \ + EXFAT_ATTR_ARCHIVE) #define BOOTSEC_JUMP_BOOT_LEN 3 #define BOOTSEC_FS_NAME_LEN 8 diff --git a/file.c b/file.c index 216c38b..59848fa 100644 --- a/file.c +++ b/file.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -154,7 +155,7 @@ int __exfat_truncate(struct inode *inode) } if (ei->type == TYPE_FILE) - ei->attr |= ATTR_ARCHIVE; + ei->attr |= EXFAT_ATTR_ARCHIVE; /* * update the directory entry @@ -443,8 +444,9 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) /* * Mask attributes so we don't set reserved fields. */ - attr &= (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE); - attr |= (is_dir ? ATTR_SUBDIR : 0); + attr &= (EXFAT_ATTR_READONLY | EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM | + EXFAT_ATTR_ARCHIVE); + attr |= (is_dir ? EXFAT_ATTR_SUBDIR : 0); /* Equivalent to a chmod() */ ia.ia_valid = ATTR_MODE | ATTR_CTIME; @@ -459,12 +461,12 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) ia.ia_mode = exfat_make_mode(sbi, attr, 0666 | (inode->i_mode & 0111)); /* The root directory has no attributes */ - if (inode->i_ino == EXFAT_ROOT_INO && attr != ATTR_SUBDIR) { + if (inode->i_ino == EXFAT_ROOT_INO && attr != EXFAT_ATTR_SUBDIR) { err = -EINVAL; goto out_unlock_inode; } - if (((attr | oldattr) & ATTR_SYSTEM) && + if (((attr | oldattr) & EXFAT_ATTR_SYSTEM) && !capable(CAP_LINUX_IMMUTABLE)) { err = -EPERM; goto out_unlock_inode; @@ -562,9 +564,9 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) u32 __user *user_attr = (u32 __user *)arg; switch (cmd) { - case EXFAT_IOCTL_GET_ATTRIBUTES: + case FAT_IOCTL_GET_ATTRIBUTES: return exfat_ioctl_get_attributes(inode, user_attr); - case EXFAT_IOCTL_SET_ATTRIBUTES: + case FAT_IOCTL_SET_ATTRIBUTES: return exfat_ioctl_set_attributes(filp, user_attr); case FITRIM: return exfat_ioctl_fitrim(inode, arg); diff --git a/inode.c b/inode.c index 4d4449e..e4dffd7 100644 --- a/inode.c +++ b/inode.c @@ -436,13 +436,13 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, if (err < len) exfat_write_failed(mapping, pos+len); - if (!(err < 0) && !(ei->attr & ATTR_ARCHIVE)) { + if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_ctime = current_time(inode); #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif - ei->attr |= ATTR_ARCHIVE; + ei->attr |= EXFAT_ATTR_ARCHIVE; mark_inode_dirty(inode); } @@ -629,7 +629,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) inode->i_generation = prandom_u32(); #endif - if (info->attr & ATTR_SUBDIR) { /* directory */ + if (info->attr & EXFAT_ATTR_SUBDIR) { /* directory */ inode->i_generation &= ~1; inode->i_mode = exfat_make_mode(sbi, info->attr, 0777); inode->i_op = &exfat_dir_inode_operations; diff --git a/namei.c b/namei.c index d43af64..5b2204c 100644 --- a/namei.c +++ b/namei.c @@ -572,12 +572,12 @@ static int exfat_add_entry(struct inode *inode, const char *path, info->type = type; if (type == TYPE_FILE) { - info->attr = ATTR_ARCHIVE; + info->attr = EXFAT_ATTR_ARCHIVE; info->start_clu = EXFAT_EOF_CLUSTER; info->size = 0; info->num_subdirs = 0; } else { - info->attr = ATTR_SUBDIR; + info->attr = EXFAT_ATTR_SUBDIR; if (sbi->options.zero_size_dir) info->start_clu = EXFAT_EOF_CLUSTER; else @@ -1177,8 +1177,8 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, *epnew = *epold; if (exfat_get_entry_type(epnew) == TYPE_FILE) { - epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); - ei->attr |= ATTR_ARCHIVE; + epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); + ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(new_bh, sync); brelse(old_bh); @@ -1209,8 +1209,8 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, ei->entry = newentry; } else { if (exfat_get_entry_type(epold) == TYPE_FILE) { - epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); - ei->attr |= ATTR_ARCHIVE; + epold->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); + ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(old_bh, sync); brelse(old_bh); @@ -1258,8 +1258,8 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, *epnew = *epmov; if (exfat_get_entry_type(epnew) == TYPE_FILE) { - epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); - ei->attr |= ATTR_ARCHIVE; + epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); + ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(new_bh, IS_DIRSYNC(inode)); brelse(mov_bh); diff --git a/super.c b/super.c index 89f60d7..caf8fd4 100644 --- a/super.c +++ b/super.c @@ -640,7 +640,7 @@ static int exfat_read_root(struct inode *inode) inode->i_version++; #endif inode->i_generation = 0; - inode->i_mode = exfat_make_mode(sbi, ATTR_SUBDIR, 0777); + inode->i_mode = exfat_make_mode(sbi, EXFAT_ATTR_SUBDIR, 0777); inode->i_op = &exfat_dir_inode_operations; inode->i_fop = &exfat_dir_operations; @@ -649,7 +649,7 @@ static int exfat_read_root(struct inode *inode) ei->i_size_aligned = i_size_read(inode); ei->i_size_ondisk = i_size_read(inode); - exfat_save_attr(inode, ATTR_SUBDIR); + exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = current_time(inode); From 98f36315064b54bf79e5db44839b800a6eccbb98 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 2 Nov 2023 22:52:14 +0900 Subject: [PATCH 085/141] exfat: fs: add CONFIG_BUFFER_HEAD Add a new config option that controls building the buffer_head code, and select it from all file systems and stacking drivers that need it. For the block device nodes and alternative iomap based buffered I/O path is provided when buffer_head support is not enabled, and iomap needs a a small tweak to define the IOMAP_F_BUFFER_HEAD flag to 0 to not call into the buffer_head code when it doesn't exist. Otherwise this is just Kconfig and ifdef changes. Signed-off-by: Christoph Hellwig Reviewed-by: Luis Chamberlain Reviewed-by: Johannes Thumshirn Link: https://lore.kernel.org/r/20230801172201.1923299-7-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/Kconfig b/Kconfig index b3e5e9e..e298907 100644 --- a/Kconfig +++ b/Kconfig @@ -2,6 +2,7 @@ config EXFAT_FS tristate "exFAT filesystem support" + select BUFFER_HEAD select NLS select LEGACY_DIRECT_IO help From 6eb9af0626759d340afd146a7c0941975edd4eea Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:03:27 +0900 Subject: [PATCH 086/141] exfat: ensure that ctime is updated whenever the mtime is When removing entries from a directory, the ctime must also be updated alongside the mtime. Signed-off-by: Jeff Layton Message-Id: <20230705190309.579783-4-jlayton@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- namei.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/namei.c b/namei.c index 5b2204c..01d1aef 100644 --- a/namei.c +++ b/namei.c @@ -903,9 +903,9 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - dir->i_mtime = dir->i_atime = current_time(dir); + dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); #else - dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; + dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) @@ -915,9 +915,9 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_mtime = inode->i_atime = current_time(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); #else - inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; + inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); @@ -1111,9 +1111,9 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - dir->i_mtime = dir->i_atime = current_time(dir); + dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); #else - dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; + dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) @@ -1124,9 +1124,9 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_mtime = inode->i_atime = current_time(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); #else - inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); From 308d7f68ce8b4285ff2eb5795b86f0a7c23678c7 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 3 Nov 2023 14:15:57 +0900 Subject: [PATCH 087/141] exfat: convert to simple_rename_timestamp A rename potentially involves updating 4 different inode timestamps. Convert to the new simple_rename_timestamp helper function. Signed-off-by: Jeff Layton Message-Id: <20230705190309.579783-10-jlayton@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- namei.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/namei.c b/namei.c index 01d1aef..ba6c604 100644 --- a/namei.c +++ b/namei.c @@ -1481,8 +1481,13 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, new_dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry); + EXFAT_I(new_dir)->i_crtime = current_time(new_dir); +#else new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = current_time(new_dir); +#endif #else new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC; @@ -1514,7 +1519,9 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, old_dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 6, 0) old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); +#endif #else old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC; #endif From c49f06ffabbbbc82d6693ae8f43ef2bfb1fa509b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:15:43 +0900 Subject: [PATCH 088/141] exfat: fs: pass the request_mask to generic_fillattr generic_fillattr just fills in the entire stat struct indiscriminately today, copying data from the inode. There is at least one attribute (STATX_CHANGE_COOKIE) that can have side effects when it is reported, and we're looking at adding more with the addition of multigrain timestamps. Add a request_mask argument to generic_fillattr and have most callers just pass in the value that is passed to getattr. Have other callers (e.g. ksmbd) just pass in STATX_BASIC_STATS. Also move the setting of STATX_CHANGE_COOKIE into generic_fillattr. Acked-by: Joseph Qi Reviewed-by: Xiubo Li Reviewed-by: "Paulo Alcantara (SUSE)" Reviewed-by: Jan Kara Signed-off-by: Jeff Layton Message-Id: <20230807-mgctime-v7-2-d1dec143a704@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/file.c b/file.c index 59848fa..c82e579 100644 --- a/file.c +++ b/file.c @@ -269,7 +269,11 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); +#else generic_fillattr(&nop_mnt_idmap, inode, stat); +#endif #else generic_fillattr(&init_user_ns, inode, stat); #endif From 0e040b023e2fb8e39719134533bbf683d262d35c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:32:26 +0900 Subject: [PATCH 089/141] exfat: convert to ctime accessor functions In later patches, we're going to change how the inode's ctime field is used. Switch to using accessor functions instead of raw accesses of inode->i_ctime. Signed-off-by: Jeff Layton Reviewed-by: Jan Kara Message-Id: <20230705190309.579783-38-jlayton@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 8 ++++++++ inode.c | 12 ++++++++++++ namei.c | 36 ++++++++++++++++++++++++++++++++++++ super.c | 4 ++++ 4 files changed, 60 insertions(+) diff --git a/file.c b/file.c index c82e579..2b74bd0 100644 --- a/file.c +++ b/file.c @@ -30,7 +30,11 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) return err; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_ctime = inode->i_mtime = current_time(inode); +#endif #else inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; #endif @@ -363,7 +367,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) if (attr->ia_valid & ATTR_SIZE) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif diff --git a/inode.c b/inode.c index e4dffd7..89e341e 100644 --- a/inode.c +++ b/inode.c @@ -380,7 +380,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) if (to > i_size_read(inode)) { truncate_pagecache(inode, i_size_read(inode)); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -438,7 +442,11 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -659,7 +667,11 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; inode->i_mtime = info->mtime; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode_set_ctime_to_ts(inode, info->mtime); +#else inode->i_ctime = info->mtime; +#endif ei->i_crtime = info->crtime; inode->i_atime = info->atime; diff --git a/namei.c b/namei.c index ba6c604..0dd33a0 100644 --- a/namei.c +++ b/namei.c @@ -625,7 +625,11 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = inode_set_ctime_current(dir); +#else dir->i_ctime = dir->i_mtime = current_time(dir); +#endif #else dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; #endif @@ -647,8 +651,12 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; @@ -903,7 +911,11 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); +#else dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); +#endif #else dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif @@ -915,7 +927,11 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif @@ -964,7 +980,11 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = inode_set_ctime_current(dir); +#else dir->i_ctime = dir->i_mtime = current_time(dir); +#endif #else dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; #endif @@ -986,8 +1006,12 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) inode->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; @@ -1111,7 +1135,11 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); +#else dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); +#endif #else dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -1124,7 +1152,11 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -1543,8 +1575,12 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, WARN_ON(new_inode->i_nlink == 0); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + EXFAT_I(new_inode)->i_crtime = current_time(new_inode); +#else new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = current_time(new_inode); +#endif #else new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = CURRENT_TIME_SEC; diff --git a/super.c b/super.c index caf8fd4..0b09959 100644 --- a/super.c +++ b/super.c @@ -651,8 +651,12 @@ static int exfat_read_root(struct inode *inode) exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = ei->i_crtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = CURRENT_TIME_SEC; From 59b349e724db3d04318735e2484f2f5aab1cdedd Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:54:56 +0900 Subject: [PATCH 090/141] exfat: convert to new timestamp accessors Convert to using the new inode timestamp accessor functions. Signed-off-by: Jeff Layton Link: https://lore.kernel.org/r/20231004185347.80880-31-jlayton@kernel.org Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- exfat_fs.h | 3 +++ file.c | 12 ++++++++++++ inode.c | 34 ++++++++++++++++++++++++++++++++++ misc.c | 10 ++++++++++ namei.c | 43 +++++++++++++++++++++++++++++++++++++++++++ super.c | 5 +++++ 6 files changed, 107 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index 9c2266a..124dfd5 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -598,6 +598,9 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_cs); void exfat_truncate_atime(struct timespec64 *ts); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) +void exfat_truncate_inode_atime(struct inode *inode); +#endif void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); #else diff --git a/file.c b/file.c index 2b74bd0..72879e4 100644 --- a/file.c +++ b/file.c @@ -31,7 +31,11 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_ctime = inode->i_mtime = current_time(inode); #endif @@ -368,7 +372,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) if (attr->ia_valid & ATTR_SIZE) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_mtime = inode->i_ctime = current_time(inode); #endif @@ -376,6 +384,9 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) setattr_copy(&nop_mnt_idmap, inode, attr); @@ -386,6 +397,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) setattr_copy(inode, attr); #endif exfat_truncate_atime(&inode->i_atime); +#endif if (attr->ia_valid & ATTR_SIZE) { error = exfat_block_truncate_page(inode, attr->ia_size); diff --git a/inode.c b/inode.c index 89e341e..7d7f2cf 100644 --- a/inode.c +++ b/inode.c @@ -28,6 +28,9 @@ int __exfat_write_inode(struct inode *inode, int sync) struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); bool is_dir = (ei->type == TYPE_DIR) ? true : false; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + struct timespec64 ts; +#endif if (inode->i_ino == EXFAT_ROOT_INO) return 0; @@ -57,6 +60,20 @@ int __exfat_write_inode(struct inode *inode, int sync) &ep->dentry.file.create_time, &ep->dentry.file.create_date, &ep->dentry.file.create_time_cs); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + exfat_set_entry_time(sbi, &ts, + &ep->dentry.file.modify_tz, + &ep->dentry.file.modify_time, + &ep->dentry.file.modify_date, + &ep->dentry.file.modify_time_cs); + inode_set_mtime_to_ts(inode, ts); + exfat_set_entry_time(sbi, &ts, + &ep->dentry.file.access_tz, + &ep->dentry.file.access_time, + &ep->dentry.file.access_date, + NULL); + inode_set_atime_to_ts(inode, ts); +#else exfat_set_entry_time(sbi, &inode->i_mtime, &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, @@ -67,6 +84,7 @@ int __exfat_write_inode(struct inode *inode, int sync) &ep->dentry.file.access_time, &ep->dentry.file.access_date, NULL); +#endif /* File size should be zero if there is no cluster allocated */ on_disk_size = i_size_read(inode); @@ -381,7 +399,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) truncate_pagecache(inode, i_size_read(inode)); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_mtime = inode->i_ctime = current_time(inode); #endif @@ -443,7 +465,11 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_mtime = inode->i_ctime = current_time(inode); #endif @@ -666,14 +692,22 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) exfat_save_attr(inode, info->attr); inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, info->mtime); +#else inode->i_mtime = info->mtime; +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode_set_ctime_to_ts(inode, info->mtime); #else inode->i_ctime = info->mtime; #endif ei->i_crtime = info->crtime; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_atime_to_ts(inode, info->atime); +#else inode->i_atime = info->atime; +#endif return 0; } diff --git a/misc.c b/misc.c index e7ae6f6..a744a1f 100644 --- a/misc.c +++ b/misc.c @@ -163,6 +163,16 @@ void exfat_truncate_atime(struct timespec *ts) ts->tv_nsec = 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) +void exfat_truncate_inode_atime(struct inode *inode) +{ + struct timespec64 atime = inode_get_atime(inode); + + exfat_truncate_atime(&atime); + inode_set_atime_to_ts(inode, atime); +} +#endif + u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type) { int i; diff --git a/namei.c b/namei.c index 0dd33a0..ef9a6df 100644 --- a/namei.c +++ b/namei.c @@ -626,7 +626,11 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); +#else dir->i_mtime = inode_set_ctime_current(dir); +#endif #else dir->i_ctime = dir->i_mtime = current_time(dir); #endif @@ -650,6 +654,10 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, inode->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); @@ -662,6 +670,7 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif /* timestamp is already written, so mark_inode_dirty() is unneeded. */ d_instantiate(dentry, inode); @@ -910,6 +919,11 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) #else dir->i_version++; #endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(dir); + exfat_truncate_inode_atime(dir); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); @@ -920,12 +934,17 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); +#endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else mark_inode_dirty(dir); clear_nlink(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); @@ -936,6 +955,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif exfat_unhash_inode(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); @@ -981,7 +1001,11 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); +#else dir->i_mtime = inode_set_ctime_current(dir); +#endif #else dir->i_ctime = dir->i_mtime = current_time(dir); #endif @@ -1005,6 +1029,10 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #else inode->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); @@ -1017,6 +1045,7 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif /* timestamp is already written, so mark_inode_dirty() is unneeded. */ d_instantiate(dentry, inode); @@ -1134,6 +1163,10 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) #else dir->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(dir); + exfat_truncate_inode_atime(dir); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); @@ -1144,6 +1177,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); +#endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else @@ -1151,6 +1185,10 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) drop_nlink(dir); clear_nlink(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); @@ -1161,6 +1199,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif exfat_unhash_inode(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); @@ -1524,7 +1563,11 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + exfat_truncate_inode_atime(new_dir); +#else exfat_truncate_atime(&new_dir->i_atime); +#endif if (IS_DIRSYNC(new_dir)) exfat_sync_inode(new_dir); else diff --git a/super.c b/super.c index 0b09959..decf1c6 100644 --- a/super.c +++ b/super.c @@ -650,6 +650,10 @@ static int exfat_read_root(struct inode *inode) ei->i_size_ondisk = i_size_read(inode); exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + ei->i_crtime = simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = ei->i_crtime = inode_set_ctime_current(inode); @@ -662,6 +666,7 @@ static int exfat_read_root(struct inode *inode) CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif return 0; } From 327699f41c880e71baf82b87d60bac73f78dda87 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 3 Nov 2023 08:51:38 +0900 Subject: [PATCH 091/141] exfat: fix setting uninitialized time to ctime/atime An uninitialized time is set to ctime/atime in __exfat_write_inode(). It causes xfstests generic/003 and generic/192 to fail. And since there will be a time gap between setting ctime/atime to the inode and writing back the inode, so ctime/atime should not be set again when writing back the inode. Fixes: 4c72a36edd54 ("exfat: convert to new timestamp accessors") Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inode.c b/inode.c index 7d7f2cf..479db42 100644 --- a/inode.c +++ b/inode.c @@ -61,18 +61,18 @@ int __exfat_write_inode(struct inode *inode, int sync) &ep->dentry.file.create_date, &ep->dentry.file.create_time_cs); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + ts = inode_get_mtime(inode); exfat_set_entry_time(sbi, &ts, &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, &ep->dentry.file.modify_date, &ep->dentry.file.modify_time_cs); - inode_set_mtime_to_ts(inode, ts); + ts = inode_get_atime(inode); exfat_set_entry_time(sbi, &ts, &ep->dentry.file.access_tz, &ep->dentry.file.access_time, &ep->dentry.file.access_date, NULL); - inode_set_atime_to_ts(inode, ts); #else exfat_set_entry_time(sbi, &inode->i_mtime, &ep->dentry.file.modify_tz, From 3ad149047d1acf158f327b3f0279d3d8b03bc894 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 3 Nov 2023 08:52:47 +0900 Subject: [PATCH 092/141] exfat: fix ctime is not updated Commit 4c72a36edd54 removed attr_copy() from exfat_set_attr(). It causes xfstests generic/221 to fail. In xfstests generic/221, it tests ctime should be updated even if futimens() update atime only. But in this case, ctime will not be updated if attr_copy() is removed. attr_copy() may also update other attributes, and removing it may cause other bugs, so this commit restores to call attr_copy() in exfat_set_attr(). Fixes: 4c72a36edd54 ("exfat: convert to new timestamp accessors") Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- file.c | 1 + 1 file changed, 1 insertion(+) diff --git a/file.c b/file.c index 72879e4..749c349 100644 --- a/file.c +++ b/file.c @@ -385,6 +385,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + setattr_copy(&nop_mnt_idmap, inode, attr); exfat_truncate_inode_atime(inode); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) From 47ed74ded6a1519c2f65a8c8cf347d98d4db1e84 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 1 Dec 2023 11:29:07 +0900 Subject: [PATCH 093/141] exfat: changes the minimum supported kernel version from 4.10 to 5.15 Backporting new patches to the 4.x kernel has become difficult. The existing 4.x support version was made into a branch of for-kernel-version-from-4.1.0. Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 13 +- README.md | 2 +- balloc.c | 8 - dir.c | 8 - exfat_fs.h | 29 ---- fatent.c | 8 - file.c | 84 ---------- inode.c | 39 ----- misc.c | 45 ------ namei.c | 148 ----------------- nls.c | 15 -- super.c | 314 ------------------------------------ 12 files changed, 6 insertions(+), 707 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 6587fd9..c161ede 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -17,10 +17,10 @@ jobs: run: | sudo apt-get update sudo apt-get install libelf-dev wget tar gzip python2.7 - wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz - tar xf linux-4.1.36.tar.gz - mv linux-4.1.36 linux-stable - rm -rf linux-4.1.36.tar.gz + wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.15.140.tar.gz + tar xf linux-5.15.140.tar.gz + mv linux-5.15.140 linux-stable + rm -rf linux-5.15.140.tar.gz - name: Prerequisite for xfstests testing run: | sudo apt-get install linux-headers-$(uname -r) @@ -35,14 +35,11 @@ jobs: - name: Copy exfat source to kernel run: | mv linux-stable ../ - mkdir ../linux-stable/fs/exfat cp -ar * ../linux-stable/fs/exfat/ - - name: Compile with 4.1 kernel + - name: Compile with 5.15 kernel run: | cd ../linux-stable yes "" | make oldconfig > /dev/null - echo 'obj-$(CONFIG_EXFAT_FS) += exfat/' >> fs/Makefile - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig echo 'CONFIG_EXFAT_FS=m' >> .config echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config make -j$((`nproc`+1)) fs/exfat/exfat.ko diff --git a/README.md b/README.md index cee3699..1992f42 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ## exFAT filesystem -This is the exfat filesystem for support from the linux 4.1 kernel +This is the exfat filesystem for support from the linux 5.15 kernel to the latest kernel. ## Installing as a stand-alone module diff --git a/balloc.c b/balloc.c index c226d5d..71814b0 100644 --- a/balloc.c +++ b/balloc.c @@ -7,11 +7,7 @@ #include #include #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include -#else -#include -#endif #include #include "exfat_raw.h" @@ -76,12 +72,8 @@ static int exfat_allocate_bitmap(struct super_block *sb, } sbi->map_sectors = ((need_map_size - 1) >> (sb->s_blocksize_bits)) + 1; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) sbi->vol_amap = kvmalloc_array(sbi->map_sectors, sizeof(struct buffer_head *), GFP_KERNEL); -#else - sbi->vol_amap = vmalloc(sbi->map_sectors * sizeof(struct buffer_head *)); -#endif if (!sbi->vol_amap) return -ENOMEM; diff --git a/dir.c b/dir.c index 4d5b863..7ac26d0 100644 --- a/dir.c +++ b/dir.c @@ -461,19 +461,11 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, { struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) struct timespec64 ts; -#else - struct timespec ts; -#endif struct exfat_dentry *ep; struct buffer_head *bh; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) ts = current_time(inode); -#else - ts = CURRENT_TIME_SEC; -#endif /* * We cannot use exfat_get_dentry_set here because file ep is not diff --git a/exfat_fs.h b/exfat_fs.h index 124dfd5..62bb7b9 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -222,15 +222,9 @@ struct exfat_dir_entry { unsigned short attr; loff_t size; unsigned int num_subdirs; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) struct timespec64 atime; struct timespec64 mtime; struct timespec64 crtime; -#else - struct timespec atime; - struct timespec mtime; - struct timespec crtime; -#endif struct exfat_dentry_namebuf namebuf; }; @@ -344,11 +338,7 @@ struct exfat_inode_info { struct rw_semaphore truncate_lock; struct inode vfs_inode; /* File creation time */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) struct timespec64 i_crtime; -#else - struct timespec i_crtime; -#endif }; static inline struct exfat_sb_info *EXFAT_SB(struct super_block *sb) @@ -478,7 +468,6 @@ extern const struct file_operations exfat_file_operations; int __exfat_truncate(struct inode *inode); void exfat_truncate(struct inode *inode); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); @@ -492,16 +481,6 @@ int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); #endif -#else -int exfat_setattr(struct dentry *dentry, struct iattr *attr); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) -int exfat_getattr(const struct path *path, struct kstat *stat, - unsigned int request_mask, unsigned int query_flags); -#else -int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, - struct kstat *stat); -#endif -#endif int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); long exfat_compat_ioctl(struct file *filp, unsigned int cmd, @@ -594,7 +573,6 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) #define exfat_debug(sb, fmt, ...) \ pr_debug("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_cs); void exfat_truncate_atime(struct timespec64 *ts); @@ -603,13 +581,6 @@ void exfat_truncate_inode_atime(struct inode *inode); #endif void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); -#else -void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, - u8 tz, __le16 time, __le16 date, u8 time_cs); -void exfat_truncate_atime(struct timespec *ts); -void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, - u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); -#endif u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type); u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type); void exfat_update_bh(struct buffer_head *bh, int sync); diff --git a/fatent.c b/fatent.c index c13a04e..81f03ea 100644 --- a/fatent.c +++ b/fatent.c @@ -27,11 +27,7 @@ static int exfat_mirror_bh(struct super_block *sb, sector_t sec, memcpy(c_bh->b_data, bh->b_data, sb->s_blocksize); set_buffer_uptodate(c_bh); mark_buffer_dirty(c_bh); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) if (sb->s_flags & SB_SYNCHRONOUS) -#else - if (sb->s_flags & MS_SYNCHRONOUS) -#endif err = sync_dirty_buffer(c_bh); brelse(c_bh); } @@ -80,11 +76,7 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, fat_entry = (__le32 *)&(bh->b_data[off]); *fat_entry = cpu_to_le32(content); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); -#else - exfat_update_bh(bh, sb->s_flags & MS_SYNCHRONOUS); -#endif exfat_mirror_bh(sb, sec, bh); brelse(bh); return 0; diff --git a/file.c b/file.c index 749c349..ef9b199 100644 --- a/file.c +++ b/file.c @@ -6,9 +6,7 @@ #include #include #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include -#endif #include #include #include @@ -29,7 +27,6 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) if (err) return err; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); @@ -38,9 +35,6 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) #endif #else inode->i_ctime = inode->i_mtime = current_time(inode); -#endif -#else - inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; #endif mark_inode_dirty(inode); @@ -211,11 +205,7 @@ void exfat_truncate(struct inode *inode) struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 72) unsigned int blocksize = i_blocksize(inode); -#else - unsigned int blocksize = 1 << inode->i_blkbits; -#endif loff_t aligned_size; int err; @@ -248,7 +238,6 @@ void exfat_truncate(struct inode *inode) mutex_unlock(&sbi->s_lock); } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, @@ -258,24 +247,10 @@ int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags) #endif -#else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) -int exfat_getattr(const struct path *path, struct kstat *stat, - unsigned int request_mask, unsigned int query_flags) -#else -int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, - struct kstat *stat) -#endif -#endif { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) struct inode *inode = d_backing_inode(path->dentry); struct exfat_inode_info *ei = EXFAT_I(inode); -#else - struct inode *inode = d_inode(dentry); -#endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); @@ -284,21 +259,15 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, #endif #else generic_fillattr(&init_user_ns, inode, stat); -#endif -#else - generic_fillattr(inode, stat); #endif exfat_truncate_atime(&stat->atime); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) stat->result_mask |= STATX_BTIME; stat->btime.tv_sec = ei->i_crtime.tv_sec; stat->btime.tv_nsec = ei->i_crtime.tv_nsec; -#endif stat->blksize = EXFAT_SB(inode->i_sb)->cluster_size; return 0; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) @@ -306,9 +275,6 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, struct iattr *attr) #endif -#else -int exfat_setattr(struct dentry *dentry, struct iattr *attr) -#endif { struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb); struct inode *inode = dentry->d_inode; @@ -331,20 +297,10 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) ATTR_TIMES_SET); } -#if ((LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)) && \ - (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37))) || \ - (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) error = setattr_prepare(&nop_mnt_idmap, dentry, attr); #else error = setattr_prepare(&init_user_ns, dentry, attr); -#endif -#else - error = setattr_prepare(dentry, attr); -#endif -#else - error = inode_change_ok(inode, attr); #endif attr->ia_valid = ia_valid; if (error) @@ -370,7 +326,6 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) } if (attr->ia_valid & ATTR_SIZE) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); @@ -380,22 +335,15 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) #else inode->i_mtime = inode->i_ctime = current_time(inode); #endif -#else - inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; -#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) setattr_copy(&nop_mnt_idmap, inode, attr); exfat_truncate_inode_atime(inode); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) setattr_copy(&nop_mnt_idmap, inode, attr); #else setattr_copy(&init_user_ns, inode, attr); -#endif -#else - setattr_copy(inode, attr); #endif exfat_truncate_atime(&inode->i_atime); #endif @@ -426,17 +374,9 @@ static int exfat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr { u32 attr; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) inode_lock_shared(inode); -#else - mutex_lock(&inode->i_mutex); -#endif attr = exfat_make_attr(inode); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) inode_unlock_shared(inode); -#else - mutex_unlock(&inode->i_mutex); -#endif return put_user(attr, user_attr); } @@ -458,11 +398,7 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) if (err) goto out; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) inode_lock(inode); -#else - mutex_lock(&inode->i_mutex); -#endif oldattr = exfat_make_attr(inode); @@ -475,11 +411,7 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) /* Equivalent to a chmod() */ ia.ia_valid = ATTR_MODE | ATTR_CTIME; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) ia.ia_ctime = current_time(inode); -#else - ia.ia_ctime = current_fs_time(inode->i_sb); -#endif if (is_dir) ia.ia_mode = exfat_make_mode(sbi, attr, 0777); else @@ -516,15 +448,11 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) if (err) goto out_unlock_inode; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) /* This MUST be done before doing anything irreversible... */ err = exfat_setattr(file_mnt_idmap(file), file->f_path.dentry, &ia); #else err = exfat_setattr(file_mnt_user_ns(file), file->f_path.dentry, &ia); -#endif -#else - err = exfat_setattr(file->f_path.dentry, &ia); #endif if (err) goto out_unlock_inode; @@ -534,11 +462,7 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) exfat_save_attr(inode, attr); mark_inode_dirty(inode); out_unlock_inode: -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) inode_unlock(inode); -#else - mutex_unlock(&inode->i_mutex); -#endif mnt_drop_write_file(file); out: return err; @@ -621,15 +545,7 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) if (err) return err; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) return blkdev_issue_flush(inode->i_sb->s_bdev); -#else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) - return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL); -#else - return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL); -#endif -#endif } const struct file_operations exfat_file_operations = { diff --git a/inode.c b/inode.c index 479db42..4149801 100644 --- a/inode.c +++ b/inode.c @@ -13,9 +13,7 @@ #include #include #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #include -#endif #include "exfat_raw.h" #include "exfat_fs.h" @@ -365,18 +363,10 @@ static int exfat_readpage(struct file *file, struct page *page) } #endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) static void exfat_readahead(struct readahead_control *rac) { mpage_readahead(rac, exfat_get_block); } -#else -static int exfat_readpages(struct file *file, struct address_space *mapping, - struct list_head *pages, unsigned int nr_pages) -{ - return mpage_readpages(mapping, pages, nr_pages, exfat_get_block); -} -#endif #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) static int exfat_writepage(struct page *page, struct writeback_control *wbc) @@ -397,7 +387,6 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) if (to > i_size_read(inode)) { truncate_pagecache(inode, i_size_read(inode)); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); @@ -406,9 +395,6 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) #endif #else inode->i_mtime = inode->i_ctime = current_time(inode); -#endif -#else - inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate(inode); } @@ -463,7 +449,6 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, exfat_write_failed(mapping, pos+len); if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); @@ -472,9 +457,6 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, #endif #else inode->i_mtime = inode->i_ctime = current_time(inode); -#endif -#else - inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif ei->attr |= EXFAT_ATTR_ARCHIVE; mark_inode_dirty(inode); @@ -484,12 +466,7 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) -#else -static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter, - loff_t offset) -#endif { struct address_space *mapping = iocb->ki_filp->f_mapping; struct inode *inode = mapping->host; @@ -515,11 +492,7 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter, * Need to use the DIO_LOCKING for avoiding the race * condition of exfat_get_block() and ->truncate(). */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) ret = blockdev_direct_IO(iocb, inode, iter, exfat_get_block); -#else - ret = blockdev_direct_IO(iocb, inode, iter, offset, exfat_get_block); -#endif if (ret < 0 && (rw & WRITE)) exfat_write_failed(mapping, size); return ret; @@ -562,11 +535,7 @@ static const struct address_space_operations exfat_aops = { #else .readpage = exfat_readpage, #endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) .readahead = exfat_readahead, -#else - .readpages = exfat_readpages, -#endif #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) .writepage = exfat_writepage, #endif @@ -652,11 +621,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) inode->i_uid = sbi->options.fs_uid; inode->i_gid = sbi->options.fs_gid; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); -#else - inode->i_version++; -#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) inode->i_generation = get_random_u32(); #else @@ -727,11 +692,7 @@ struct inode *exfat_build_inode(struct super_block *sb, goto out; } inode->i_ino = iunique(sb, EXFAT_ROOT_INO); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_set_iversion(inode, 1); -#else - inode->i_version = 1; -#endif err = exfat_fill_inode(inode, info); if (err) { iput(inode); diff --git a/misc.c b/misc.c index a744a1f..6a9a488 100644 --- a/misc.c +++ b/misc.c @@ -40,14 +40,8 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) if (opts->errors == EXFAT_ERRORS_PANIC) { panic("exFAT-fs (%s): fs panic from previous error\n", sb->s_id); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) } else if (opts->errors == EXFAT_ERRORS_RO && !sb_rdonly(sb)) { sb->s_flags |= SB_RDONLY; -#else - } else if (opts->errors == EXFAT_ERRORS_RO && - !(sb->s_flags & MS_RDONLY)) { - sb->s_flags |= MS_RDONLY; -#endif exfat_err(sb, "Filesystem has been set read-only"); } } @@ -55,11 +49,7 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) #define SECS_PER_MIN (60) #define TIMEZONE_SEC(x) ((x) * 15 * SECS_PER_MIN) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) static void exfat_adjust_tz(struct timespec64 *ts, u8 tz_off) -#else -static void exfat_adjust_tz(struct timespec *ts, u8 tz_off) -#endif { if (tz_off <= 0x3F) ts->tv_sec -= TIMEZONE_SEC(tz_off); @@ -75,13 +65,8 @@ static inline int exfat_tz_offset(struct exfat_sb_info *sbi) } /* Convert a EXFAT time/date pair to a UNIX date (seconds since 1 1 70). */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_cs) -#else -void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, - u8 tz, __le16 time, __le16 date, u8 time_cs) -#endif { u16 t = le16_to_cpu(time); u16 d = le16_to_cpu(date); @@ -105,35 +90,13 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, } /* Convert linear UNIX date to a EXFAT time/date pair. */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs) -#else -#undef EXFAT_MAX_TIMESTAMP_SECS -#define EXFAT_MAX_TIMESTAMP_SECS 0xffffffff -void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, - u8 *tz, __le16 *time, __le16 *date, u8 *time_cs) -#endif { struct tm tm; u16 t, d; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) - if (ts->tv_sec < EXFAT_MIN_TIMESTAMP_SECS) { - ts->tv_sec = EXFAT_MIN_TIMESTAMP_SECS; - ts->tv_nsec = 0; - } - else if (ts->tv_sec > EXFAT_MAX_TIMESTAMP_SECS) { - ts->tv_sec = EXFAT_MAX_TIMESTAMP_SECS; - ts->tv_nsec = 0; - } -#endif - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) time64_to_tm(ts->tv_sec, 0, &tm); -#else - time_to_tm(ts->tv_sec, 0, &tm); -#endif t = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); d = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; @@ -153,11 +116,7 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, } /* atime has only a 2-second resolution */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_truncate_atime(struct timespec64 *ts) -#else -void exfat_truncate_atime(struct timespec *ts) -#endif { ts->tv_sec = round_down(ts->tv_sec, 2); ts->tv_nsec = 0; @@ -217,11 +176,7 @@ int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync) set_buffer_uptodate(bhs[i]); mark_buffer_dirty(bhs[i]); if (sync) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) write_dirty_buffer(bhs[i], REQ_SYNC); -#else - write_dirty_buffer(bhs[i], WRITE_SYNC); -#endif } for (i = 0; i < nr_bhs && sync; i++) { diff --git a/namei.c b/namei.c index ef9a6df..52c437e 100644 --- a/namei.c +++ b/namei.c @@ -4,9 +4,7 @@ */ #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #include -#endif #include #include #include @@ -62,13 +60,8 @@ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags) return 0; spin_lock(&dentry->d_lock); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) ret = inode_eq_iversion(d_inode(dentry->d_parent), exfat_d_version(dentry)); -#else - if (dentry->d_parent->d_inode->i_version != exfat_d_version(dentry)) - ret = 0; -#endif spin_unlock(&dentry->d_lock); return ret; } @@ -96,11 +89,7 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr) const unsigned char *name = qstr->name; unsigned int len = exfat_striptail_len(qstr->len, qstr->name, EXFAT_SB(sb)->options.keep_last_dots); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) unsigned long hash = init_name_hash(dentry); -#else - unsigned long hash = init_name_hash(); -#endif int i, charlen; wchar_t c; @@ -115,13 +104,8 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr) return 0; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) static int exfat_d_cmp(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name) -#else -static int exfat_d_cmp(const struct dentry *parent, const struct dentry *dentry, - unsigned int len, const char *str, const struct qstr *name) -#endif { struct super_block *sb = dentry->d_sb; struct nls_table *t = EXFAT_SB(sb)->nls_io; @@ -161,11 +145,7 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr) const unsigned char *name = qstr->name; unsigned int len = exfat_striptail_len(qstr->len, qstr->name, EXFAT_SB(sb)->options.keep_last_dots); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) unsigned long hash = init_name_hash(dentry); -#else - unsigned long hash = init_name_hash(); -#endif int i, charlen; unicode_t u; @@ -185,14 +165,8 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr) return 0; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name) -#else -static int exfat_utf8_d_cmp(const struct dentry *parent, - const struct dentry *dentry, unsigned int len, - const char *str, const struct qstr *name) -#endif { struct super_block *sb = dentry->d_sb; unsigned int alen = exfat_striptail_len(name->len, name->name, @@ -592,7 +566,6 @@ static int exfat_add_entry(struct inode *inode, const char *path, return ret; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) @@ -600,10 +573,6 @@ static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) #endif -#else -static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, - bool excl) -#endif { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -619,12 +588,7 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, if (err) goto unlock; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); -#else - dir->i_version++; -#endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); @@ -633,9 +597,6 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, #endif #else dir->i_ctime = dir->i_mtime = current_time(dir); -#endif -#else - dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; #endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); @@ -648,26 +609,17 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, if (err) goto unlock; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); -#else - inode->i_version++; -#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); exfat_truncate_inode_atime(inode); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); #else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); -#endif -#else - inode->i_mtime = inode->i_atime = inode->i_ctime = - EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); #endif @@ -703,18 +655,10 @@ static int exfat_find(struct inode *dir, struct qstr *qname, return ret; /* check the validation of hint_stat and initialize it if required */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { -#else - if (ei->version != (dir->i_version & 0xffffffff)) { -#endif ei->hint_stat.clu = cdir.dir; ei->hint_stat.eidx = 0; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) ei->version = (inode_peek_iversion_raw(dir) & 0xffffffff); -#else - ei->version = (dir->i_version & 0xffffffff); -#endif ei->hint_femp.eidx = EXFAT_HINT_NONE; } @@ -858,11 +802,7 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, out: mutex_unlock(&EXFAT_SB(sb)->s_lock); if (!inode) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); -#else - exfat_d_version_set(dentry, dir->i_version); -#endif return d_splice_alias(inode, dentry); unlock: @@ -914,24 +854,16 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) /* This doesn't modify ei */ ei->dir.dir = DIR_DELETED; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); -#else - dir->i_version++; -#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) simple_inode_init_ts(dir); exfat_truncate_inode_atime(dir); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); #else dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); -#endif -#else - dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); #endif @@ -945,29 +877,20 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) simple_inode_init_ts(inode); exfat_truncate_inode_atime(inode); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); #else inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); -#endif -#else - inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); #endif exfat_unhash_inode(inode); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); -#else - exfat_d_version_set(dentry, dir->i_version); -#endif unlock: mutex_unlock(&EXFAT_SB(sb)->s_lock); return err; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) static int exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) @@ -975,9 +898,6 @@ static int exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, struct dentry *dentry, umode_t mode) #endif -#else -static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) -#endif { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -993,13 +913,8 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) if (err) goto unlock; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); -#else - dir->i_version++; -#endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); @@ -1008,9 +923,6 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #endif #else dir->i_ctime = dir->i_mtime = current_time(dir); -#endif -#else - dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; #endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); @@ -1024,25 +936,16 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) if (err) goto unlock; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); -#else - inode->i_version++; -#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); exfat_truncate_inode_atime(inode); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); #else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); -#endif -#else - inode->i_mtime = inode->i_atime = inode->i_ctime = - EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); #endif @@ -1158,23 +1061,15 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) } ei->dir.dir = DIR_DELETED; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); -#else - dir->i_version++; -#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) simple_inode_init_ts(dir); exfat_truncate_inode_atime(dir); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); #else dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); -#endif -#else - dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); #endif @@ -1189,23 +1084,15 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) simple_inode_init_ts(inode); exfat_truncate_inode_atime(inode); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); #else inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); -#endif -#else - inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); #endif exfat_unhash_inode(inode); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); -#else - exfat_d_version_set(dentry, dir->i_version); -#endif unlock: mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); return err; @@ -1500,7 +1387,6 @@ static int __exfat_rename(struct inode *old_parent_inode, return ret; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) static int exfat_rename(struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, @@ -1512,23 +1398,12 @@ static int exfat_rename(struct user_namespace *mnt_userns, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) #endif -#else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) -static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry, - unsigned int flags) -#else -static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) -#endif -#endif { struct inode *old_inode, *new_inode; struct super_block *sb = old_dir->i_sb; loff_t i_pos; int err; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) /* * The VFS already checks for existence, so for local filesystems * the RENAME_NOREPLACE implementation is equivalent to plain rename. @@ -1536,7 +1411,6 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, */ if (flags & ~RENAME_NOREPLACE) return -EINVAL; -#endif mutex_lock(&EXFAT_SB(sb)->s_lock); old_inode = old_dentry->d_inode; @@ -1546,12 +1420,7 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, if (err) goto unlock; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(new_dir); -#else - new_dir->i_version++; -#endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry); EXFAT_I(new_dir)->i_crtime = current_time(new_dir); @@ -1559,10 +1428,6 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = current_time(new_dir); #endif -#else - new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = - EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC; -#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) exfat_truncate_inode_atime(new_dir); #else @@ -1588,17 +1453,9 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, inc_nlink(new_dir); } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(old_dir); -#else - old_dir->i_version++; -#endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 6, 0) old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); -#endif -#else - old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC; #endif if (IS_DIRSYNC(old_dir)) exfat_sync_inode(old_dir); @@ -1617,16 +1474,11 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, exfat_warn(sb, "abnormal access to an inode dropped"); WARN_ON(new_inode->i_nlink == 0); } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) EXFAT_I(new_inode)->i_crtime = current_time(new_inode); #else new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = current_time(new_inode); -#endif -#else - new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = - CURRENT_TIME_SEC; #endif } diff --git a/nls.c b/nls.c index 3b9fd46..a5d5e25 100644 --- a/nls.c +++ b/nls.c @@ -7,9 +7,6 @@ #include #include #include -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 18, 0) -#include -#endif #include #include "exfat_raw.h" @@ -663,11 +660,7 @@ static int exfat_load_upcase_table(struct super_block *sb, unsigned char skip = false; unsigned short *upcase_table; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) upcase_table = kvcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); -#else - upcase_table = vzalloc(UTBL_COUNT * sizeof(unsigned short)); -#endif if (!upcase_table) return -ENOMEM; @@ -723,11 +716,7 @@ static int exfat_load_default_upcase_table(struct super_block *sb) unsigned short uni = 0, *upcase_table; unsigned int index = 0; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) upcase_table = kvcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); -#else - upcase_table = vzalloc(UTBL_COUNT * sizeof(unsigned short)); -#endif if (!upcase_table) return -ENOMEM; @@ -815,9 +804,5 @@ int exfat_create_upcase_table(struct super_block *sb) void exfat_free_upcase_table(struct exfat_sb_info *sbi) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) kvfree(sbi->vol_utbl); -#else - vfree(sbi->vol_utbl); -#endif } diff --git a/super.c b/super.c index decf1c6..9250090 100644 --- a/super.c +++ b/super.c @@ -4,13 +4,8 @@ */ #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) #include #include -#else -#include -#include -#endif #include #include #include @@ -22,10 +17,7 @@ #include #include #include - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #include -#endif #include "exfat_raw.h" #include "exfat_fs.h" @@ -126,11 +118,7 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags) /* skip updating volume dirty flag, * if this volume has been mounted with read-only */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) if (sb_rdonly(sb)) -#else - if (sb->s_flags & MS_RDONLY) -#endif return 0; p_boot->vol_flags = cpu_to_le16(new_flags); @@ -138,11 +126,7 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags) set_buffer_uptodate(sbi->boot_bh); mark_buffer_dirty(sbi->boot_bh); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) __sync_dirty_buffer(sbi->boot_bh, REQ_SYNC | REQ_FUA | REQ_PREFLUSH); -#else - __sync_dirty_buffer(sbi->boot_bh, WRITE_FLUSH_FUA); -#endif return 0; } @@ -216,48 +200,14 @@ static struct inode *exfat_alloc_inode(struct super_block *sb) return &ei->vfs_inode; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) static void exfat_free_inode(struct inode *inode) { kmem_cache_free(exfat_inode_cachep, EXFAT_I(inode)); } -#else -static void exfat_i_callback(struct rcu_head *head) -{ - struct inode *inode = container_of(head, struct inode, i_rcu); - kmem_cache_free(exfat_inode_cachep, EXFAT_I(inode)); -} - -static void exfat_destroy_inode(struct inode *inode) -{ - call_rcu(&inode->i_rcu, exfat_i_callback); -} -#endif - -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) -static int exfat_remount(struct super_block *sb, int *flags, char *data) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) - *flags |= SB_NODIRATIME; -#else - *flags |= MS_NODIRATIME; -#endif - - sync_filesystem(sb); - return 0; -} -#endif static const struct super_operations exfat_sops = { .alloc_inode = exfat_alloc_inode, -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) .free_inode = exfat_free_inode, -#else - .destroy_inode = exfat_destroy_inode, -#endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) - .remount_fs = exfat_remount, -#endif .write_inode = exfat_write_inode, .evict_inode = exfat_evict_inode, .put_super = exfat_put_super, @@ -266,7 +216,6 @@ static const struct super_operations exfat_sops = { .show_options = exfat_show_options, }; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) enum { Opt_uid, Opt_gid, @@ -289,27 +238,14 @@ enum { Opt_codepage, }; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) static const struct constant_table exfat_param_enums[] = { { "continue", EXFAT_ERRORS_CONT }, { "panic", EXFAT_ERRORS_PANIC }, { "remount-ro", EXFAT_ERRORS_RO }, {} }; -#else -static const struct fs_parameter_enum exfat_param_enums[] = { - { Opt_errors, "continue", EXFAT_ERRORS_CONT }, - { Opt_errors, "panic", EXFAT_ERRORS_PANIC }, - { Opt_errors, "remount-ro", EXFAT_ERRORS_RO }, - {} -}; -#endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) static const struct fs_parameter_spec exfat_parameters[] = { -#else -static const struct fs_parameter_spec exfat_param_specs[] = { -#endif fsparam_u32("uid", Opt_uid), fsparam_u32("gid", Opt_gid), fsparam_u32oct("umask", Opt_umask), @@ -317,11 +253,7 @@ static const struct fs_parameter_spec exfat_param_specs[] = { fsparam_u32oct("fmask", Opt_fmask), fsparam_u32oct("allow_utime", Opt_allow_utime), fsparam_string("iocharset", Opt_charset), -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) fsparam_enum("errors", Opt_errors, exfat_param_enums), -#else - fsparam_enum("errors", Opt_errors), -#endif fsparam_flag("discard", Opt_discard), fsparam_flag("keep_last_dots", Opt_keep_last_dots), fsparam_flag("sys_tz", Opt_sys_tz), @@ -338,14 +270,6 @@ static const struct fs_parameter_spec exfat_param_specs[] = { {} }; -#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 6, 0) -static const struct fs_parameter_description exfat_parameters = { - .name = "exfat", - .specs = exfat_param_specs, - .enums = exfat_param_enums, -}; -#endif - static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) { struct exfat_sb_info *sbi = fc->s_fs_info; @@ -353,11 +277,7 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) struct fs_parse_result result; int opt; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) opt = fs_parse(fc, exfat_parameters, param, &result); -#else - opt = fs_parse(fc, &exfat_parameters, param, &result); -#endif if (opt < 0) return opt; @@ -421,176 +341,6 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) return 0; } -#else -enum { - Opt_uid, - Opt_gid, - Opt_umask, - Opt_dmask, - Opt_fmask, - Opt_allow_utime, - Opt_charset, - Opt_err_cont, - Opt_err_panic, - Opt_err_ro, - Opt_err, - Opt_discard, - Opt_time_offset, - - /* Deprecated options */ - Opt_utf8, - Opt_debug, - Opt_namecase, - Opt_codepage, - Opt_zero_size_dir, - Opt_fs, -}; - -static const match_table_t exfat_tokens = { - {Opt_uid, "uid=%u"}, - {Opt_gid, "gid=%u"}, - {Opt_umask, "umask=%o"}, - {Opt_dmask, "dmask=%o"}, - {Opt_fmask, "fmask=%o"}, - {Opt_allow_utime, "allow_utime=%o"}, - {Opt_charset, "iocharset=%s"}, - {Opt_err_cont, "errors=continue"}, - {Opt_err_panic, "errors=panic"}, - {Opt_err_ro, "errors=remount-ro"}, - {Opt_discard, "discard"}, - {Opt_codepage, "codepage=%u"}, - {Opt_namecase, "namecase=%u"}, - {Opt_debug, "debug"}, - {Opt_utf8, "utf8"}, - {Opt_zero_size_dir, "zero_size_dir"}, - {Opt_err, NULL} -}; - -static int parse_options(struct super_block *sb, char *options, int silent, - struct exfat_mount_options *opts) -{ - char *p; - substring_t args[MAX_OPT_ARGS]; - int option; - char *tmpstr; - - opts->fs_uid = current_uid(); - opts->fs_gid = current_gid(); - opts->fs_fmask = opts->fs_dmask = current->fs->umask; - opts->allow_utime = -1; - opts->iocharset = exfat_default_iocharset; - opts->utf8 = 0; - opts->errors = EXFAT_ERRORS_RO; - opts->discard = 0; - - if (!options) - goto out; - - while ((p = strsep(&options, ",")) != NULL) { - int token; - - if (!*p) - continue; - token = match_token(p, exfat_tokens, args); - switch (token) { - case Opt_uid: - if (match_int(&args[0], &option)) - return 0; - opts->fs_uid = make_kuid(current_user_ns(), option); - break; - case Opt_gid: - if (match_int(&args[0], &option)) - return 0; - opts->fs_gid = make_kgid(current_user_ns(), option); - break; - case Opt_umask: - case Opt_dmask: - case Opt_fmask: - if (match_octal(&args[0], &option)) - return 0; - if (token != Opt_dmask) - opts->fs_fmask = option; - if (token != Opt_fmask) - opts->fs_dmask = option; - break; - case Opt_allow_utime: - if (match_octal(&args[0], &option)) - return 0; - opts->allow_utime = option & (0022); - break; - case Opt_charset: - if (opts->iocharset != exfat_default_iocharset) - kfree(opts->iocharset); - tmpstr = match_strdup(&args[0]); - if (!tmpstr) - return -ENOMEM; - opts->iocharset = tmpstr; - break; - case Opt_err_cont: - opts->errors = EXFAT_ERRORS_CONT; - break; - case Opt_err_panic: - opts->errors = EXFAT_ERRORS_PANIC; - break; - case Opt_err_ro: - opts->errors = EXFAT_ERRORS_RO; - break; - case Opt_discard: - opts->discard = 1; - break; - case Opt_time_offset: - if (match_int(&args[0], &option)) - return -EINVAL; - /* - * Make the limit 24 just in case someone - * invents something unusual. - */ - if (option < -24 * 60 || option > 24 * 60) - return -EINVAL; - opts->time_offset = option; - break; - case Opt_utf8: - case Opt_debug: - case Opt_namecase: - case Opt_codepage: - break; - case Opt_zero_size_dir: - opts->zero_size_dir = true; - break; - default: - if (!silent) { - exfat_err(sb, - "unrecognized mount option \"%s\" or missing value", - p); - } - return -EINVAL; - } - } - -out: - if (opts->allow_utime == -1) - opts->allow_utime = ~opts->fs_dmask & (0022); - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) - if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) { - exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); - opts->discard = 0; - } -#else - if (opts->discard) { - struct request_queue *q = bdev_get_queue(sb->s_bdev); - - if (!blk_queue_discard(q)) { - exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); - opts->discard = 0; - } - } -#endif - - return 0; -} -#endif - static void exfat_hash_init(struct super_block *sb) { @@ -634,11 +384,7 @@ static int exfat_read_root(struct inode *inode) inode->i_uid = sbi->options.fs_uid; inode->i_gid = sbi->options.fs_gid; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); -#else - inode->i_version++; -#endif inode->i_generation = 0; inode->i_mode = exfat_make_mode(sbi, EXFAT_ATTR_SUBDIR, 0777); inode->i_op = &exfat_dir_inode_operations; @@ -654,16 +400,11 @@ static int exfat_read_root(struct inode *inode) ei->i_crtime = simple_inode_init_ts(inode); exfat_truncate_inode_atime(inode); #else -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = ei->i_crtime = inode_set_ctime_current(inode); #else inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = current_time(inode); -#endif -#else - inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = - CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); #endif @@ -908,15 +649,10 @@ static int __exfat_fill_super(struct super_block *sb) return ret; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) -#else -static int exfat_fill_super(struct super_block *sb, void *data, int silent) -#endif { struct inode *root_inode; int err; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) struct exfat_sb_info *sbi = sb->s_fs_info; struct exfat_mount_options *opts = &sbi->options; @@ -938,44 +674,14 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) } } #endif -#else - struct exfat_sb_info *sbi; - - /* - * GFP_KERNEL is ok here, because while we do hold the - * supeblock lock, memory pressure can't call back into - * the filesystem, since we're only just about to mount - * it and have no inodes etc active! - */ - sbi = kzalloc(sizeof(struct exfat_sb_info), GFP_KERNEL); - if (!sbi) - return -ENOMEM; - mutex_init(&sbi->s_lock); - mutex_init(&sbi->bitmap_lock); - sb->s_fs_info = sbi; - ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, - DEFAULT_RATELIMIT_BURST); - err = parse_options(sb, data, silent, &sbi->options); - if (err) { - exfat_err(sb, "failed to parse options"); - goto check_nls_io; - } -#endif - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) sb->s_flags |= SB_NODIRATIME; -#else - sb->s_flags |= MS_NODIRATIME; -#endif sb->s_magic = EXFAT_SUPER_MAGIC; sb->s_op = &exfat_sops; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) sb->s_time_gran = 10 * NSEC_PER_MSEC; sb->s_time_min = EXFAT_MIN_TIMESTAMP_SECS; sb->s_time_max = EXFAT_MAX_TIMESTAMP_SECS; -#endif err = __exfat_fill_super(sb); if (err) { @@ -987,11 +693,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) exfat_hash_init(sb); if (!strcmp(sbi->options.iocharset, "utf8")) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) opts->utf8 = 1; -#else - sbi->options.utf8 = 1; -#endif else { sbi->nls_io = load_nls(sbi->options.iocharset); if (!sbi->nls_io) { @@ -1016,11 +718,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) root_inode->i_ino = EXFAT_ROOT_INO; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_set_iversion(root_inode, 1); -#else - root_inode->i_version = 1; -#endif err = exfat_read_root(root_inode); if (err) { exfat_err(sb, "failed to initialize root inode"); @@ -1056,7 +754,6 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) return err; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) static int exfat_get_tree(struct fs_context *fc) { return get_tree_bdev(fc, exfat_fill_super); @@ -1113,23 +810,12 @@ static int exfat_init_fs_context(struct fs_context *fc) fc->ops = &exfat_context_ops; return 0; } -#else -static struct dentry *exfat_fs_mount(struct file_system_type *fs_type, - int flags, const char *dev_name, void *data) -{ - return mount_bdev(fs_type, flags, dev_name, data, exfat_fill_super); -} -#endif static struct file_system_type exfat_fs_type = { .owner = THIS_MODULE, .name = "exfat", -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) .init_fs_context = exfat_init_fs_context, .parameters = exfat_parameters, -#else - .mount = exfat_fs_mount, -#endif .kill_sb = kill_block_super, .fs_flags = FS_REQUIRES_DEV, }; From 41f8ba0e555609839634d3fe59556fc248bb06ab Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 1 Dec 2023 12:09:21 +0900 Subject: [PATCH 094/141] exfat: github action: avoid errors in case of undefined symbols when build test with 5.15 kernel Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index c161ede..d6a8e80 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -42,7 +42,7 @@ jobs: yes "" | make oldconfig > /dev/null echo 'CONFIG_EXFAT_FS=m' >> .config echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config - make -j$((`nproc`+1)) fs/exfat/exfat.ko + make KBUILD_MODPOST_WARN=1 KBUILD_MODPOST_NOFINAL=1 fs/exfat/exfat.ko - name: Run xfstests testsuite run: | cd .. From e4754d8136fd76193dee850661566aff8995fd4e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 5 Dec 2023 23:48:57 +0900 Subject: [PATCH 095/141] exfat: note old exfat oot for 4.1 kernel support in README Signed-off-by: Namjae Jeon --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1992f42..97af48b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ## exFAT filesystem This is the exfat filesystem for support from the linux 5.15 kernel -to the latest kernel. +to the latest kernel. If you want to use exfat from 4.1 ~ 5.15 kernel, +Please use #for-kernel-version-from-4.1.0 branch. ## Installing as a stand-alone module From d64a1f9d9afc6f8178b5294a7a3f0bc693d0071a Mon Sep 17 00:00:00 2001 From: John Sanpe Date: Wed, 6 Dec 2023 23:08:01 +0900 Subject: [PATCH 096/141] exfat: using hweight instead of internal logic Replace the internal table lookup algorithm with the hweight library, which has instruction set acceleration capabilities. Use it to increase the length of a single calculation of the exfat_find_free_bitmap function to the long type. Signed-off-by: John Sanpe Signed-off-by: Namjae Jeon --- balloc.c | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/balloc.c b/balloc.c index 71814b0..fc743e8 100644 --- a/balloc.c +++ b/balloc.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,16 @@ #include "exfat_raw.h" #include "exfat_fs.h" +#if BITS_PER_LONG == 32 +#define __le_long __le32 +#define lel_to_cpu(A) le32_to_cpu(A) +#elif BITS_PER_LONG == 64 +#define __le_long __le64 +#define lel_to_cpu(A) le64_to_cpu(A) +#else +#error "BITS_PER_LONG not 32 or 64" +#endif + static const unsigned char free_bit[] = { 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/* 0 ~ 19*/ 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~ 39*/ @@ -29,22 +40,6 @@ static const unsigned char free_bit[] = { 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /*240 ~ 254*/ }; -static const unsigned char used_bit[] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,/* 0 ~ 19*/ - 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,/* 20 ~ 39*/ - 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,/* 40 ~ 59*/ - 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,/* 60 ~ 79*/ - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,/* 80 ~ 99*/ - 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,/*100 ~ 119*/ - 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4,/*120 ~ 139*/ - 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,/*140 ~ 159*/ - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,/*160 ~ 179*/ - 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,/*180 ~ 199*/ - 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,/*200 ~ 219*/ - 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,/*220 ~ 239*/ - 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 /*240 ~ 255*/ -}; - /* * Allocation Bitmap Management Functions */ @@ -247,25 +242,24 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count) unsigned int count = 0; unsigned int i, map_i = 0, map_b = 0; unsigned int total_clus = EXFAT_DATA_CLUSTER_COUNT(sbi); - unsigned int last_mask = total_clus & BITS_PER_BYTE_MASK; - unsigned char clu_bits; - const unsigned char last_bit_mask[] = {0, 0b00000001, 0b00000011, - 0b00000111, 0b00001111, 0b00011111, 0b00111111, 0b01111111}; + unsigned int last_mask = total_clus & (BITS_PER_LONG - 1); + unsigned long *bitmap, clu_bits; total_clus &= ~last_mask; - for (i = 0; i < total_clus; i += BITS_PER_BYTE) { - clu_bits = *(sbi->vol_amap[map_i]->b_data + map_b); - count += used_bit[clu_bits]; - if (++map_b >= (unsigned int)sb->s_blocksize) { + for (i = 0; i < total_clus; i += BITS_PER_LONG) { + bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b); + count += hweight_long(*bitmap); + map_b += sizeof(long); + if (map_b >= (unsigned int)sb->s_blocksize) { map_i++; map_b = 0; } } if (last_mask) { - clu_bits = *(sbi->vol_amap[map_i]->b_data + map_b); - clu_bits &= last_bit_mask[last_mask]; - count += used_bit[clu_bits]; + bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b); + clu_bits = lel_to_cpu(*(__le_long *)bitmap); + count += hweight_long(clu_bits & BITMAP_LAST_WORD_MASK(last_mask)); } *ret_count = count; From 2254ecbe18a9bf1830d25cb0841fb2429161c35f Mon Sep 17 00:00:00 2001 From: John Sanpe Date: Mon, 11 Dec 2023 09:26:13 +0900 Subject: [PATCH 097/141] exfat: using ffs instead of internal logic Replaced the internal table lookup algorithm with ffs of the bitops library with better performance. Use it to increase the single processing length of the exfat_find_free_bitmap function, from single-byte search to long type. Signed-off-by: John Sanpe Signed-off-by: Namjae Jeon --- balloc.c | 41 +++++++++++++++-------------------------- exfat_fs.h | 3 +-- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/balloc.c b/balloc.c index fc743e8..439c23c 100644 --- a/balloc.c +++ b/balloc.c @@ -17,29 +17,15 @@ #if BITS_PER_LONG == 32 #define __le_long __le32 #define lel_to_cpu(A) le32_to_cpu(A) +#define cpu_to_lel(A) cpu_to_le32(A) #elif BITS_PER_LONG == 64 #define __le_long __le64 #define lel_to_cpu(A) le64_to_cpu(A) +#define cpu_to_lel(A) cpu_to_le64(A) #else #error "BITS_PER_LONG not 32 or 64" #endif -static const unsigned char free_bit[] = { - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/* 0 ~ 19*/ - 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~ 39*/ - 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/* 40 ~ 59*/ - 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/* 60 ~ 79*/ - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2,/* 80 ~ 99*/ - 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3,/*100 ~ 119*/ - 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*120 ~ 139*/ - 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,/*140 ~ 159*/ - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/*160 ~ 179*/ - 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3,/*180 ~ 199*/ - 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*200 ~ 219*/ - 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/*220 ~ 239*/ - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /*240 ~ 254*/ -}; - /* * Allocation Bitmap Management Functions */ @@ -198,32 +184,35 @@ unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu) { unsigned int i, map_i, map_b, ent_idx; unsigned int clu_base, clu_free; - unsigned char k, clu_mask; + unsigned long clu_bits, clu_mask; struct exfat_sb_info *sbi = EXFAT_SB(sb); + __le_long bitval; WARN_ON(clu < EXFAT_FIRST_CLUSTER); - ent_idx = CLUSTER_TO_BITMAP_ENT(clu); - clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx & ~(BITS_PER_BYTE_MASK)); + ent_idx = ALIGN_DOWN(CLUSTER_TO_BITMAP_ENT(clu), BITS_PER_LONG); + clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx); clu_mask = IGNORED_BITS_REMAINED(clu, clu_base); map_i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); map_b = BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent_idx); for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters; - i += BITS_PER_BYTE) { - k = *(sbi->vol_amap[map_i]->b_data + map_b); + i += BITS_PER_LONG) { + bitval = *(__le_long *)(sbi->vol_amap[map_i]->b_data + map_b); if (clu_mask > 0) { - k |= clu_mask; + bitval |= cpu_to_lel(clu_mask); clu_mask = 0; } - if (k < 0xFF) { - clu_free = clu_base + free_bit[k]; + if (lel_to_cpu(bitval) != ULONG_MAX) { + clu_bits = lel_to_cpu(bitval); + clu_free = clu_base + ffz(clu_bits); if (clu_free < sbi->num_clusters) return clu_free; } - clu_base += BITS_PER_BYTE; + clu_base += BITS_PER_LONG; + map_b += sizeof(long); - if (++map_b >= sb->s_blocksize || + if (map_b >= sb->s_blocksize || clu_base >= sbi->num_clusters) { if (++map_i >= sbi->map_sectors) { clu_base = EXFAT_FIRST_CLUSTER; diff --git a/exfat_fs.h b/exfat_fs.h index 62bb7b9..462f048 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -148,8 +148,7 @@ enum { #define BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent) (ent & BITS_PER_SECTOR_MASK(sb)) #define BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent) \ ((ent / BITS_PER_BYTE) & ((sb)->s_blocksize - 1)) -#define BITS_PER_BYTE_MASK 0x7 -#define IGNORED_BITS_REMAINED(clu, clu_base) ((1 << ((clu) - (clu_base))) - 1) +#define IGNORED_BITS_REMAINED(clu, clu_base) ((1UL << ((clu) - (clu_base))) - 1) #define ES_ENTRY_NUM(name_len) (ES_IDX_LAST_FILENAME(name_len) + 1) /* 19 entries = 1 file entry + 1 stream entry + 17 filename entries */ From f9cc939cb063513fdf166aaa7a35aee2a76a50ed Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 11 Dec 2023 22:03:39 +0900 Subject: [PATCH 098/141] exfat: change to get file size from DataLength In stream extension directory entry, the ValidDataLength field describes how far into the data stream user data has been written, and the DataLength field describes the file size. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 + file.c | 132 ++++++++++++++++++++++++++++++++++++++++++- inode.c | 160 ++++++++++++++++++++++++++++++++++++++++++++++------- namei.c | 6 ++ 4 files changed, 278 insertions(+), 22 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 462f048..d55741e 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -220,6 +220,7 @@ struct exfat_dir_entry { unsigned char flags; unsigned short attr; loff_t size; + loff_t valid_size; unsigned int num_subdirs; struct timespec64 atime; struct timespec64 mtime; @@ -331,6 +332,7 @@ struct exfat_inode_info { loff_t i_size_aligned; /* on-disk position of directory entry or 0 */ loff_t i_pos; + loff_t valid_size; /* hash by i_location */ struct hlist_node i_hash_fat; /* protect bmap against truncate */ diff --git a/file.c b/file.c index ef9b199..d97bdd0 100644 --- a/file.c +++ b/file.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -36,6 +37,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) #else inode->i_ctime = inode->i_mtime = current_time(inode); #endif + EXFAT_I(inode)->valid_size = size; mark_inode_dirty(inode); if (!IS_SYNC(inode)) @@ -156,6 +158,9 @@ int __exfat_truncate(struct inode *inode) ei->start_clu = EXFAT_EOF_CLUSTER; } + if (i_size_read(inode) < ei->valid_size) + ei->valid_size = i_size_read(inode); + if (ei->type == TYPE_FILE) ei->attr |= EXFAT_ATTR_ARCHIVE; @@ -548,15 +553,138 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) return blkdev_issue_flush(inode->i_sb->s_bdev); } +static int exfat_file_zeroed_range(struct file *file, loff_t start, loff_t end) +{ + int err; + struct inode *inode = file_inode(file); + struct address_space *mapping = inode->i_mapping; + const struct address_space_operations *ops = mapping->a_ops; + + while (start < end) { + u32 zerofrom, len; + struct page *page = NULL; + + zerofrom = start & (PAGE_SIZE - 1); + len = PAGE_SIZE - zerofrom; + if (start + len > end) + len = end - start; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) + err = ops->write_begin(file, mapping, start, len, &page, NULL); +#else + err = ops->write_begin(file, mapping, start, len, 0, &page, NULL); +#endif + if (err) + goto out; + + zero_user_segment(page, zerofrom, zerofrom + len); + + err = ops->write_end(file, mapping, start, len, len, page, NULL); + if (err < 0) + goto out; + start += len; + + balance_dirty_pages_ratelimited(mapping); + cond_resched(); + } + +out: + return err; +} + +static inline bool exfat_iocb_is_dsync(const struct kiocb *iocb) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) + return iocb_is_dsync(iocb); +#else + return (iocb->ki_flags & IOCB_DSYNC) || + IS_SYNC(iocb->ki_filp->f_mapping->host); +#endif +} + +static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter) +{ + ssize_t ret; + struct file *file = iocb->ki_filp; + struct inode *inode = file_inode(file); + struct exfat_inode_info *ei = EXFAT_I(inode); + loff_t pos = iocb->ki_pos; + loff_t valid_size; + + inode_lock(inode); + + valid_size = ei->valid_size; + + ret = generic_write_checks(iocb, iter); + if (ret < 0) + goto unlock; + + if (pos > valid_size) { + ret = exfat_file_zeroed_range(file, valid_size, pos); + if (ret < 0 && ret != -ENOSPC) { + exfat_err(inode->i_sb, + "write: fail to zero from %llu to %llu(%zd)", + valid_size, pos, ret); + } + if (ret < 0) + goto unlock; + } + + ret = __generic_file_write_iter(iocb, iter); + if (ret < 0) + goto unlock; + + inode_unlock(inode); + + if (pos > valid_size) + pos = valid_size; + + if (exfat_iocb_is_dsync(iocb) && iocb->ki_pos > pos) { + ssize_t err = vfs_fsync_range(file, pos, iocb->ki_pos - 1, + iocb->ki_flags & IOCB_SYNC); + if (err < 0) + return err; + } + + return ret; + +unlock: + inode_unlock(inode); + + return ret; +} + +static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + int ret; + struct inode *inode = file_inode(file); + struct exfat_inode_info *ei = EXFAT_I(inode); + loff_t start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT); + loff_t end = min_t(loff_t, i_size_read(inode), + start + vma->vm_end - vma->vm_start); + + if ((vma->vm_flags & VM_WRITE) && ei->valid_size < end) { + ret = exfat_file_zeroed_range(file, ei->valid_size, end); + if (ret < 0) { + exfat_err(inode->i_sb, + "mmap: fail to zero from %llu to %llu(%d)", + start, end, ret); + return ret; + } + } + + return generic_file_mmap(file, vma); +} + const struct file_operations exfat_file_operations = { .llseek = generic_file_llseek, .read_iter = generic_file_read_iter, - .write_iter = generic_file_write_iter, + .write_iter = exfat_file_write_iter, .unlocked_ioctl = exfat_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = exfat_compat_ioctl, #endif - .mmap = generic_file_mmap, + .mmap = exfat_file_mmap, .fsync = exfat_file_fsync, #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) .splice_read = filemap_splice_read, diff --git a/inode.c b/inode.c index 4149801..764b05b 100644 --- a/inode.c +++ b/inode.c @@ -90,8 +90,8 @@ int __exfat_write_inode(struct inode *inode, int sync) if (ei->start_clu == EXFAT_EOF_CLUSTER) on_disk_size = 0; - ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size); - ep2->dentry.stream.size = ep2->dentry.stream.valid_size; + ep2->dentry.stream.valid_size = cpu_to_le64(ei->valid_size); + ep2->dentry.stream.size = cpu_to_le64(on_disk_size); if (on_disk_size) { ep2->dentry.stream.flags = ei->flags; ep2->dentry.stream.start_clu = cpu_to_le32(ei->start_clu); @@ -281,6 +281,23 @@ static int exfat_map_new_buffer(struct exfat_inode_info *ei, return 0; } +static int exfat_bh_read(struct buffer_head *bh) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + return bh_read(bh, 0); +#else + if (buffer_uptodate(bh)) + return 1; + + ll_rw_block(REQ_OP_READ, 0, 1, &bh); + + wait_on_buffer(bh); + if (!buffer_uptodate(bh)) + return -EIO; + return 0; +#endif +} + static int exfat_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_result, int create) { @@ -293,6 +310,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, unsigned int cluster, sec_offset; sector_t last_block; sector_t phys = 0; + sector_t valid_blks; loff_t pos; mutex_lock(&sbi->s_lock); @@ -321,17 +339,32 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, mapped_blocks = sbi->sect_per_clus - sec_offset; max_blocks = min(mapped_blocks, max_blocks); - /* Treat newly added block / cluster */ - if (iblock < last_block) - create = 0; - - if (create || buffer_delay(bh_result)) { - pos = EXFAT_BLK_TO_B((iblock + 1), sb); + pos = EXFAT_BLK_TO_B((iblock + 1), sb); + if ((create && iblock >= last_block) || buffer_delay(bh_result)) { if (ei->i_size_ondisk < pos) ei->i_size_ondisk = pos; } + map_bh(bh_result, sb, phys); + if (buffer_delay(bh_result)) + clear_buffer_delay(bh_result); + if (create) { + valid_blks = EXFAT_B_TO_BLK_ROUND_UP(ei->valid_size, sb); + + if (iblock + max_blocks < valid_blks) { + /* The range has been written, map it */ + goto done; + } else if (iblock < valid_blks) { + /* + * The range has been partially written, + * map the written part. + */ + max_blocks = valid_blks - iblock; + goto done; + } + + /* The area has not been written, map and mark as new. */ err = exfat_map_new_buffer(ei, bh_result, pos); if (err) { exfat_fs_error(sb, @@ -339,11 +372,69 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, pos, ei->i_size_aligned); goto unlock_ret; } - } + } else { + valid_blks = EXFAT_B_TO_BLK(ei->valid_size, sb); + + if (iblock + max_blocks < valid_blks) { + /* The range has been written, map it */ + goto done; + } else if (iblock < valid_blks) { + /* + * The area has been partially written, + * map the written part. + */ + max_blocks = valid_blks - iblock; + goto done; + } else if (iblock == valid_blks && + (ei->valid_size & (sb->s_blocksize - 1))) { + /* + * The block has been partially written, + * zero the unwritten part and map the block. + */ + loff_t size, off; + + max_blocks = 1; + + /* + * For direct read, the unwritten part will be zeroed in + * exfat_direct_IO() + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + if (!bh_result->b_folio) + goto done; +#else + if (!bh_result->b_page) + goto done; +#endif - if (buffer_delay(bh_result)) - clear_buffer_delay(bh_result); - map_bh(bh_result, sb, phys); + pos -= sb->s_blocksize; + size = ei->valid_size - pos; + off = pos & (PAGE_SIZE - 1); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + folio_set_bh(bh_result, bh_result->b_folio, off); +#else + set_bh_page(bh_result, bh_result->b_page, off); +#endif + err = exfat_bh_read(bh_result); + if (err < 0) + goto unlock_ret; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + folio_zero_segment(bh_result->b_folio, off + size, + off + sb->s_blocksize); +#else + zero_user_segment(bh_result->b_page, off + size, + off + sb->s_blocksize); +#endif + } else { + /* + * The range has not been written, clear the mapped flag + * to only zero the cache and do not read from disk. + */ + clear_buffer_mapped(bh_result); + } + } done: bh_result->b_size = EXFAT_BLK_TO_B(max_blocks, sb); unlock_ret: @@ -365,6 +456,17 @@ static int exfat_readpage(struct file *file, struct page *page) static void exfat_readahead(struct readahead_control *rac) { + struct address_space *mapping = rac->mapping; + struct inode *inode = mapping->host; + struct exfat_inode_info *ei = EXFAT_I(inode); + loff_t pos = readahead_pos(rac); + + /* Range cross valid_size, read it page by page. */ + if (ei->valid_size < i_size_read(inode) && + pos <= ei->valid_size && + ei->valid_size < pos + readahead_length(rac)) + return; + mpage_readahead(rac, exfat_get_block); } @@ -414,13 +516,10 @@ static int exfat_write_begin(struct file *file, struct address_space *mapping, *pagep = NULL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) - ret = cont_write_begin(file, mapping, pos, len, pagep, fsdata, - exfat_get_block, - &EXFAT_I(mapping->host)->i_size_ondisk); + ret = block_write_begin(mapping, pos, len, pagep, exfat_get_block); #else - ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata, - exfat_get_block, - &EXFAT_I(mapping->host)->i_size_ondisk); + ret = block_write_begin(mapping, pos, len, flags, pagep, + exfat_get_block); #endif if (ret < 0) exfat_write_failed(mapping, pos+len); @@ -448,6 +547,11 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, if (err < len) exfat_write_failed(mapping, pos+len); + if (!(err < 0) && pos + err > ei->valid_size) { + ei->valid_size = pos + err; + mark_inode_dirty(inode); + } + if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) @@ -470,6 +574,8 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) { struct address_space *mapping = iocb->ki_filp->f_mapping; struct inode *inode = mapping->host; + struct exfat_inode_info *ei = EXFAT_I(inode); + loff_t pos = iocb->ki_pos; loff_t size = iocb->ki_pos + iov_iter_count(iter); int rw = iov_iter_rw(iter); ssize_t ret; @@ -493,8 +599,21 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) * condition of exfat_get_block() and ->truncate(). */ ret = blockdev_direct_IO(iocb, inode, iter, exfat_get_block); - if (ret < 0 && (rw & WRITE)) - exfat_write_failed(mapping, size); + if (ret < 0) { + if (rw == WRITE) + exfat_write_failed(mapping, size); + + if (ret != -EIOCBQUEUED) + return ret; + } else + size = pos + ret; + + /* zero the unwritten part in the partially written block */ + if (rw == READ && pos < ei->valid_size && ei->valid_size < size) { + iov_iter_revert(iter, size - ei->valid_size); + iov_iter_zero(size - ei->valid_size, iter); + } + return ret; } @@ -611,6 +730,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) ei->start_clu = info->start_clu; ei->flags = info->flags; ei->type = info->type; + ei->valid_size = info->valid_size; ei->version = 0; ei->hint_stat.eidx = 0; diff --git a/namei.c b/namei.c index 52c437e..6d3d5d1 100644 --- a/namei.c +++ b/namei.c @@ -407,6 +407,7 @@ static int exfat_find_empty_entry(struct inode *inode, i_size_write(inode, size); ei->i_size_ondisk += sbi->cluster_size; ei->i_size_aligned += sbi->cluster_size; + ei->valid_size += sbi->cluster_size; ei->flags = p_dir->flags; inode->i_blocks += sbi->cluster_size >> 9; } @@ -559,6 +560,8 @@ static int exfat_add_entry(struct inode *inode, const char *path, info->size = clu_size; info->num_subdirs = EXFAT_MIN_SUBDIR; } + info->valid_size = info->size; + memset(&info->crtime, 0, sizeof(info->crtime)); memset(&info->mtime, 0, sizeof(info->mtime)); memset(&info->atime, 0, sizeof(info->atime)); @@ -684,6 +687,8 @@ static int exfat_find(struct inode *dir, struct qstr *qname, info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); info->size = le64_to_cpu(ep2->dentry.stream.valid_size); + info->valid_size = le64_to_cpu(ep2->dentry.stream.valid_size); + info->size = le64_to_cpu(ep2->dentry.stream.size); if (info->size == 0) { info->flags = ALLOC_NO_FAT_CHAIN; info->start_clu = EXFAT_EOF_CLUSTER; @@ -1373,6 +1378,7 @@ static int __exfat_rename(struct inode *old_parent_inode, } i_size_write(new_inode, 0); + new_ei->valid_size = 0; new_ei->start_clu = EXFAT_EOF_CLUSTER; new_ei->flags = ALLOC_NO_FAT_CHAIN; } From 7a111ad8de749bf68571932518f2d99a3a9198d8 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sun, 10 Dec 2023 23:53:25 +0900 Subject: [PATCH 099/141] exfat: do not zero the extended part Since the read operation beyond the ValidDataLength returns zero, if we just extend the size of the file, we don't need to zero the extended part, but only change the DataLength without changing the ValidDataLength. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- file.c | 77 ++++++++++++++++++++++++++++++++++++++++++--------------- inode.c | 14 ++++++++++- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/file.c b/file.c index d97bdd0..34a9a55 100644 --- a/file.c +++ b/file.c @@ -20,14 +20,51 @@ 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; + int ret; + unsigned int num_clusters, new_num_clusters, last_clu; + struct exfat_inode_info *ei = EXFAT_I(inode); + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_chain clu; - err = generic_cont_expand_simple(inode, size); - if (err) - return err; + ret = inode_newsize_ok(inode, size); + if (ret) + return ret; + + num_clusters = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi); + new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi); + + if (new_num_clusters == num_clusters) + goto out; + + exfat_chain_set(&clu, ei->start_clu, num_clusters, ei->flags); + ret = exfat_find_last_cluster(sb, &clu, &last_clu); + if (ret) + return ret; + clu.dir = (last_clu == EXFAT_EOF_CLUSTER) ? + EXFAT_EOF_CLUSTER : last_clu + 1; + clu.size = 0; + clu.flags = ei->flags; + + ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters, + &clu, IS_DIRSYNC(inode)); + if (ret) + return ret; + + /* Append new clusters to chain */ + if (clu.flags != ei->flags) { + exfat_chain_cont_cluster(sb, ei->start_clu, num_clusters); + ei->flags = ALLOC_FAT_CHAIN; + } + if (clu.flags == ALLOC_FAT_CHAIN) + if (exfat_ent_set(sb, last_clu, clu.dir)) + goto free_clu; + + if (num_clusters == 0) + ei->start_clu = clu.dir; + +out: #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); @@ -37,23 +74,23 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) #else inode->i_ctime = inode->i_mtime = current_time(inode); #endif - EXFAT_I(inode)->valid_size = size; - mark_inode_dirty(inode); + /* Expanded range not zeroed, do not update valid_size */ + i_size_write(inode, size); - if (!IS_SYNC(inode)) - return 0; + ei->i_size_aligned = round_up(size, sb->s_blocksize); + ei->i_size_ondisk = ei->i_size_aligned; + inode->i_blocks = round_up(size, sbi->cluster_size) >> 9; - 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; + if (IS_DIRSYNC(inode)) + return write_inode_now(inode, 1); + + mark_inode_dirty(inode); + + return 0; - return filemap_fdatawait_range(mapping, start, start + count - 1); +free_clu: + exfat_free_cluster(inode, &clu); + return -EIO; } static bool exfat_allow_set_time(struct exfat_sb_info *sbi, struct inode *inode) diff --git a/inode.c b/inode.c index 764b05b..96102b6 100644 --- a/inode.c +++ b/inode.c @@ -90,8 +90,17 @@ int __exfat_write_inode(struct inode *inode, int sync) if (ei->start_clu == EXFAT_EOF_CLUSTER) on_disk_size = 0; - ep2->dentry.stream.valid_size = cpu_to_le64(ei->valid_size); ep2->dentry.stream.size = cpu_to_le64(on_disk_size); + /* + * mmap write does not use exfat_write_end(), valid_size may be + * extended to the sector-aligned length in exfat_get_block(). + * So we need to fixup valid_size to the writren length. + */ + if (on_disk_size < ei->valid_size) + ep2->dentry.stream.valid_size = ep2->dentry.stream.size; + else + ep2->dentry.stream.valid_size = cpu_to_le64(ei->valid_size); + if (on_disk_size) { ep2->dentry.stream.flags = ei->flags; ep2->dentry.stream.start_clu = cpu_to_le32(ei->start_clu); @@ -372,6 +381,9 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, pos, ei->i_size_aligned); goto unlock_ret; } + + ei->valid_size = EXFAT_BLK_TO_B(iblock + max_blocks, sb); + mark_inode_dirty(inode); } else { valid_blks = EXFAT_B_TO_BLK(ei->valid_size, sb); From b918344448212b43466dd74a6c24cefb9375a05c Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sun, 18 Feb 2024 13:33:00 +0900 Subject: [PATCH 100/141] exfat: fix zero the unwritten part for dio read For dio read, bio will be leave in flight when a successful partial aio read have been setup, blockdev_direct_IO() will return -EIOCBQUEUED. In the case, iter->iov_offset will be not advanced, the oops reported by syzbot will occur if revert iter->iov_offset with iov_iter_revert(). The unwritten part had been zeroed by aio read, so there is no need to zero it in dio read. Reported-by: syzbot+fd404f6b03a58e8bc403@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=fd404f6b03a58e8bc403 Fixes: 11a347fb6cef ("exfat: change to get file size from DataLength") Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- inode.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/inode.c b/inode.c index 96102b6..e027620 100644 --- a/inode.c +++ b/inode.c @@ -588,7 +588,7 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) struct inode *inode = mapping->host; struct exfat_inode_info *ei = EXFAT_I(inode); loff_t pos = iocb->ki_pos; - loff_t size = iocb->ki_pos + iov_iter_count(iter); + loff_t size = pos + iov_iter_count(iter); int rw = iov_iter_rw(iter); ssize_t ret; @@ -612,11 +612,10 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) */ ret = blockdev_direct_IO(iocb, inode, iter, exfat_get_block); if (ret < 0) { - if (rw == WRITE) + if (rw == WRITE && ret != -EIOCBQUEUED) exfat_write_failed(mapping, size); - if (ret != -EIOCBQUEUED) - return ret; + return ret; } else size = pos + ret; From 21313279887dba506b42eeb55a8e497d69efa17d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 1 Mar 2024 20:23:44 +0900 Subject: [PATCH 101/141] exfat: fix appending discontinuous clusters to empty file Eric Hong found that when using ftruncate to expand an empty file, exfat_ent_set() will fail if discontinuous clusters are allocated. The reason is that the empty file does not have a cluster chain, but exfat_ent_set() attempts to append the newly allocated cluster to the cluster chain. In addition, exfat_find_last_cluster() only supports finding the last cluster in a non-empty file. So this commit adds a check whether the file is empty. If the file is empty, exfat_find_last_cluster() and exfat_ent_set() are no longer called as they do not need to be called. Fixes: f55c096f62f1 ("exfat: do not zero the extended part") Reported-by: Eric Hong Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- file.c | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/file.c b/file.c index 34a9a55..43faa0f 100644 --- a/file.c +++ b/file.c @@ -37,13 +37,18 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) if (new_num_clusters == num_clusters) goto out; - exfat_chain_set(&clu, ei->start_clu, num_clusters, ei->flags); - ret = exfat_find_last_cluster(sb, &clu, &last_clu); - if (ret) - return ret; + if (num_clusters) { + exfat_chain_set(&clu, ei->start_clu, num_clusters, ei->flags); + ret = exfat_find_last_cluster(sb, &clu, &last_clu); + if (ret) + return ret; + + clu.dir = last_clu + 1; + } else { + last_clu = EXFAT_EOF_CLUSTER; + clu.dir = EXFAT_EOF_CLUSTER; + } - clu.dir = (last_clu == EXFAT_EOF_CLUSTER) ? - EXFAT_EOF_CLUSTER : last_clu + 1; clu.size = 0; clu.flags = ei->flags; @@ -53,17 +58,19 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) return ret; /* Append new clusters to chain */ - if (clu.flags != ei->flags) { - exfat_chain_cont_cluster(sb, ei->start_clu, num_clusters); - ei->flags = ALLOC_FAT_CHAIN; - } - if (clu.flags == ALLOC_FAT_CHAIN) - if (exfat_ent_set(sb, last_clu, clu.dir)) - goto free_clu; - - if (num_clusters == 0) + if (num_clusters) { + if (clu.flags != ei->flags) + if (exfat_chain_cont_cluster(sb, ei->start_clu, num_clusters)) + goto free_clu; + + if (clu.flags == ALLOC_FAT_CHAIN) + if (exfat_ent_set(sb, last_clu, clu.dir)) + goto free_clu; + } else ei->start_clu = clu.dir; + ei->flags = clu.flags; + out: #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) From 1aa99bfa180cf4271c2e8ac6a2bca1e9f029390c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 7 Aug 2024 15:26:15 +0900 Subject: [PATCH 102/141] exfat: don't RCU-free the sbi There are no RCU critical sections for accessing any information in the sbi, so drop the call_rcu indirection for freeing the sbi. Signed-off-by: Christoph Hellwig Message-Id: <20230809220545.1308228-10-hch@lst.de> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 -- super.c | 15 ++++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index d55741e..3a4f36d 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -288,8 +288,6 @@ struct exfat_sb_info { spinlock_t inode_hash_lock; struct hlist_head inode_hashtable[EXFAT_HASH_SIZE]; - - struct rcu_head rcu; }; #define EXFAT_CACHE_VALID 0 diff --git a/super.c b/super.c index 9250090..f9982d1 100644 --- a/super.c +++ b/super.c @@ -35,16 +35,6 @@ static void exfat_free_iocharset(struct exfat_sb_info *sbi) kfree(sbi->options.iocharset); } -static void exfat_delayed_free(struct rcu_head *p) -{ - struct exfat_sb_info *sbi = container_of(p, struct exfat_sb_info, rcu); - - unload_nls(sbi->nls_io); - exfat_free_iocharset(sbi); - exfat_free_upcase_table(sbi); - kfree(sbi); -} - static void exfat_put_super(struct super_block *sb) { struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -54,7 +44,10 @@ static void exfat_put_super(struct super_block *sb) brelse(sbi->boot_bh); mutex_unlock(&sbi->s_lock); - call_rcu(&sbi->rcu, exfat_delayed_free); + unload_nls(sbi->nls_io); + exfat_free_iocharset(sbi); + exfat_free_upcase_table(sbi); + kfree(sbi); } static int exfat_sync_fs(struct super_block *sb, int wait) From 8269c2d7f032cdc59295a14d3418bda40be7c573 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 7 Aug 2024 15:27:43 +0900 Subject: [PATCH 103/141] exfat: free the sbi and iocharset in ->kill_sb As a rule of thumb everything allocated to the fs_context and moved into the super_block should be freed by ->kill_sb so that the teardown handling doesn't need to be duplicated between the fill_super error path and put_super. Implement an exfat-specific kill_sb method to do that and share the code with the mount contex free helper for the mount error handling case. Signed-off-by: Christoph Hellwig Message-Id: <20230809220545.1308228-11-hch@lst.de> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- super.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/super.c b/super.c index f9982d1..7fd2da2 100644 --- a/super.c +++ b/super.c @@ -45,9 +45,7 @@ static void exfat_put_super(struct super_block *sb) mutex_unlock(&sbi->s_lock); unload_nls(sbi->nls_io); - exfat_free_iocharset(sbi); exfat_free_upcase_table(sbi); - kfree(sbi); } static int exfat_sync_fs(struct super_block *sb, int wait) @@ -741,9 +739,6 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) check_nls_io: unload_nls(sbi->nls_io); - exfat_free_iocharset(sbi); - sb->s_fs_info = NULL; - kfree(sbi); return err; } @@ -752,14 +747,18 @@ static int exfat_get_tree(struct fs_context *fc) return get_tree_bdev(fc, exfat_fill_super); } +static void exfat_free_sbi(struct exfat_sb_info *sbi) +{ + exfat_free_iocharset(sbi); + kfree(sbi); +} + static void exfat_free(struct fs_context *fc) { struct exfat_sb_info *sbi = fc->s_fs_info; - if (sbi) { - exfat_free_iocharset(sbi); - kfree(sbi); - } + if (sbi) + exfat_free_sbi(sbi); } static int exfat_reconfigure(struct fs_context *fc) @@ -804,12 +803,21 @@ static int exfat_init_fs_context(struct fs_context *fc) return 0; } +static void exfat_kill_sb(struct super_block *sb) +{ + struct exfat_sb_info *sbi = sb->s_fs_info; + + kill_block_super(sb); + if (sbi) + exfat_free_sbi(sbi); +} + static struct file_system_type exfat_fs_type = { .owner = THIS_MODULE, .name = "exfat", .init_fs_context = exfat_init_fs_context, .parameters = exfat_parameters, - .kill_sb = kill_block_super, + .kill_sb = exfat_kill_sb, .fs_flags = FS_REQUIRES_DEV, }; From 7ef5cda2349988f5869384929e3ff5162f406bc2 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 7 Aug 2024 15:30:23 +0900 Subject: [PATCH 104/141] exfat: move freeing sbi, upcase table and dropping nls into rcu-delayed helper That stuff can be accessed by ->d_hash()/->d_compare(); as it is, we have a hard-to-hit UAF if rcu pathwalk manages to get into ->d_hash() on a filesystem that is in process of getting shut down. Besides, having nls and upcase table cleanup moved from ->put_super() towards the place where sbi is freed makes for simpler failure exits. Acked-by: Christian Brauner Signed-off-by: Al Viro Signed-off-by: Namjae Jeon --- exfat_fs.h | 1 + nls.c | 14 ++++---------- super.c | 20 +++++++++++--------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 3a4f36d..4760652 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -288,6 +288,7 @@ struct exfat_sb_info { spinlock_t inode_hash_lock; struct hlist_head inode_hashtable[EXFAT_HASH_SIZE]; + struct rcu_head rcu; }; #define EXFAT_CACHE_VALID 0 diff --git a/nls.c b/nls.c index a5d5e25..335a6d2 100644 --- a/nls.c +++ b/nls.c @@ -656,7 +656,6 @@ static int exfat_load_upcase_table(struct super_block *sb, unsigned int sect_size = sb->s_blocksize; unsigned int i, index = 0; u32 chksum = 0; - int ret; unsigned char skip = false; unsigned short *upcase_table; @@ -674,8 +673,7 @@ static int exfat_load_upcase_table(struct super_block *sb, if (!bh) { exfat_err(sb, "failed to read sector(0x%llx)", (unsigned long long)sector); - ret = -EIO; - goto free_table; + return -EIO; } sector++; for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) { @@ -702,15 +700,12 @@ static int exfat_load_upcase_table(struct super_block *sb, exfat_err(sb, "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)", index, chksum, utbl_checksum); - ret = -EINVAL; -free_table: - exfat_free_upcase_table(sbi); - return ret; + return -EINVAL; } static int exfat_load_default_upcase_table(struct super_block *sb) { - int i, ret = -EIO; + int i; struct exfat_sb_info *sbi = EXFAT_SB(sb); unsigned char skip = false; unsigned short uni = 0, *upcase_table; @@ -741,8 +736,7 @@ static int exfat_load_default_upcase_table(struct super_block *sb) return 0; /* FATAL error: default upcase table has error */ - exfat_free_upcase_table(sbi); - return ret; + return -EIO; } int exfat_create_upcase_table(struct super_block *sb) diff --git a/super.c b/super.c index 7fd2da2..d8fa375 100644 --- a/super.c +++ b/super.c @@ -43,9 +43,6 @@ static void exfat_put_super(struct super_block *sb) exfat_free_bitmap(sbi); brelse(sbi->boot_bh); mutex_unlock(&sbi->s_lock); - - unload_nls(sbi->nls_io); - exfat_free_upcase_table(sbi); } static int exfat_sync_fs(struct super_block *sb, int wait) @@ -620,7 +617,7 @@ static int __exfat_fill_super(struct super_block *sb) ret = exfat_load_bitmap(sb); if (ret) { exfat_err(sb, "failed to load alloc-bitmap"); - goto free_upcase_table; + goto free_bh; } ret = exfat_count_used_clusters(sb, &sbi->used_clusters); @@ -633,8 +630,6 @@ static int __exfat_fill_super(struct super_block *sb) free_alloc_bitmap: exfat_free_bitmap(sbi); -free_upcase_table: - exfat_free_upcase_table(sbi); free_bh: brelse(sbi->boot_bh); return ret; @@ -733,12 +728,10 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_root = NULL; free_table: - exfat_free_upcase_table(sbi); exfat_free_bitmap(sbi); brelse(sbi->boot_bh); check_nls_io: - unload_nls(sbi->nls_io); return err; } @@ -803,13 +796,22 @@ static int exfat_init_fs_context(struct fs_context *fc) return 0; } +static void delayed_free(struct rcu_head *p) +{ + struct exfat_sb_info *sbi = container_of(p, struct exfat_sb_info, rcu); + + unload_nls(sbi->nls_io); + exfat_free_upcase_table(sbi); + exfat_free_sbi(sbi); +} + static void exfat_kill_sb(struct super_block *sb) { struct exfat_sb_info *sbi = sb->s_fs_info; kill_block_super(sb); if (sbi) - exfat_free_sbi(sbi); + call_rcu(&sbi->rcu, delayed_free); } static struct file_system_type exfat_fs_type = { From 498ec0fc05c29c124c7d7ed66ff47e3b7120b7e1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 7 Aug 2024 15:36:09 +0900 Subject: [PATCH 105/141] mm, slab: remove last vestiges of SLAB_MEM_SPREAD Yes, yes, I know the slab people were planning on going slow and letting every subsystem fight this thing on their own. But let's just rip off the band-aid and get it over and done with. I don't want to see a number of unnecessary pull requests just to get rid of a flag that no longer has any meaning. This was mainly done with a couple of 'sed' scripts and then some manual cleanup of the end result. Link: https://lore.kernel.org/all/CAHk-=wji0u+OOtmAOD-5JV3SXcRJF___k_+8XNKmak0yd5vW1Q@mail.gmail.com/ Signed-off-by: Linus Torvalds Signed-off-by: Namjae Jeon --- cache.c | 4 ++++ super.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/cache.c b/cache.c index 5a2f119..26792b6 100644 --- a/cache.c +++ b/cache.c @@ -46,7 +46,11 @@ int exfat_cache_init(void) { exfat_cachep = kmem_cache_create("exfat_cache", sizeof(struct exfat_cache), +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0) + 0, SLAB_RECLAIM_ACCOUNT, +#else 0, SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, +#endif exfat_cache_init_once); if (!exfat_cachep) return -ENOMEM; diff --git a/super.c b/super.c index d8fa375..7c319d9 100644 --- a/super.c +++ b/super.c @@ -845,7 +845,11 @@ static int __init init_exfat_fs(void) exfat_inode_cachep = kmem_cache_create("exfat_inode_cache", sizeof(struct exfat_inode_info), +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0) + 0, SLAB_RECLAIM_ACCOUNT, +#else 0, SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD, +#endif exfat_inode_init_once); if (!exfat_inode_cachep) { err = -ENOMEM; From 856f32ba9c8c854d169a21de3a89b3a2350c257d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:13:29 +0900 Subject: [PATCH 106/141] exfat: add __exfat_get_dentry_set() helper Since exfat_get_dentry_set() invokes the validate functions of exfat_validate_entry(), it only supports getting a directory entry set of an existing file, doesn't support getting an empty entry set. To remove the limitation, add this helper. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 63 ++++++++++++++++++++++++++++++++++++------------------ exfat_fs.h | 2 +- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/dir.c b/dir.c index 7ac26d0..d3d5159 100644 --- a/dir.c +++ b/dir.c @@ -784,7 +784,6 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb, } enum exfat_validate_dentry_mode { - ES_MODE_STARTED, ES_MODE_GET_FILE_ENTRY, ES_MODE_GET_STRM_ENTRY, ES_MODE_GET_NAME_ENTRY, @@ -799,11 +798,6 @@ static bool exfat_validate_entry(unsigned int type, return false; switch (*mode) { - case ES_MODE_STARTED: - if (type != TYPE_FILE && type != TYPE_DIR) - return false; - *mode = ES_MODE_GET_FILE_ENTRY; - break; case ES_MODE_GET_FILE_ENTRY: if (type != TYPE_STREAM) return false; @@ -843,7 +837,7 @@ struct exfat_dentry *exfat_get_dentry_cached( } /* - * Returns a set of dentries for a file or dir. + * Returns a set of dentries. * * Note It provides a direct pointer to bh->data via exfat_get_dentry_cached(). * User should call exfat_get_dentry_set() after setting 'modified' to apply @@ -851,22 +845,24 @@ struct exfat_dentry *exfat_get_dentry_cached( * * in: * sb+p_dir+entry: indicates a file/dir - * type: specifies how many dentries should be included. + * num_entries: specifies how many dentries should be included. + * It will be set to es->num_entries if it is not 0. + * If num_entries is 0, es->num_entries will be obtained + * from the first dentry. + * out: + * es: pointer of entry set on success. * return: - * pointer of entry set on success, - * NULL on failure. + * 0 on success + * -error code on failure */ -int exfat_get_dentry_set(struct exfat_entry_set_cache *es, +static int __exfat_get_dentry_set(struct exfat_entry_set_cache *es, struct super_block *sb, struct exfat_chain *p_dir, int entry, - unsigned int type) + unsigned int num_entries) { int ret, i, num_bh; unsigned int off; sector_t sec; struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct exfat_dentry *ep; - int num_entries; - enum exfat_validate_dentry_mode mode = ES_MODE_STARTED; struct buffer_head *bh; if (p_dir->dir == DIR_DELETED) { @@ -889,12 +885,18 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, return -EIO; es->bh[es->num_bh++] = bh; - ep = exfat_get_dentry_cached(es, ES_IDX_FILE); - if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) - goto put_es; + if (num_entries == ES_ALL_ENTRIES) { + struct exfat_dentry *ep; + + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); + if (ep->type != EXFAT_FILE) { + brelse(bh); + return -EIO; + } + + num_entries = ep->dentry.file.num_ext + 1; + } - num_entries = type == ES_ALL_ENTRIES ? - ep->dentry.file.num_ext + 1 : type; es->num_entries = num_entries; num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); @@ -927,8 +929,27 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, es->bh[es->num_bh++] = bh; } + return 0; + +put_es: + exfat_put_dentry_set(es, false); + return -EIO; +} + +int exfat_get_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, + int entry, unsigned int num_entries) +{ + int ret, i; + struct exfat_dentry *ep; + enum exfat_validate_dentry_mode mode = ES_MODE_GET_FILE_ENTRY; + + ret = __exfat_get_dentry_set(es, sb, p_dir, entry, num_entries); + if (ret < 0) + return ret; + /* validate cached dentries */ - for (i = ES_IDX_STREAM; i < num_entries; i++) { + for (i = ES_IDX_STREAM; i < es->num_entries; i++) { ep = exfat_get_dentry_cached(es, i); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) goto put_es; diff --git a/exfat_fs.h b/exfat_fs.h index 4760652..f39f497 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -524,7 +524,7 @@ struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, int num); int exfat_get_dentry_set(struct exfat_entry_set_cache *es, struct super_block *sb, struct exfat_chain *p_dir, int entry, - unsigned int type); + unsigned int num_entries); int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); From 2c2c8402bbb9d476bf5792f404dd3f29187a0b06 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:14:30 +0900 Subject: [PATCH 107/141] exfat: add exfat_get_empty_dentry_set() helper This helper is used to lookup empty dentry set. If there are no enough empty dentries at the input location, this helper will return the number of dentries that need to be skipped for the next lookup. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ exfat_fs.h | 3 +++ 2 files changed, 82 insertions(+) diff --git a/dir.c b/dir.c index d3d5159..5cbb776 100644 --- a/dir.c +++ b/dir.c @@ -961,6 +961,85 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, return -EIO; } +static int exfat_validate_empty_dentry_set(struct exfat_entry_set_cache *es) +{ + struct exfat_dentry *ep; + struct buffer_head *bh; + int i, off; + bool unused_hit = false; + + /* + * ONLY UNUSED OR DELETED DENTRIES ARE ALLOWED: + * Although it violates the specification for a deleted entry to + * follow an unused entry, some exFAT implementations could work + * like this. Therefore, to improve compatibility, let's allow it. + */ + for (i = 0; i < es->num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); + if (ep->type == EXFAT_UNUSED) { + unused_hit = true; + } else if (!IS_EXFAT_DELETED(ep->type)) { + if (unused_hit) + goto err_used_follow_unused; + i++; + goto count_skip_entries; + } + } + + return 0; + +err_used_follow_unused: + off = es->start_off + (i << DENTRY_SIZE_BITS); + bh = es->bh[EXFAT_B_TO_BLK(off, es->sb)]; + + exfat_fs_error(es->sb, + "in sector %lld, dentry %d should be unused, but 0x%x", + bh->b_blocknr, off >> DENTRY_SIZE_BITS, ep->type); + + return -EIO; + +count_skip_entries: + es->num_entries = EXFAT_B_TO_DEN(EXFAT_BLK_TO_B(es->num_bh, es->sb) - es->start_off); + for (; i < es->num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); + if (IS_EXFAT_DELETED(ep->type)) + break; + } + + return i; +} + +/* + * Get an empty dentry set. + * + * in: + * sb+p_dir+entry: indicates the empty dentry location + * num_entries: specifies how many empty dentries should be included. + * out: + * es: pointer of empty dentry set on success. + * return: + * 0 : on success + * >0 : the dentries are not empty, the return value is the number of + * dentries to be skipped for the next lookup. + * <0 : on failure + */ +int exfat_get_empty_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, + int entry, unsigned int num_entries) +{ + int ret; + + ret = __exfat_get_dentry_set(es, sb, p_dir, entry, num_entries); + if (ret < 0) + return ret; + + ret = exfat_validate_empty_dentry_set(es); + if (ret) + exfat_put_dentry_set(es, false); + + return ret; +} + static inline void exfat_reset_empty_hint(struct exfat_hint_femp *hint_femp) { hint_femp->eidx = EXFAT_HINT_NONE; diff --git a/exfat_fs.h b/exfat_fs.h index f39f497..4019c99 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -525,6 +525,9 @@ struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, int exfat_get_dentry_set(struct exfat_entry_set_cache *es, struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned int num_entries); +int exfat_get_empty_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, int entry, + unsigned int num_entries); int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); From 70c6e7ddd38270fd5ad1aac84648c90bcc88f51f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:33:32 +0900 Subject: [PATCH 108/141] exfat: convert exfat_add_entry() to use dentry cache After this conversion, if dirsync or sync is enabled, the number of synchronized dentries in exfat_add_entry() will change from 2 to 1. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 39 +++++++++------------------------------ exfat_fs.h | 6 +++--- namei.c | 12 ++++++++++-- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/dir.c b/dir.c index 5cbb776..43c48ef 100644 --- a/dir.c +++ b/dir.c @@ -455,55 +455,34 @@ static void exfat_init_name_entry(struct exfat_dentry *ep, } } -int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, - int entry, unsigned int type, unsigned int start_clu, - unsigned long long size) +void exfat_init_dir_entry(struct exfat_entry_set_cache *es, + unsigned int type, unsigned int start_clu, + unsigned long long size, struct timespec64 *ts) { - struct super_block *sb = inode->i_sb; + struct super_block *sb = es->sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct timespec64 ts; struct exfat_dentry *ep; - struct buffer_head *bh; - - ts = current_time(inode); - - /* - * We cannot use exfat_get_dentry_set here because file ep is not - * initialized yet. - */ - ep = exfat_get_dentry(sb, p_dir, entry, &bh); - if (!ep) - return -EIO; + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); exfat_set_entry_type(ep, type); - exfat_set_entry_time(sbi, &ts, + exfat_set_entry_time(sbi, ts, &ep->dentry.file.create_tz, &ep->dentry.file.create_time, &ep->dentry.file.create_date, &ep->dentry.file.create_time_cs); - exfat_set_entry_time(sbi, &ts, + exfat_set_entry_time(sbi, ts, &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, &ep->dentry.file.modify_date, &ep->dentry.file.modify_time_cs); - exfat_set_entry_time(sbi, &ts, + exfat_set_entry_time(sbi, ts, &ep->dentry.file.access_tz, &ep->dentry.file.access_time, &ep->dentry.file.access_date, NULL); - exfat_update_bh(bh, IS_DIRSYNC(inode)); - brelse(bh); - - ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh); - if (!ep) - return -EIO; - + ep = exfat_get_dentry_cached(es, ES_IDX_STREAM); exfat_init_stream_entry(ep, start_clu, size); - exfat_update_bh(bh, IS_DIRSYNC(inode)); - brelse(bh); - - return 0; } int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, diff --git a/exfat_fs.h b/exfat_fs.h index 4019c99..4e9844f 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -503,9 +503,9 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, extern const struct inode_operations exfat_dir_inode_operations; extern const struct file_operations exfat_dir_operations; unsigned int exfat_get_entry_type(struct exfat_dentry *p_entry); -int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, - int entry, unsigned int type, unsigned int start_clu, - unsigned long long size); +void exfat_init_dir_entry(struct exfat_entry_set_cache *es, + unsigned int type, unsigned int start_clu, + unsigned long long size, struct timespec64 *ts); int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, int entry, int num_entries, struct exfat_uni_name *p_uniname); int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, diff --git a/namei.c b/namei.c index 6d3d5d1..aa28d2b 100644 --- a/namei.c +++ b/namei.c @@ -500,6 +500,8 @@ static int exfat_add_entry(struct inode *inode, const char *path, struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_uni_name uniname; struct exfat_chain clu; + struct timespec64 ts = current_time(inode); + struct exfat_entry_set_cache es; int clu_size = 0; unsigned int start_clu = EXFAT_FREE_CLUSTER; @@ -532,8 +534,14 @@ static int exfat_add_entry(struct inode *inode, const char *path, /* fill the dos name directory entry information of the created file. * the first cluster is not determined yet. (0) */ - ret = exfat_init_dir_entry(inode, p_dir, dentry, type, - start_clu, clu_size); + + ret = exfat_get_empty_dentry_set(&es, sb, p_dir, dentry, num_entries); + if (ret) + goto out; + + exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts); + + ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); if (ret) goto out; From 6ceba2a190901368786f39549874764f19e2584a Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:34:45 +0900 Subject: [PATCH 109/141] exfat: convert exfat_remove_entries() to use dentry cache Before this conversion, in exfat_remove_entries(), to mark the dentries in a dentry set as deleted, the sync times is equals the dentry numbers if 'dirsync' or 'sync' is enabled. That affects not only performance but also device life. After this conversion, only needs to be synchronized once if 'dirsync' or 'sync' is enabled. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 17 ++--- exfat_fs.h | 4 +- namei.c | 184 ++++++++++++++++++++++++----------------------------- 3 files changed, 90 insertions(+), 115 deletions(-) diff --git a/dir.c b/dir.c index 43c48ef..ab1935e 100644 --- a/dir.c +++ b/dir.c @@ -584,28 +584,23 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, return 0; } -int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, - int entry, int order, int num_entries) +void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, + int order) { - struct super_block *sb = inode->i_sb; int i; struct exfat_dentry *ep; - struct buffer_head *bh; - for (i = order; i < num_entries; i++) { - ep = exfat_get_dentry(sb, p_dir, entry + i, &bh); - if (!ep) - return -EIO; + for (i = order; i < es->num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) exfat_free_benign_secondary_clusters(inode, ep); exfat_set_entry_type(ep, TYPE_DELETED); - exfat_update_bh(bh, IS_DIRSYNC(inode)); - brelse(bh); } - return 0; + if (order < es->num_entries) + es->modified = true; } void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) diff --git a/exfat_fs.h b/exfat_fs.h index 4e9844f..f323c3c 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -508,8 +508,8 @@ void exfat_init_dir_entry(struct exfat_entry_set_cache *es, unsigned long long size, struct timespec64 *ts); int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, int entry, int num_entries, struct exfat_uni_name *p_uniname); -int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, - int entry, int order, int num_entries); +void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, + int order); int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, int entry); void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es); diff --git a/namei.c b/namei.c index aa28d2b..a305bfa 100644 --- a/namei.c +++ b/namei.c @@ -827,12 +827,11 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, static int exfat_unlink(struct inode *dir, struct dentry *dentry) { struct exfat_chain cdir; - struct exfat_dentry *ep; struct super_block *sb = dir->i_sb; struct inode *inode = dentry->d_inode; struct exfat_inode_info *ei = EXFAT_I(inode); - struct buffer_head *bh; - int num_entries, entry, err = 0; + struct exfat_entry_set_cache es; + int entry, err = 0; mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_chain_dup(&cdir, &ei->dir); @@ -843,26 +842,20 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) goto unlock; } - ep = exfat_get_dentry(sb, &cdir, entry, &bh); - if (!ep) { - err = -EIO; - goto unlock; - } - num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep); - if (num_entries < 0) { + err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES); + if (err) { err = -EIO; - brelse(bh); goto unlock; } - num_entries++; - brelse(bh); exfat_set_volume_dirty(sb); + /* update the directory entry */ - if (exfat_remove_entries(dir, &cdir, entry, 0, num_entries)) { - err = -EIO; + exfat_remove_entries(inode, &es, ES_IDX_FILE); + + err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); + if (err) goto unlock; - } /* This doesn't modify ei */ ei->dir.dir = DIR_DELETED; @@ -1021,13 +1014,12 @@ static int exfat_check_dir_empty(struct super_block *sb, static int exfat_rmdir(struct inode *dir, struct dentry *dentry) { struct inode *inode = dentry->d_inode; - struct exfat_dentry *ep; struct exfat_chain cdir, clu_to_free; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); - struct buffer_head *bh; - int num_entries, entry, err; + struct exfat_entry_set_cache es; + int entry, err; mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); @@ -1051,27 +1043,20 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) goto unlock; } - ep = exfat_get_dentry(sb, &cdir, entry, &bh); - if (!ep) { - err = -EIO; - goto unlock; - } - - num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep); - if (num_entries < 0) { + err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES); + if (err) { err = -EIO; - brelse(bh); goto unlock; } - num_entries++; - brelse(bh); exfat_set_volume_dirty(sb); - err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries); - if (err) { - exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err); + + exfat_remove_entries(inode, &es, ES_IDX_FILE); + + err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir)); + if (err) goto unlock; - } + ei->dir.dir = DIR_DELETED; inode_inc_iversion(dir); @@ -1115,36 +1100,40 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, int oldentry, struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { - int ret, num_old_entries, num_new_entries; + int ret, num_new_entries; struct exfat_dentry *epold, *epnew; struct super_block *sb = inode->i_sb; - struct buffer_head *new_bh, *old_bh; + struct buffer_head *new_bh; + struct exfat_entry_set_cache old_es; int sync = IS_DIRSYNC(inode); - epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh); - if (!epold) - return -EIO; - - num_old_entries = exfat_count_ext_entries(sb, p_dir, oldentry, epold); - if (num_old_entries < 0) - return -EIO; - num_old_entries++; - num_new_entries = exfat_calc_num_entries(p_uniname); if (num_new_entries < 0) return num_new_entries; - if (num_old_entries < num_new_entries) { + ret = exfat_get_dentry_set(&old_es, sb, p_dir, oldentry, ES_ALL_ENTRIES); + if (ret) { + ret = -EIO; + return ret; + } + + epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE); + + if (old_es.num_entries < num_new_entries) { int newentry; newentry = exfat_find_empty_entry(inode, p_dir, num_new_entries); - if (newentry < 0) - return newentry; /* -EIO or -ENOSPC */ + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ + goto put_old_es; + } epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh); - if (!epnew) - return -EIO; + if (!epnew) { + ret = -EIO; + goto put_old_es; + } *epnew = *epold; if (exfat_get_entry_type(epnew) == TYPE_FILE) { @@ -1152,30 +1141,25 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(new_bh, sync); - brelse(old_bh); brelse(new_bh); - epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh); - if (!epold) - return -EIO; + epold = exfat_get_dentry_cached(&old_es, ES_IDX_STREAM); epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh); if (!epnew) { - brelse(old_bh); - return -EIO; + ret = -EIO; + goto put_old_es; } *epnew = *epold; exfat_update_bh(new_bh, sync); - brelse(old_bh); brelse(new_bh); ret = exfat_init_ext_entry(inode, p_dir, newentry, num_new_entries, p_uniname); if (ret) - return ret; + goto put_old_es; - exfat_remove_entries(inode, p_dir, oldentry, 0, - num_old_entries); + exfat_remove_entries(inode, &old_es, ES_IDX_FILE); ei->dir = *p_dir; ei->entry = newentry; } else { @@ -1183,37 +1167,29 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, epold->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); ei->attr |= EXFAT_ATTR_ARCHIVE; } - exfat_update_bh(old_bh, sync); - brelse(old_bh); ret = exfat_init_ext_entry(inode, p_dir, oldentry, num_new_entries, p_uniname); if (ret) - return ret; + goto put_old_es; - exfat_remove_entries(inode, p_dir, oldentry, num_new_entries, - num_old_entries); + exfat_remove_entries(inode, &old_es, num_new_entries); } - return 0; + return exfat_put_dentry_set(&old_es, sync); + +put_old_es: + exfat_put_dentry_set(&old_es, false); + return ret; } static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, int oldentry, struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { - int ret, newentry, num_new_entries, num_old_entries; + int ret, newentry, num_new_entries; struct exfat_dentry *epmov, *epnew; struct super_block *sb = inode->i_sb; - struct buffer_head *mov_bh, *new_bh; - - epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh); - if (!epmov) - return -EIO; - - num_old_entries = exfat_count_ext_entries(sb, p_olddir, oldentry, - epmov); - if (num_old_entries < 0) - return -EIO; - num_old_entries++; + struct buffer_head *new_bh; + struct exfat_entry_set_cache mov_es; num_new_entries = exfat_calc_num_entries(p_uniname); if (num_new_entries < 0) @@ -1223,31 +1199,35 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, if (newentry < 0) return newentry; /* -EIO or -ENOSPC */ - epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh); - if (!epnew) + ret = exfat_get_dentry_set(&mov_es, sb, p_olddir, oldentry, + ES_ALL_ENTRIES); + if (ret) return -EIO; + epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE); + epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh); + if (!epnew) { + ret = -EIO; + goto put_mov_es; + } + *epnew = *epmov; if (exfat_get_entry_type(epnew) == TYPE_FILE) { epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(new_bh, IS_DIRSYNC(inode)); - brelse(mov_bh); brelse(new_bh); - epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh); - if (!epmov) - return -EIO; + epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_STREAM); epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh); if (!epnew) { - brelse(mov_bh); - return -EIO; + ret = -EIO; + goto put_mov_es; } *epnew = *epmov; exfat_update_bh(new_bh, IS_DIRSYNC(inode)); - brelse(mov_bh); brelse(new_bh); ret = exfat_init_ext_entry(inode, p_newdir, newentry, num_new_entries, @@ -1255,13 +1235,18 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, if (ret) return ret; - exfat_remove_entries(inode, p_olddir, oldentry, 0, num_old_entries); + exfat_remove_entries(inode, &mov_es, ES_IDX_FILE); exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, p_newdir->flags); ei->entry = newentry; - return 0; + return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(inode)); + +put_mov_es: + exfat_put_dentry_set(&mov_es, false); + + return ret; } /* rename or move a old file into a new file */ @@ -1279,7 +1264,6 @@ static int __exfat_rename(struct inode *old_parent_inode, struct exfat_sb_info *sbi = EXFAT_SB(sb); const unsigned char *new_path = new_dentry->d_name.name; struct inode *new_inode = new_dentry->d_inode; - int num_entries; struct exfat_inode_info *new_ei = NULL; unsigned int new_entry_type = TYPE_UNUSED; int new_entry = 0; @@ -1350,25 +1334,21 @@ static int __exfat_rename(struct inode *old_parent_inode, &newdir, &uni_name, ei); if (!ret && new_inode) { + struct exfat_entry_set_cache es; + /* delete entries of new_dir */ - ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); - if (!ep) { + ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry, + ES_ALL_ENTRIES); + if (ret) { ret = -EIO; goto del_out; } - num_entries = exfat_count_ext_entries(sb, p_dir, new_entry, ep); - if (num_entries < 0) { - ret = -EIO; - goto del_out; - } - brelse(new_bh); + exfat_remove_entries(new_inode, &es, ES_IDX_FILE); - if (exfat_remove_entries(new_inode, p_dir, new_entry, 0, - num_entries + 1)) { - ret = -EIO; + ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode)); + if (ret) goto del_out; - } /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */ if (new_entry_type == TYPE_DIR && From 220ba131fbe704f3ccfc9f226a1c277a1200ea17 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:35:49 +0900 Subject: [PATCH 110/141] exfat: move free cluster out of exfat_init_ext_entry() exfat_init_ext_entry() is an init function, it's a bit strange to free cluster in it. And the argument 'inode' will be removed from exfat_init_ext_entry(). So this commit changes to free the cluster in exfat_remove_entries(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 3 --- namei.c | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dir.c b/dir.c index ab1935e..6d314fc 100644 --- a/dir.c +++ b/dir.c @@ -571,9 +571,6 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, if (!ep) return -EIO; - if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) - exfat_free_benign_secondary_clusters(inode, ep); - exfat_init_name_entry(ep, uniname); exfat_update_bh(bh, sync); brelse(bh); diff --git a/namei.c b/namei.c index a305bfa..defae1e 100644 --- a/namei.c +++ b/namei.c @@ -1167,12 +1167,13 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, epold->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); ei->attr |= EXFAT_ATTR_ARCHIVE; } + + exfat_remove_entries(inode, &old_es, ES_IDX_FIRST_FILENAME + 1); + ret = exfat_init_ext_entry(inode, p_dir, oldentry, num_new_entries, p_uniname); if (ret) goto put_old_es; - - exfat_remove_entries(inode, &old_es, num_new_entries); } return exfat_put_dentry_set(&old_es, sync); From 11deb2077655d1f7d3d6b8a58d7468e70ce3419f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:36:56 +0900 Subject: [PATCH 111/141] exfat: convert exfat_init_ext_entry() to use dentry cache Before this conversion, in exfat_init_ext_entry(), to init the dentries in a dentry set, the sync times is equals the dentry number if 'dirsync' or 'sync' is enabled. That affects not only performance but also device life. After this conversion, only needs to be synchronized once if 'dirsync' or 'sync' is enabled. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 33 ++++++------------------ exfat_fs.h | 4 +-- namei.c | 73 ++++++++++++++++++------------------------------------ 3 files changed, 33 insertions(+), 77 deletions(-) diff --git a/dir.c b/dir.c index 6d314fc..422c2e8 100644 --- a/dir.c +++ b/dir.c @@ -539,46 +539,27 @@ static void exfat_free_benign_secondary_clusters(struct inode *inode, exfat_free_cluster(inode, &dir); } -int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, - int entry, int num_entries, struct exfat_uni_name *p_uniname) +void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, + struct exfat_uni_name *p_uniname) { - struct super_block *sb = inode->i_sb; int i; unsigned short *uniname = p_uniname->name; struct exfat_dentry *ep; - struct buffer_head *bh; - int sync = IS_DIRSYNC(inode); - - ep = exfat_get_dentry(sb, p_dir, entry, &bh); - if (!ep) - return -EIO; + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); - exfat_update_bh(bh, sync); - brelse(bh); - - ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh); - if (!ep) - return -EIO; + ep = exfat_get_dentry_cached(es, ES_IDX_STREAM); ep->dentry.stream.name_len = p_uniname->name_len; ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash); - exfat_update_bh(bh, sync); - brelse(bh); - - for (i = EXFAT_FIRST_CLUSTER; i < num_entries; i++) { - ep = exfat_get_dentry(sb, p_dir, entry + i, &bh); - if (!ep) - return -EIO; + for (i = ES_IDX_FIRST_FILENAME; i < num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); exfat_init_name_entry(ep, uniname); - exfat_update_bh(bh, sync); - brelse(bh); uniname += EXFAT_FILE_NAME_LEN; } - exfat_update_dir_chksum(inode, p_dir, entry); - return 0; + exfat_update_dir_chksum_with_entry_set(es); } void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, diff --git a/exfat_fs.h b/exfat_fs.h index f323c3c..2141f02 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -506,8 +506,8 @@ unsigned int exfat_get_entry_type(struct exfat_dentry *p_entry); void exfat_init_dir_entry(struct exfat_entry_set_cache *es, unsigned int type, unsigned int start_clu, unsigned long long size, struct timespec64 *ts); -int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, - int entry, int num_entries, struct exfat_uni_name *p_uniname); +void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, + struct exfat_uni_name *p_uniname); void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, int order); int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, diff --git a/namei.c b/namei.c index defae1e..28138fc 100644 --- a/namei.c +++ b/namei.c @@ -540,15 +540,12 @@ static int exfat_add_entry(struct inode *inode, const char *path, goto out; exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts); + exfat_init_ext_entry(&es, num_entries, &uniname); ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); if (ret) goto out; - ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname); - if (ret) - goto out; - info->dir = *p_dir; info->entry = dentry; info->flags = ALLOC_NO_FAT_CHAIN; @@ -1103,8 +1100,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, int ret, num_new_entries; struct exfat_dentry *epold, *epnew; struct super_block *sb = inode->i_sb; - struct buffer_head *new_bh; - struct exfat_entry_set_cache old_es; + struct exfat_entry_set_cache old_es, new_es; int sync = IS_DIRSYNC(inode); num_new_entries = exfat_calc_num_entries(p_uniname); @@ -1129,33 +1125,25 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, goto put_old_es; } - epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh); - if (!epnew) { - ret = -EIO; + ret = exfat_get_empty_dentry_set(&new_es, sb, p_dir, newentry, + num_new_entries); + if (ret) goto put_old_es; - } + epnew = exfat_get_dentry_cached(&new_es, ES_IDX_FILE); *epnew = *epold; if (exfat_get_entry_type(epnew) == TYPE_FILE) { epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); ei->attr |= EXFAT_ATTR_ARCHIVE; } - exfat_update_bh(new_bh, sync); - brelse(new_bh); epold = exfat_get_dentry_cached(&old_es, ES_IDX_STREAM); - epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh); - if (!epnew) { - ret = -EIO; - goto put_old_es; - } - + epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM); *epnew = *epold; - exfat_update_bh(new_bh, sync); - brelse(new_bh); - ret = exfat_init_ext_entry(inode, p_dir, newentry, - num_new_entries, p_uniname); + exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); + + ret = exfat_put_dentry_set(&new_es, sync); if (ret) goto put_old_es; @@ -1169,11 +1157,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, } exfat_remove_entries(inode, &old_es, ES_IDX_FIRST_FILENAME + 1); - - ret = exfat_init_ext_entry(inode, p_dir, oldentry, - num_new_entries, p_uniname); - if (ret) - goto put_old_es; + exfat_init_ext_entry(&old_es, num_new_entries, p_uniname); } return exfat_put_dentry_set(&old_es, sync); @@ -1189,8 +1173,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, int ret, newentry, num_new_entries; struct exfat_dentry *epmov, *epnew; struct super_block *sb = inode->i_sb; - struct buffer_head *new_bh; - struct exfat_entry_set_cache mov_es; + struct exfat_entry_set_cache mov_es, new_es; num_new_entries = exfat_calc_num_entries(p_uniname); if (num_new_entries < 0) @@ -1205,43 +1188,35 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, if (ret) return -EIO; - epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE); - epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh); - if (!epnew) { - ret = -EIO; + ret = exfat_get_empty_dentry_set(&new_es, sb, p_newdir, newentry, + num_new_entries); + if (ret) goto put_mov_es; - } + epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE); + epnew = exfat_get_dentry_cached(&new_es, ES_IDX_FILE); *epnew = *epmov; if (exfat_get_entry_type(epnew) == TYPE_FILE) { epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); ei->attr |= EXFAT_ATTR_ARCHIVE; } - exfat_update_bh(new_bh, IS_DIRSYNC(inode)); - brelse(new_bh); epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_STREAM); - epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh); - if (!epnew) { - ret = -EIO; - goto put_mov_es; - } - + epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM); *epnew = *epmov; - exfat_update_bh(new_bh, IS_DIRSYNC(inode)); - brelse(new_bh); - - ret = exfat_init_ext_entry(inode, p_newdir, newentry, num_new_entries, - p_uniname); - if (ret) - return ret; + exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); exfat_remove_entries(inode, &mov_es, ES_IDX_FILE); exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, p_newdir->flags); ei->entry = newentry; + + ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(inode)); + if (ret) + goto put_mov_es; + return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(inode)); put_mov_es: From 11581ee92869a8796b5656229061b38893e5086d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:37:53 +0900 Subject: [PATCH 112/141] exfat: convert exfat_find_empty_entry() to use dentry cache Before this conversion, each dentry traversed needs to be read from the storage device or page cache. There are at least 16 dentries in a sector. This will result in frequent page cache searches. After this conversion, if all directory entries in a sector are used, the sector only needs to be read once. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 126 +++++++++++++++++++------------------------------------- 1 file changed, 42 insertions(+), 84 deletions(-) diff --git a/namei.c b/namei.c index 28138fc..271c9cf 100644 --- a/namei.c +++ b/namei.c @@ -205,21 +205,16 @@ const struct dentry_operations exfat_utf8_dentry_ops = { .d_compare = exfat_utf8_d_cmp, }; -/* used only in search empty_slot() */ -#define CNT_UNUSED_NOHIT (-1) -#define CNT_UNUSED_HIT (-2) /* search EMPTY CONTINUOUS "num_entries" entries */ static int exfat_search_empty_slot(struct super_block *sb, struct exfat_hint_femp *hint_femp, struct exfat_chain *p_dir, - int num_entries) + int num_entries, struct exfat_entry_set_cache *es) { - int i, dentry, num_empty = 0; + int i, dentry, ret; int dentries_per_clu; - unsigned int type; struct exfat_chain clu; - struct exfat_dentry *ep; struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct buffer_head *bh; + int total_entries = EXFAT_CLU_TO_DEN(p_dir->size, sbi); dentries_per_clu = sbi->dentries_per_clu; @@ -232,7 +227,7 @@ static int exfat_search_empty_slot(struct super_block *sb, * Otherwise, and if "dentry + hint_famp->count" is also equal * to "p_dir->size * dentries_per_clu", it means ENOSPC. */ - if (dentry + hint_femp->count == p_dir->size * dentries_per_clu && + if (dentry + hint_femp->count == total_entries && num_entries > hint_femp->count) return -ENOSPC; @@ -243,69 +238,41 @@ static int exfat_search_empty_slot(struct super_block *sb, dentry = 0; } - while (clu.dir != EXFAT_EOF_CLUSTER) { + while (dentry + num_entries < total_entries && + clu.dir != EXFAT_EOF_CLUSTER) { i = dentry & (dentries_per_clu - 1); - for (; i < dentries_per_clu; i++, dentry++) { - ep = exfat_get_dentry(sb, &clu, i, &bh); - if (!ep) - return -EIO; - type = exfat_get_entry_type(ep); - brelse(bh); - - if (type == TYPE_UNUSED || type == TYPE_DELETED) { - num_empty++; - if (hint_femp->eidx == EXFAT_HINT_NONE) { - hint_femp->eidx = dentry; - hint_femp->count = CNT_UNUSED_NOHIT; - exfat_chain_set(&hint_femp->cur, - clu.dir, clu.size, clu.flags); - } - - if (type == TYPE_UNUSED && - hint_femp->count != CNT_UNUSED_HIT) - hint_femp->count = CNT_UNUSED_HIT; + ret = exfat_get_empty_dentry_set(es, sb, &clu, i, num_entries); + if (ret < 0) + return ret; + else if (ret == 0) + return dentry; + + dentry += ret; + i += ret; + + while (i >= dentries_per_clu) { + if (clu.flags == ALLOC_NO_FAT_CHAIN) { + if (--clu.size > 0) + clu.dir++; + else + clu.dir = EXFAT_EOF_CLUSTER; } else { - if (hint_femp->eidx != EXFAT_HINT_NONE && - hint_femp->count == CNT_UNUSED_HIT) { - /* unused empty group means - * an empty group which includes - * unused dentry - */ - exfat_fs_error(sb, - "found bogus dentry(%d) beyond unused empty group(%d) (start_clu : %u, cur_clu : %u)", - dentry, hint_femp->eidx, - p_dir->dir, clu.dir); + if (exfat_get_next_cluster(sb, &clu.dir)) return -EIO; - } - - num_empty = 0; - hint_femp->eidx = EXFAT_HINT_NONE; } - if (num_empty >= num_entries) { - /* found and invalidate hint_femp */ - hint_femp->eidx = EXFAT_HINT_NONE; - return (dentry - (num_entries - 1)); - } - } - - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - if (--clu.size > 0) - clu.dir++; - else - clu.dir = EXFAT_EOF_CLUSTER; - } else { - if (exfat_get_next_cluster(sb, &clu.dir)) - return -EIO; + i -= dentries_per_clu; } } - hint_femp->eidx = p_dir->size * dentries_per_clu - num_empty; - hint_femp->count = num_empty; - if (num_empty == 0) + hint_femp->eidx = dentry; + hint_femp->count = 0; + if (dentry == total_entries || clu.dir == EXFAT_EOF_CLUSTER) exfat_chain_set(&hint_femp->cur, EXFAT_EOF_CLUSTER, 0, clu.flags); + else + hint_femp->cur = clu; return -ENOSPC; } @@ -326,7 +293,8 @@ static int exfat_check_max_dentries(struct inode *inode) * if there isn't any empty slot, expand cluster chain. */ static int exfat_find_empty_entry(struct inode *inode, - struct exfat_chain *p_dir, int num_entries) + struct exfat_chain *p_dir, int num_entries, + struct exfat_entry_set_cache *es) { int dentry; unsigned int ret, last_clu; @@ -345,7 +313,7 @@ static int exfat_find_empty_entry(struct inode *inode, } while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir, - num_entries)) < 0) { + num_entries, es)) < 0) { if (dentry == -EIO) break; @@ -516,7 +484,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, } /* exfat_find_empty_entry must be called before alloc_cluster() */ - dentry = exfat_find_empty_entry(inode, p_dir, num_entries); + dentry = exfat_find_empty_entry(inode, p_dir, num_entries, &es); if (dentry < 0) { ret = dentry; /* -EIO or -ENOSPC */ goto out; @@ -524,8 +492,10 @@ static int exfat_add_entry(struct inode *inode, const char *path, if (type == TYPE_DIR && !sbi->options.zero_size_dir) { ret = exfat_alloc_new_dir(inode, &clu); - if (ret) + if (ret) { + exfat_put_dentry_set(&es, false); goto out; + } start_clu = clu.dir; clu_size = sbi->cluster_size; } @@ -534,11 +504,6 @@ static int exfat_add_entry(struct inode *inode, const char *path, /* fill the dos name directory entry information of the created file. * the first cluster is not determined yet. (0) */ - - ret = exfat_get_empty_dentry_set(&es, sb, p_dir, dentry, num_entries); - if (ret) - goto out; - exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts); exfat_init_ext_entry(&es, num_entries, &uniname); @@ -1118,18 +1083,13 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, if (old_es.num_entries < num_new_entries) { int newentry; - newentry = - exfat_find_empty_entry(inode, p_dir, num_new_entries); + newentry = exfat_find_empty_entry(inode, p_dir, num_new_entries, + &new_es); if (newentry < 0) { ret = newentry; /* -EIO or -ENOSPC */ goto put_old_es; } - ret = exfat_get_empty_dentry_set(&new_es, sb, p_dir, newentry, - num_new_entries); - if (ret) - goto put_old_es; - epnew = exfat_get_dentry_cached(&new_es, ES_IDX_FILE); *epnew = *epold; if (exfat_get_entry_type(epnew) == TYPE_FILE) { @@ -1179,19 +1139,17 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, if (num_new_entries < 0) return num_new_entries; - newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries); - if (newentry < 0) - return newentry; /* -EIO or -ENOSPC */ - ret = exfat_get_dentry_set(&mov_es, sb, p_olddir, oldentry, ES_ALL_ENTRIES); if (ret) return -EIO; - ret = exfat_get_empty_dentry_set(&new_es, sb, p_newdir, newentry, - num_new_entries); - if (ret) + newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries, + &new_es); + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ goto put_mov_es; + } epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE); epnew = exfat_get_dentry_cached(&new_es, ES_IDX_FILE); From f5f217a54a952728ce2b89fa911514b8927b17dd Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:39:08 +0900 Subject: [PATCH 113/141] exfat: remove unused functions exfat_count_ext_entries() is no longer called, remove it. exfat_update_dir_chksum() is no longer called, remove it and rename exfat_update_dir_chksum_with_entry_set() to it. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 60 ++---------------------------------------------------- exfat_fs.h | 6 +----- inode.c | 2 +- 3 files changed, 4 insertions(+), 64 deletions(-) diff --git a/dir.c b/dir.c index 422c2e8..9035e69 100644 --- a/dir.c +++ b/dir.c @@ -485,41 +485,6 @@ void exfat_init_dir_entry(struct exfat_entry_set_cache *es, exfat_init_stream_entry(ep, start_clu, size); } -int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, - int entry) -{ - struct super_block *sb = inode->i_sb; - int ret = 0; - int i, num_entries; - u16 chksum; - struct exfat_dentry *ep, *fep; - struct buffer_head *fbh, *bh; - - fep = exfat_get_dentry(sb, p_dir, entry, &fbh); - if (!fep) - return -EIO; - - num_entries = fep->dentry.file.num_ext + 1; - chksum = exfat_calc_chksum16(fep, DENTRY_SIZE, 0, CS_DIR_ENTRY); - - for (i = 1; i < num_entries; i++) { - ep = exfat_get_dentry(sb, p_dir, entry + i, &bh); - if (!ep) { - ret = -EIO; - goto release_fbh; - } - chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, - CS_DEFAULT); - brelse(bh); - } - - fep->dentry.file.checksum = cpu_to_le16(chksum); - exfat_update_bh(fbh, IS_DIRSYNC(inode)); -release_fbh: - brelse(fbh); - return ret; -} - static void exfat_free_benign_secondary_clusters(struct inode *inode, struct exfat_dentry *ep) { @@ -559,7 +524,7 @@ void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, uniname += EXFAT_FILE_NAME_LEN; } - exfat_update_dir_chksum_with_entry_set(es); + exfat_update_dir_chksum(es); } void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, @@ -581,7 +546,7 @@ void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, es->modified = true; } -void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) +void exfat_update_dir_chksum(struct exfat_entry_set_cache *es) { int chksum_type = CS_DIR_ENTRY, i; unsigned short chksum = 0; @@ -1248,27 +1213,6 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, return dentry - num_ext; } -int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir, - int entry, struct exfat_dentry *ep) -{ - int i, count = 0; - unsigned int type; - struct exfat_dentry *ext_ep; - struct buffer_head *bh; - - for (i = 0, entry++; i < ep->dentry.file.num_ext; i++, entry++) { - ext_ep = exfat_get_dentry(sb, p_dir, entry, &bh); - if (!ext_ep) - return -EIO; - - type = exfat_get_entry_type(ext_ep); - brelse(bh); - if (type & TYPE_CRITICAL_SEC || type & TYPE_BENIGN_SEC) - count++; - } - return count; -} - int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir) { int i, count = 0; diff --git a/exfat_fs.h b/exfat_fs.h index 2141f02..47d2291 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -444,8 +444,6 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc, unsigned int *content); int exfat_ent_set(struct super_block *sb, unsigned int loc, unsigned int content); -int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir, - int entry, struct exfat_dentry *p_entry); int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, unsigned int len); int exfat_zeroed_cluster(struct inode *dir, unsigned int clu); @@ -510,9 +508,7 @@ void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, struct exfat_uni_name *p_uniname); void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, int order); -int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, - int entry); -void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es); +void exfat_update_dir_chksum(struct exfat_entry_set_cache *es); int exfat_calc_num_entries(struct exfat_uni_name *p_uniname); int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, diff --git a/inode.c b/inode.c index e027620..4e7fbba 100644 --- a/inode.c +++ b/inode.c @@ -109,7 +109,7 @@ int __exfat_write_inode(struct inode *inode, int sync) ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; } - exfat_update_dir_chksum_with_entry_set(&es); + exfat_update_dir_chksum(&es); return exfat_put_dentry_set(&es, sync); } From 4de5c4e7983964a209de066ddacbb22c5292fc4c Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:45:29 +0900 Subject: [PATCH 114/141] exfat: do not sync parent dir if just update timestamp When sync or dir_sync is enabled, there is no need to sync the parent directory's inode if only for updating its timestamp. 1. If an unexpected power failure occurs, the timestamp of the parent directory is not updated to the storage, which has no impact on the user. 2. The number of writes will be greatly reduced, which can not only improve performance, but also prolong device life. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/namei.c b/namei.c index 271c9cf..c425c3b 100644 --- a/namei.c +++ b/namei.c @@ -553,6 +553,7 @@ static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, struct exfat_dir_entry info; loff_t i_pos; int err; + loff_t size = i_size_read(dir); mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_set_volume_dirty(sb); @@ -571,7 +572,7 @@ static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, #else dir->i_ctime = dir->i_mtime = current_time(dir); #endif - if (IS_DIRSYNC(dir)) + if (IS_DIRSYNC(dir) && size != i_size_read(dir)) exfat_sync_inode(dir); else mark_inode_dirty(dir); @@ -835,10 +836,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) #endif exfat_truncate_atime(&dir->i_atime); #endif - if (IS_DIRSYNC(dir)) - exfat_sync_inode(dir); - else - mark_inode_dirty(dir); + mark_inode_dirty(dir); clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) @@ -873,6 +871,7 @@ static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, struct exfat_chain cdir; loff_t i_pos; int err; + loff_t size = i_size_read(dir); mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_set_volume_dirty(sb); @@ -892,7 +891,7 @@ static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, #else dir->i_ctime = dir->i_mtime = current_time(dir); #endif - if (IS_DIRSYNC(dir)) + if (IS_DIRSYNC(dir) && size != i_size_read(dir)) exfat_sync_inode(dir); else mark_inode_dirty(dir); @@ -1331,6 +1330,7 @@ static int exfat_rename(struct user_namespace *mnt_userns, struct super_block *sb = old_dir->i_sb; loff_t i_pos; int err; + loff_t size = i_size_read(new_dir); /* * The VFS already checks for existence, so for local filesystems @@ -1361,7 +1361,7 @@ static int exfat_rename(struct user_namespace *mnt_userns, #else exfat_truncate_atime(&new_dir->i_atime); #endif - if (IS_DIRSYNC(new_dir)) + if (IS_DIRSYNC(new_dir) && size != i_size_read(new_dir)) exfat_sync_inode(new_dir); else mark_inode_dirty(new_dir); @@ -1385,10 +1385,7 @@ static int exfat_rename(struct user_namespace *mnt_userns, #if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 6, 0) old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); #endif - if (IS_DIRSYNC(old_dir)) - exfat_sync_inode(old_dir); - else - mark_inode_dirty(old_dir); + mark_inode_dirty(old_dir); if (new_inode) { exfat_unhash_inode(new_inode); From f497981eb379a44d258b274f498a91d4d15f451f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:47:07 +0900 Subject: [PATCH 115/141] exfat: remove duplicate update parent dir For renaming, the directory only needs to be updated once if it is in the same directory. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/namei.c b/namei.c index c425c3b..8647a57 100644 --- a/namei.c +++ b/namei.c @@ -1385,7 +1385,8 @@ static int exfat_rename(struct user_namespace *mnt_userns, #if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 6, 0) old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); #endif - mark_inode_dirty(old_dir); + if (new_dir != old_dir) + mark_inode_dirty(old_dir); if (new_inode) { exfat_unhash_inode(new_inode); From 15be380f969f60d8638c0dbbb3af3ab42256d25e Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:51:14 +0900 Subject: [PATCH 116/141] exfat: fix timing of synchronizing bitmap and inode Commit(f55c096f62f1 exfat: do not zero the extended part) changed the timing of synchronizing bitmap and inode in exfat_cont_expand(). The change caused xfstests generic/013 to fail if 'dirsync' or 'sync' is enabled. So this commit restores the timing. Fixes: f55c096f62f1 ("exfat: do not zero the extended part") Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- file.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/file.c b/file.c index 43faa0f..6e20a66 100644 --- a/file.c +++ b/file.c @@ -53,7 +53,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) clu.flags = ei->flags; ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters, - &clu, IS_DIRSYNC(inode)); + &clu, inode_needs_sync(inode)); if (ret) return ret; @@ -87,12 +87,11 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) ei->i_size_aligned = round_up(size, sb->s_blocksize); ei->i_size_ondisk = ei->i_size_aligned; inode->i_blocks = round_up(size, sbi->cluster_size) >> 9; + mark_inode_dirty(inode); - if (IS_DIRSYNC(inode)) + if (IS_SYNC(inode)) return write_inode_now(inode, 1); - mark_inode_dirty(inode); - return 0; free_clu: From 228f68933905dd27c7d5489d8e4300edb5760bcf Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Aug 2024 16:52:25 +0900 Subject: [PATCH 117/141] exfat: zero the reserved fields of file and stream extension dentries From exFAT specification, the reserved fields should initialize to zero and should not use for any purpose. If create a new dentry set in the UNUSED dentries, all fields had been zeroed when allocating cluster to parent directory. But if create a new dentry set in the DELETED dentries, the reserved fields in file and stream extension dentries may be non-zero. Because only the valid bit of the type field of the dentry is cleared in exfat_remove_entries(), if the type of dentry is different from the original(For example, a dentry that was originally a file name dentry, then set to deleted dentry, and then set as a file dentry), the reserved fields is non-zero. So this commit initializes the dentry to 0 before createing file dentry and stream extension dentry. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dir.c b/dir.c index 9035e69..68bfb02 100644 --- a/dir.c +++ b/dir.c @@ -427,6 +427,7 @@ static void exfat_set_entry_type(struct exfat_dentry *ep, unsigned int type) static void exfat_init_stream_entry(struct exfat_dentry *ep, unsigned int start_clu, unsigned long long size) { + memset(ep, 0, sizeof(*ep)); exfat_set_entry_type(ep, TYPE_STREAM); if (size == 0) ep->dentry.stream.flags = ALLOC_FAT_CHAIN; @@ -464,6 +465,7 @@ void exfat_init_dir_entry(struct exfat_entry_set_cache *es, struct exfat_dentry *ep; ep = exfat_get_dentry_cached(es, ES_IDX_FILE); + memset(ep, 0, sizeof(*ep)); exfat_set_entry_type(ep, type); exfat_set_entry_time(sbi, ts, &ep->dentry.file.create_tz, From 05b27be3f86568266374a47feb05d7cce50905e5 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Wed, 7 Aug 2024 16:57:06 +0900 Subject: [PATCH 118/141] exfat: Convert to new uid/gid option parsing helpers Convert to new uid/gid option parsing helpers Signed-off-by: Eric Sandeen Link: https://lore.kernel.org/r/dda575de-11a7-4139-8a25-07957d311ed3@redhat.com Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- super.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/super.c b/super.c index 7c319d9..ae9bfaf 100644 --- a/super.c +++ b/super.c @@ -234,8 +234,13 @@ static const struct constant_table exfat_param_enums[] = { }; static const struct fs_parameter_spec exfat_parameters[] = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) + fsparam_uid("uid", Opt_uid), + fsparam_gid("gid", Opt_gid), +#else fsparam_u32("uid", Opt_uid), fsparam_u32("gid", Opt_gid), +#endif fsparam_u32oct("umask", Opt_umask), fsparam_u32oct("dmask", Opt_dmask), fsparam_u32oct("fmask", Opt_fmask), @@ -271,10 +276,18 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) switch (opt) { case Opt_uid: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) + opts->fs_uid = result.uid; +#else opts->fs_uid = make_kuid(current_user_ns(), result.uint_32); +#endif break; case Opt_gid: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) + opts->fs_gid = result.gid; +#else opts->fs_gid = make_kgid(current_user_ns(), result.uint_32); +#endif break; case Opt_umask: opts->fs_fmask = result.uint_32; From cf95d93111b3a7ffeeeeebdc2b9a5c5a94f436cd Mon Sep 17 00:00:00 2001 From: Michael Jeanson Date: Wed, 7 Aug 2024 22:33:48 +0900 Subject: [PATCH 119/141] exfat: handle idmapped mounts Pass the idmapped mount information to the different helper functions. Adapt the uid/gid checks in exfat_setattr to use the vfsuid/vfsgid helpers. Based on the fat implementation in commit 4b7899368108 ("fat: handle idmapped mounts") by Christian Brauner. Signed-off-by: Michael Jeanson Signed-off-by: Namjae Jeon --- file.c | 34 +++++++++++++++++++++++++++++----- super.c | 2 +- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/file.c b/file.c index 6e20a66..650a83c 100644 --- a/file.c +++ b/file.c @@ -99,12 +99,23 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) return -EIO; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static bool exfat_allow_set_time(struct mnt_idmap *idmap, + struct exfat_sb_info *sbi, struct inode *inode) +#else static bool exfat_allow_set_time(struct exfat_sb_info *sbi, struct inode *inode) +#endif { mode_t allow_utime = sbi->options.allow_utime; - if (!uid_eq(current_fsuid(), inode->i_uid)) { - if (in_group_p(inode->i_gid)) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + if (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), + current_fsuid())) { + if (vfsgid_in_group_p(i_gid_into_vfsgid(idmap, inode))) +#else + if (!uid_eq(current_fsuid(), inode->i_uid)) { + if (in_group_p(inode->i_gid)) +#endif allow_utime >>= 3; if (allow_utime & MAY_WRITE) return true; @@ -301,7 +312,7 @@ int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path, #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) - generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); + generic_fillattr(idmap, request_mask, inode, stat); #else generic_fillattr(&nop_mnt_idmap, inode, stat); #endif @@ -340,13 +351,17 @@ int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, /* Check for setting the inode time. */ ia_valid = attr->ia_valid; if ((ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) && +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + exfat_allow_set_time(idmap, sbi, inode)) { +#else exfat_allow_set_time(sbi, inode)) { +#endif attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) - error = setattr_prepare(&nop_mnt_idmap, dentry, attr); + error = setattr_prepare(idmap, dentry, attr); #else error = setattr_prepare(&init_user_ns, dentry, attr); #endif @@ -354,10 +369,19 @@ int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, if (error) goto out; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + if (((attr->ia_valid & ATTR_UID) && + (!uid_eq(from_vfsuid(idmap, i_user_ns(inode), attr->ia_vfsuid), + sbi->options.fs_uid))) || + ((attr->ia_valid & ATTR_GID) && + (!gid_eq(from_vfsgid(idmap, i_user_ns(inode), attr->ia_vfsgid), + sbi->options.fs_gid))) || +#else 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)) || +#endif ((attr->ia_valid & ATTR_MODE) && (attr->ia_mode & ~(S_IFREG | S_IFLNK | S_IFDIR | 0777)))) { error = -EPERM; @@ -385,7 +409,7 @@ int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) - setattr_copy(&nop_mnt_idmap, inode, attr); + setattr_copy(idmap, inode, attr); exfat_truncate_inode_atime(inode); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) diff --git a/super.c b/super.c index ae9bfaf..efe9356 100644 --- a/super.c +++ b/super.c @@ -833,7 +833,7 @@ static struct file_system_type exfat_fs_type = { .init_fs_context = exfat_init_fs_context, .parameters = exfat_parameters, .kill_sb = exfat_kill_sb, - .fs_flags = FS_REQUIRES_DEV, + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, }; static void exfat_inode_init_once(void *foo) From 3071e02a83e0e61e0563dbb4642a017ca15f86a8 Mon Sep 17 00:00:00 2001 From: Sungjong Seo Date: Wed, 7 Aug 2024 17:19:42 +0900 Subject: [PATCH 120/141] exfat: fix potential deadlock on __exfat_get_dentry_set When accessing a file with more entries than ES_MAX_ENTRY_NUM, the bh-array is allocated in __exfat_get_entry_set. The problem is that the bh-array is allocated with GFP_KERNEL. It does not make sense. In the following cases, a deadlock for sbi->s_lock between the two processes may occur. CPU0 CPU1 ---- ---- kswapd balance_pgdat lock(fs_reclaim) exfat_iterate lock(&sbi->s_lock) exfat_readdir exfat_get_uniname_from_ext_entry exfat_get_dentry_set __exfat_get_dentry_set kmalloc_array ... lock(fs_reclaim) ... evict exfat_evict_inode lock(&sbi->s_lock) To fix this, let's allocate bh-array with GFP_NOFS. Fixes: a3ff29a95fde ("exfat: support dynamic allocate bh for exfat_entry_set_cache") Cc: stable@vger.kernel.org # v6.2+ Reported-by: syzbot+412a392a2cd4a65e71db@syzkaller.appspotmail.com Closes: https://lore.kernel.org/lkml/000000000000fef47e0618c0327f@google.com Signed-off-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dir.c b/dir.c index 68bfb02..c10ba2c 100644 --- a/dir.c +++ b/dir.c @@ -820,7 +820,7 @@ static int __exfat_get_dentry_set(struct exfat_entry_set_cache *es, num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); if (num_bh > ARRAY_SIZE(es->__bh)) { - es->bh = kmalloc_array(num_bh, sizeof(*es->bh), GFP_KERNEL); + es->bh = kmalloc_array(num_bh, sizeof(*es->bh), GFP_NOFS); if (!es->bh) { brelse(bh); return -ENOMEM; From 42361a682f4e9664eba86d0f029e160ea6f9df03 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 8 Aug 2024 08:45:03 +0900 Subject: [PATCH 121/141] github action: make the space Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index d6a8e80..97caa6d 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download the kernel run: | sudo apt-get update @@ -24,9 +24,9 @@ jobs: - name: Prerequisite for xfstests testing run: | sudo apt-get install linux-headers-$(uname -r) - sudo apt-get install autoconf libtool pkg-config libnl-3-dev libnl-genl-3-dev + sudo apt-get install autoconf libtool pkg-config sudo apt-get install xfslibs-dev uuid-dev libtool-bin xfsprogs libgdbm-dev gawk fio attr libattr1-dev libacl1-dev libaio-dev - git clone --branch=exfat-next https://github.com/exfat-utils/exfat-utils + git clone --branch=exfat-next https://github.com/exfatprogs/exfatprogs git clone https://github.com/namjaejeon/exfat-testsuites export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH export PATH=/usr/local/lib:$PATH @@ -51,7 +51,7 @@ jobs: make > /dev/null sudo make install > /dev/null sudo insmod exfat.ko - cd exfat-utils + cd exfatprogs ./autogen.sh > /dev/null ./configure > /dev/null make -j$((`nproc`+1)) > /dev/null @@ -84,6 +84,7 @@ jobs: run: | cd exfat-testsuites/ tar xzvf xfstests-exfat.tgz > /dev/null + rm -f xfstests-exfat.tgz cd xfstests-exfat make -j$((`nproc`+1)) > /dev/null truncate -s 100G test.img From 5ea13be886020a7763b9d8b9c8dd2e8f01a6487a Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 8 Aug 2024 09:31:02 +0900 Subject: [PATCH 122/141] github action: remove generic/130 Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 97caa6d..770598c 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -124,7 +124,6 @@ jobs: sudo ./check generic/124 sudo ./check generic/127 sudo ./check generic/129 - sudo ./check generic/130 sudo ./check generic/131 sudo ./check generic/132 sudo ./check generic/133 From f203d46b70237e1ed7b937d562a9ad1f57dab034 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 8 Aug 2024 10:01:09 +0900 Subject: [PATCH 123/141] exfat: drop ->i_size_ondisk ->i_size_ondisk is no longer used by exfat_write_begin() after commit(11a347fb6cef exfat: change to get file size from DataLength), drop it. Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- exfat_fs.h | 10 +++++----- file.c | 8 ++------ inode.c | 17 +++++------------ namei.c | 1 - super.c | 1 - 5 files changed, 12 insertions(+), 25 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 47d2291..a1e8849 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -322,11 +322,6 @@ struct exfat_inode_info { /* for avoiding the race between alloc and free */ unsigned int cache_valid_id; - /* - * NOTE: i_size_ondisk is 64bits, so must hold ->inode_lock to access. - * physically allocated size. - */ - loff_t i_size_ondisk; /* block-aligned i_size (used in cont_write_begin) */ loff_t i_size_aligned; /* on-disk position of directory entry or 0 */ @@ -430,6 +425,11 @@ static inline bool is_valid_cluster(struct exfat_sb_info *sbi, return clus >= EXFAT_FIRST_CLUSTER && clus < sbi->num_clusters; } +static inline loff_t exfat_ondisk_size(const struct inode *inode) +{ + return ((loff_t)inode->i_blocks) << 9; +} + /* super.c */ int exfat_set_volume_dirty(struct super_block *sb); int exfat_clear_volume_dirty(struct super_block *sb); diff --git a/file.c b/file.c index 650a83c..135e858 100644 --- a/file.c +++ b/file.c @@ -31,7 +31,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) if (ret) return ret; - num_clusters = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi); + num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi); new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi); if (new_num_clusters == num_clusters) @@ -85,7 +85,6 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) i_size_write(inode, size); ei->i_size_aligned = round_up(size, sb->s_blocksize); - ei->i_size_ondisk = ei->i_size_aligned; inode->i_blocks = round_up(size, sbi->cluster_size) >> 9; mark_inode_dirty(inode); @@ -178,7 +177,7 @@ int __exfat_truncate(struct inode *inode) exfat_set_volume_dirty(sb); num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi); - num_clusters_phys = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi); + num_clusters_phys = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi); exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags); @@ -289,9 +288,6 @@ void exfat_truncate(struct inode *inode) aligned_size++; } - if (ei->i_size_ondisk > i_size_read(inode)) - ei->i_size_ondisk = aligned_size; - if (ei->i_size_aligned > i_size_read(inode)) ei->i_size_aligned = aligned_size; mutex_unlock(&sbi->s_lock); diff --git a/inode.c b/inode.c index 4e7fbba..0182879 100644 --- a/inode.c +++ b/inode.c @@ -145,11 +145,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); unsigned int local_clu_offset = clu_offset; - unsigned int num_to_be_allocated = 0, num_clusters = 0; + unsigned int num_to_be_allocated = 0, num_clusters; - if (ei->i_size_ondisk > 0) - num_clusters = - EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi); + num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi); if (clu_offset >= num_clusters) num_to_be_allocated = clu_offset - num_clusters + 1; @@ -283,10 +281,10 @@ static int exfat_map_new_buffer(struct exfat_inode_info *ei, set_buffer_new(bh); /* - * Adjust i_size_aligned if i_size_ondisk is bigger than it. + * Adjust i_size_aligned if ondisk_size is bigger than it. */ - if (ei->i_size_ondisk > ei->i_size_aligned) - ei->i_size_aligned = ei->i_size_ondisk; + if (exfat_ondisk_size(&ei->vfs_inode) > ei->i_size_aligned) + ei->i_size_aligned = exfat_ondisk_size(&ei->vfs_inode); return 0; } @@ -349,10 +347,6 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, max_blocks = min(mapped_blocks, max_blocks); pos = EXFAT_BLK_TO_B((iblock + 1), sb); - if ((create && iblock >= last_block) || buffer_delay(bh_result)) { - if (ei->i_size_ondisk < pos) - ei->i_size_ondisk = pos; - } map_bh(bh_result, sb, phys); if (buffer_delay(bh_result)) @@ -783,7 +777,6 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) } ei->i_size_aligned = size; - ei->i_size_ondisk = size; exfat_save_attr(inode, info->attr); diff --git a/namei.c b/namei.c index 8647a57..093ff03 100644 --- a/namei.c +++ b/namei.c @@ -373,7 +373,6 @@ static int exfat_find_empty_entry(struct inode *inode, /* directory inode should be updated in here */ i_size_write(inode, size); - ei->i_size_ondisk += sbi->cluster_size; ei->i_size_aligned += sbi->cluster_size; ei->valid_size += sbi->cluster_size; ei->flags = p_dir->flags; diff --git a/super.c b/super.c index efe9356..d7eb0fb 100644 --- a/super.c +++ b/super.c @@ -394,7 +394,6 @@ static int exfat_read_root(struct inode *inode) inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; ei->i_size_aligned = i_size_read(inode); - ei->i_size_ondisk = i_size_read(inode); exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) From dc7c0b4568f65efca0d098cbd5623652654824ef Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 8 Aug 2024 10:11:33 +0900 Subject: [PATCH 124/141] exfat: do not fallback to buffered write After commit(11a347fb6cef exfat: change to get file size from DataLength), the remaining area or hole had been filled with zeros before calling exfat_direct_IO(), so there is no need to fallback to buffered write, and ->i_size_aligned is no longer needed, drop it. Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 -- file.c | 11 -------- inode.c | 74 +++++++++++------------------------------------------- namei.c | 1 - super.c | 1 - 5 files changed, 15 insertions(+), 74 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index a1e8849..efd8498 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -322,8 +322,6 @@ struct exfat_inode_info { /* for avoiding the race between alloc and free */ unsigned int cache_valid_id; - /* block-aligned i_size (used in cont_write_begin) */ - loff_t i_size_aligned; /* on-disk position of directory entry or 0 */ loff_t i_pos; loff_t valid_size; diff --git a/file.c b/file.c index 135e858..ba319a9 100644 --- a/file.c +++ b/file.c @@ -84,7 +84,6 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) /* Expanded range not zeroed, do not update valid_size */ i_size_write(inode, size); - ei->i_size_aligned = round_up(size, sb->s_blocksize); inode->i_blocks = round_up(size, sbi->cluster_size) >> 9; mark_inode_dirty(inode); @@ -263,8 +262,6 @@ void exfat_truncate(struct inode *inode) struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); - unsigned int blocksize = i_blocksize(inode); - loff_t aligned_size; int err; mutex_lock(&sbi->s_lock); @@ -282,14 +279,6 @@ void exfat_truncate(struct inode *inode) inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; write_size: - aligned_size = i_size_read(inode); - if (aligned_size & (blocksize - 1)) { - aligned_size |= (blocksize - 1); - aligned_size++; - } - - if (ei->i_size_aligned > i_size_read(inode)) - ei->i_size_aligned = aligned_size; mutex_unlock(&sbi->s_lock); } diff --git a/inode.c b/inode.c index 0182879..b4c50c0 100644 --- a/inode.c +++ b/inode.c @@ -273,21 +273,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, return 0; } -static int exfat_map_new_buffer(struct exfat_inode_info *ei, - struct buffer_head *bh, loff_t pos) -{ - if (buffer_delay(bh) && pos > ei->i_size_aligned) - return -EIO; - set_buffer_new(bh); - - /* - * Adjust i_size_aligned if ondisk_size is bigger than it. - */ - if (exfat_ondisk_size(&ei->vfs_inode) > ei->i_size_aligned) - ei->i_size_aligned = exfat_ondisk_size(&ei->vfs_inode); - return 0; -} - static int exfat_bh_read(struct buffer_head *bh) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) @@ -318,7 +303,6 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, sector_t last_block; sector_t phys = 0; sector_t valid_blks; - loff_t pos; mutex_lock(&sbi->s_lock); last_block = EXFAT_B_TO_BLK_ROUND_UP(i_size_read(inode), sb); @@ -346,8 +330,6 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, mapped_blocks = sbi->sect_per_clus - sec_offset; max_blocks = min(mapped_blocks, max_blocks); - pos = EXFAT_BLK_TO_B((iblock + 1), sb); - map_bh(bh_result, sb, phys); if (buffer_delay(bh_result)) clear_buffer_delay(bh_result); @@ -368,13 +350,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, } /* The area has not been written, map and mark as new. */ - err = exfat_map_new_buffer(ei, bh_result, pos); - if (err) { - exfat_fs_error(sb, - "requested for bmap out of range(pos : (%llu) > i_size_aligned(%llu)\n", - pos, ei->i_size_aligned); - goto unlock_ret; - } + set_buffer_new(bh_result); ei->valid_size = EXFAT_BLK_TO_B(iblock + max_blocks, sb); mark_inode_dirty(inode); @@ -397,7 +373,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, * The block has been partially written, * zero the unwritten part and map the block. */ - loff_t size, off; + loff_t size, off, pos; max_blocks = 1; @@ -413,7 +389,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, goto done; #endif - pos -= sb->s_blocksize; + pos = EXFAT_BLK_TO_B(iblock, sb); size = ei->valid_size - pos; off = pos & (PAGE_SIZE - 1); @@ -542,14 +518,6 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, int err; err = generic_write_end(file, mapping, pos, len, copied, pagep, fsdata); - - if (ei->i_size_aligned < i_size_read(inode)) { - exfat_fs_error(inode->i_sb, - "invalid size(size(%llu) > aligned(%llu)\n", - i_size_read(inode), ei->i_size_aligned); - return -EIO; - } - if (err < len) exfat_write_failed(mapping, pos+len); @@ -586,20 +554,6 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) int rw = iov_iter_rw(iter); ssize_t ret; - if (rw == WRITE) { - /* - * FIXME: blockdev_direct_IO() doesn't use ->write_begin(), - * so we need to update the ->i_size_aligned to block boundary. - * - * But we must fill the remaining area or hole by nul for - * updating ->i_size_aligned - * - * Return 0, and fallback to normal buffered write. - */ - if (EXFAT_I(inode)->i_size_aligned < size) - return 0; - } - /* * Need to use the DIO_LOCKING for avoiding the race * condition of exfat_get_block() and ->truncate(). @@ -613,8 +567,18 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) } else size = pos + ret; - /* zero the unwritten part in the partially written block */ - if (rw == READ && pos < ei->valid_size && ei->valid_size < size) { + if (rw == WRITE) { + /* + * If the block had been partially written before this write, + * ->valid_size will not be updated in exfat_get_block(), + * update it here. + */ + if (ei->valid_size < size) { + ei->valid_size = size; + mark_inode_dirty(inode); + } + } else if (pos < ei->valid_size && ei->valid_size < size) { + /* zero the unwritten part in the partially written block */ iov_iter_revert(iter, size - ei->valid_size); iov_iter_zero(size - ei->valid_size, iter); } @@ -770,14 +734,6 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) i_size_write(inode, size); - /* ondisk and aligned size should be aligned with block size */ - if (size & (inode->i_sb->s_blocksize - 1)) { - size |= (inode->i_sb->s_blocksize - 1); - size++; - } - - ei->i_size_aligned = size; - exfat_save_attr(inode, info->attr); inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; diff --git a/namei.c b/namei.c index 093ff03..86cc2af 100644 --- a/namei.c +++ b/namei.c @@ -373,7 +373,6 @@ static int exfat_find_empty_entry(struct inode *inode, /* directory inode should be updated in here */ i_size_write(inode, size); - ei->i_size_aligned += sbi->cluster_size; ei->valid_size += sbi->cluster_size; ei->flags = p_dir->flags; inode->i_blocks += sbi->cluster_size >> 9; diff --git a/super.c b/super.c index d7eb0fb..9f8f4db 100644 --- a/super.c +++ b/super.c @@ -393,7 +393,6 @@ static int exfat_read_root(struct inode *inode) inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; - ei->i_size_aligned = i_size_read(inode); exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) From fcb977094613061f564fc2666d0d76de2dfe752f Mon Sep 17 00:00:00 2001 From: Dongliang Cui Date: Wed, 27 Nov 2024 15:18:34 +0900 Subject: [PATCH 125/141] 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 Signed-off-by: Zhiguo Niu Signed-off-by: Namjae Jeon --- exfat_fs.h | 16 ++++++++++++++++ file.c | 21 +++++++++++++++++++++ inode.c | 6 ++++++ namei.c | 15 +++++++++++++++ super.c | 39 +++++++++++++++++++++++++++++++++++++++ uapi_exfat.h | 25 +++++++++++++++++++++++++ 6 files changed, 122 insertions(+) create mode 100644 uapi_exfat.h diff --git a/exfat_fs.h b/exfat_fs.h index efd8498..878856e 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -11,6 +11,11 @@ #include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#include +#else +#include "uapi_exfat.h" +#endif #ifndef SECTOR_SIZE #define SECTOR_SIZE 512 @@ -161,6 +166,9 @@ enum { #define DIR_CACHE_SIZE \ (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) +/* Superblock flags */ +#define EXFAT_FLAGS_SHUTDOWN 1 + struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ @@ -280,6 +288,8 @@ struct exfat_sb_info { unsigned int clu_srch_ptr; /* cluster search pointer */ unsigned int used_clusters; /* number of used clusters */ + unsigned long s_exfat_flags; /* Exfat superblock flags */ + struct mutex s_lock; /* superblock lock */ struct mutex bitmap_lock; /* bitmap lock */ struct exfat_mount_options options; @@ -344,6 +354,11 @@ static inline struct exfat_inode_info *EXFAT_I(struct inode *inode) return container_of(inode, struct exfat_inode_info, vfs_inode); } +static inline int exfat_forced_shutdown(struct super_block *sb) +{ + return test_bit(EXFAT_FLAGS_SHUTDOWN, &EXFAT_SB(sb)->s_exfat_flags); +} + /* * If ->i_mode can't hold 0222 (i.e. ATTR_RO), we use ->i_attrs to * save ATTR_RO instead of ->i_mode. @@ -481,6 +496,7 @@ int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); long exfat_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); +int exfat_force_shutdown(struct super_block *sb, u32 flags); /* namei.c */ diff --git a/file.c b/file.c index ba319a9..e9dbeb9 100644 --- a/file.c +++ b/file.c @@ -325,6 +325,9 @@ int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, unsigned int ia_valid; int error; + if (unlikely(exfat_forced_shutdown(inode->i_sb))) + return -EIO; + if ((attr->ia_valid & ATTR_SIZE) && attr->ia_size > i_size_read(inode)) { error = exfat_cont_expand(inode, attr->ia_size); @@ -564,6 +567,19 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) return 0; } +static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg) +{ + u32 flags; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (get_user(flags, (__u32 __user *)arg)) + return -EFAULT; + + return exfat_force_shutdown(sb, flags); +} + long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -574,6 +590,8 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return exfat_ioctl_get_attributes(inode, user_attr); case FAT_IOCTL_SET_ATTRIBUTES: return exfat_ioctl_set_attributes(filp, user_attr); + case EXFAT_IOC_SHUTDOWN: + return exfat_ioctl_shutdown(inode->i_sb, arg); case FITRIM: return exfat_ioctl_fitrim(inode, arg); default: @@ -594,6 +612,9 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) struct inode *inode = filp->f_mapping->host; int err; + if (unlikely(exfat_forced_shutdown(inode->i_sb))) + return -EIO; + err = __generic_file_fsync(filp, start, end, datasync); if (err) return err; diff --git a/inode.c b/inode.c index b4c50c0..26ceaca 100644 --- a/inode.c +++ b/inode.c @@ -117,6 +117,9 @@ int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) { int ret; + if (unlikely(exfat_forced_shutdown(inode->i_sb))) + return -EIO; + mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); ret = __exfat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL); mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); @@ -462,6 +465,9 @@ static int exfat_writepage(struct page *page, struct writeback_control *wbc) static int exfat_writepages(struct address_space *mapping, struct writeback_control *wbc) { + if (unlikely(exfat_forced_shutdown(mapping->host->i_sb))) + return -EIO; + return mpage_writepages(mapping, wbc, exfat_get_block); } diff --git a/namei.c b/namei.c index 86cc2af..9762e81 100644 --- a/namei.c +++ b/namei.c @@ -553,6 +553,9 @@ static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, int err; loff_t size = i_size_read(dir); + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; + mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_set_volume_dirty(sb); err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE, @@ -794,6 +797,9 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) struct exfat_entry_set_cache es; int entry, err = 0; + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; + mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_chain_dup(&cdir, &ei->dir); entry = ei->entry; @@ -871,6 +877,9 @@ static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, int err; loff_t size = i_size_read(dir); + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; + mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_set_volume_dirty(sb); err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR, @@ -980,6 +989,9 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) struct exfat_entry_set_cache es; int entry, err; + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; + mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); exfat_chain_dup(&cdir, &ei->dir); @@ -1065,6 +1077,9 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, struct exfat_entry_set_cache old_es, new_es; int sync = IS_DIRSYNC(inode); + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; + num_new_entries = exfat_calc_num_entries(p_uniname); if (num_new_entries < 0) return num_new_entries; diff --git a/super.c b/super.c index 9f8f4db..560f287 100644 --- a/super.c +++ b/super.c @@ -50,6 +50,9 @@ static int exfat_sync_fs(struct super_block *sb, int wait) struct exfat_sb_info *sbi = EXFAT_SB(sb); int err = 0; + if (unlikely(exfat_forced_shutdown(sb))) + return 0; + if (!wait) return 0; @@ -172,6 +175,41 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) return 0; } +int exfat_force_shutdown(struct super_block *sb, u32 flags) +{ + int ret; + struct exfat_sb_info *sbi = sb->s_fs_info; + struct exfat_mount_options *opts = &sbi->options; + + if (exfat_forced_shutdown(sb)) + return 0; + + switch (flags) { + case EXFAT_GOING_DOWN_DEFAULT: + case EXFAT_GOING_DOWN_FULLSYNC: + ret = bdev_freeze(sb->s_bdev); + if (ret) + return ret; + bdev_thaw(sb->s_bdev); + set_bit(EXFAT_FLAGS_SHUTDOWN, &sbi->s_exfat_flags); + break; + case EXFAT_GOING_DOWN_NOSYNC: + set_bit(EXFAT_FLAGS_SHUTDOWN, &sbi->s_exfat_flags); + break; + default: + return -EINVAL; + } + + if (opts->discard) + opts->discard = 0; + return 0; +} + +static void exfat_shutdown(struct super_block *sb) +{ + exfat_force_shutdown(sb, EXFAT_GOING_DOWN_NOSYNC); +} + static struct inode *exfat_alloc_inode(struct super_block *sb) { struct exfat_inode_info *ei; @@ -202,6 +240,7 @@ static const struct super_operations exfat_sops = { .sync_fs = exfat_sync_fs, .statfs = exfat_statfs, .show_options = exfat_show_options, + .shutdown = exfat_shutdown, }; enum { diff --git a/uapi_exfat.h b/uapi_exfat.h new file mode 100644 index 0000000..46d95b1 --- /dev/null +++ b/uapi_exfat.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2024 Unisoc Technologies Co., Ltd. + */ + +#ifndef _UAPI_LINUX_EXFAT_H +#define _UAPI_LINUX_EXFAT_H +#include +#include + +/* + * exfat-specific ioctl commands + */ + +#define EXFAT_IOC_SHUTDOWN _IOR('X', 125, __u32) + +/* + * Flags used by EXFAT_IOC_SHUTDOWN + */ + +#define EXFAT_GOING_DOWN_DEFAULT 0x0 /* default with full sync */ +#define EXFAT_GOING_DOWN_FULLSYNC 0x1 /* going down with full sync*/ +#define EXFAT_GOING_DOWN_NOSYNC 0x2 /* going down */ + +#endif /* _UAPI_LINUX_EXFAT_H */ From 4a92b803151f621bc5aab974b5c71fb3c306a3ef Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 14:17:54 +0900 Subject: [PATCH 126/141] exfat: fix memory leak in exfat_load_bitmap() If the first directory entry in the root directory is not a bitmap directory entry, 'bh' will not be released and reassigned, which will cause a memory leak. Fixes: 1e49a94cf707 ("exfat: add bitmap operations") Cc: stable@vger.kernel.org Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- balloc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/balloc.c b/balloc.c index 439c23c..79ea787 100644 --- a/balloc.c +++ b/balloc.c @@ -94,11 +94,8 @@ int exfat_load_bitmap(struct super_block *sb) return -EIO; type = exfat_get_entry_type(ep); - if (type == TYPE_UNUSED) - break; - if (type != TYPE_BITMAP) - continue; - if (ep->dentry.bitmap.flags == 0x0) { + if (type == TYPE_BITMAP && + ep->dentry.bitmap.flags == 0x0) { int err; err = exfat_allocate_bitmap(sb, ep); @@ -106,6 +103,9 @@ int exfat_load_bitmap(struct super_block *sb) return err; } brelse(bh); + + if (type == TYPE_UNUSED) + return -EINVAL; } if (exfat_get_next_cluster(sb, &clu.dir)) From 0aaed85d40a715e49b3a8bec26d4438211930fb9 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Wed, 27 Nov 2024 14:40:32 +0900 Subject: [PATCH 127/141] fs: Convert aops->write_end to take a folio Most callers have a folio, and most implementations operate on a folio, so remove the conversion from folio->page->folio to fit through this interface. Reviewed-by: Josef Bacik Signed-off-by: Matthew Wilcox (Oracle) Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 4 ++++ inode.c | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/file.c b/file.c index e9dbeb9..bcf573a 100644 --- a/file.c +++ b/file.c @@ -652,7 +652,11 @@ static int exfat_file_zeroed_range(struct file *file, loff_t start, loff_t end) zero_user_segment(page, zerofrom, zerofrom + len); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + err = ops->write_end(file, mapping, start, len, len, page_folio(page), NULL); +#else err = ops->write_end(file, mapping, start, len, len, page, NULL); +#endif if (err < 0) goto out; start += len; diff --git a/inode.c b/inode.c index 26ceaca..c44613c 100644 --- a/inode.c +++ b/inode.c @@ -517,13 +517,21 @@ static int exfat_write_begin(struct file *file, struct address_space *mapping, static int exfat_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int copied, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + struct folio *folio, void *fsdata) +#else struct page *pagep, void *fsdata) +#endif { struct inode *inode = mapping->host; struct exfat_inode_info *ei = EXFAT_I(inode); int err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + err = generic_write_end(file, mapping, pos, len, copied, folio, fsdata); +#else err = generic_write_end(file, mapping, pos, len, copied, pagep, fsdata); +#endif if (err < len) exfat_write_failed(mapping, pos+len); From ce761de945c4ee7a10770b1691ebf4da6ea369e9 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Wed, 27 Nov 2024 14:44:20 +0900 Subject: [PATCH 128/141] fs: Convert aops->write_begin to take a folio Convert all callers from working on a page to working on one page of a folio (support for working on an entire folio can come later). Removes a lot of folio->page->folio conversions. Reviewed-by: Josef Bacik Signed-off-by: Matthew Wilcox (Oracle) Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 14 +++++++++++++- inode.c | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/file.c b/file.c index bcf573a..be80032 100644 --- a/file.c +++ b/file.c @@ -635,7 +635,11 @@ static int exfat_file_zeroed_range(struct file *file, loff_t start, loff_t end) while (start < end) { u32 zerofrom, len; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + struct folio *folio; +#else struct page *page = NULL; +#endif zerofrom = start & (PAGE_SIZE - 1); len = PAGE_SIZE - zerofrom; @@ -643,17 +647,25 @@ static int exfat_file_zeroed_range(struct file *file, loff_t start, loff_t end) len = end - start; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + err = ops->write_begin(file, mapping, start, len, &folio, NULL); +#else err = ops->write_begin(file, mapping, start, len, &page, NULL); +#endif #else err = ops->write_begin(file, mapping, start, len, 0, &page, NULL); #endif if (err) goto out; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + folio_zero_range(folio, offset_in_folio(folio, start), len); +#else zero_user_segment(page, zerofrom, zerofrom + len); +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) - err = ops->write_end(file, mapping, start, len, len, page_folio(page), NULL); + err = ops->write_end(file, mapping, start, len, len, folio, NULL); #else err = ops->write_end(file, mapping, start, len, len, page, NULL); #endif diff --git a/inode.c b/inode.c index c44613c..fedf0c9 100644 --- a/inode.c +++ b/inode.c @@ -493,7 +493,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) static int exfat_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + struct folio **foliop, void **fsdata) +#else struct page **pagep, void **fsdata) +#endif #else static int exfat_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, @@ -502,12 +506,16 @@ static int exfat_write_begin(struct file *file, struct address_space *mapping, { int ret; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + ret = block_write_begin(mapping, pos, len, foliop, exfat_get_block); +#else *pagep = NULL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) ret = block_write_begin(mapping, pos, len, pagep, exfat_get_block); #else ret = block_write_begin(mapping, pos, len, flags, pagep, exfat_get_block); +#endif #endif if (ret < 0) exfat_write_failed(mapping, pos+len); From b07a69d1f7de1d6cec1f824a81b5c6e774dae770 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 14:52:35 +0900 Subject: [PATCH 129/141] exfat: move extend valid_size into ->page_mkwrite() It is not a good way to extend valid_size to the end of the mmap area by writing zeros in mmap. Because after calling mmap, no data may be written, or only a small amount of data may be written to the head of the mmap area. This commit moves extending valid_size to exfat_page_mkwrite(). In exfat_page_mkwrite() only extend valid_size to the starting position of new data writing, which reduces unnecessary writing of zeros. If the block is not mapped and is marked as new after being mapped for writing, block_write_begin() will zero the page cache corresponding to the block, so there is no need to call zero_user_segment() in exfat_file_zeroed_range(). And after moving extending valid_size to exfat_page_mkwrite(), the data written by mmap will be copied to the page cache but the page cache may be not mapped to the disk. Calling zero_user_segment() will cause the data written by mmap to be cleared. So this commit removes calling zero_user_segment() from exfat_file_zeroed_range() and renames exfat_file_zeroed_range() to exfat_extend_valid_size(). Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- file.c | 80 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/file.c b/file.c index be80032..09093ac 100644 --- a/file.c +++ b/file.c @@ -626,52 +626,48 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) return blkdev_issue_flush(inode->i_sb->s_bdev); } -static int exfat_file_zeroed_range(struct file *file, loff_t start, loff_t end) +static int exfat_extend_valid_size(struct file *file, loff_t new_valid_size) { int err; + loff_t pos; struct inode *inode = file_inode(file); + struct exfat_inode_info *ei = EXFAT_I(inode); struct address_space *mapping = inode->i_mapping; const struct address_space_operations *ops = mapping->a_ops; - while (start < end) { - u32 zerofrom, len; + pos = ei->valid_size; + while (pos < new_valid_size) { + u32 len; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) struct folio *folio; #else struct page *page = NULL; #endif - zerofrom = start & (PAGE_SIZE - 1); - len = PAGE_SIZE - zerofrom; - if (start + len > end) - len = end - start; + len = PAGE_SIZE - (pos & (PAGE_SIZE - 1)); + if (pos + len > new_valid_size) + len = new_valid_size - pos; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) - err = ops->write_begin(file, mapping, start, len, &folio, NULL); + err = ops->write_begin(file, mapping, pos, len, &folio, NULL); #else - err = ops->write_begin(file, mapping, start, len, &page, NULL); + err = ops->write_begin(file, mapping, pos, len, &page, NULL); #endif #else - err = ops->write_begin(file, mapping, start, len, 0, &page, NULL); + err = ops->write_begin(file, mapping, pos, len, 0, &page, NULL); #endif if (err) goto out; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) - folio_zero_range(folio, offset_in_folio(folio, start), len); -#else - zero_user_segment(page, zerofrom, zerofrom + len); -#endif - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) - err = ops->write_end(file, mapping, start, len, len, folio, NULL); + err = ops->write_end(file, mapping, pos, len, len, folio, NULL); #else - err = ops->write_end(file, mapping, start, len, len, page, NULL); + err = ops->write_end(file, mapping, pos, len, len, page, NULL); #endif if (err < 0) goto out; - start += len; + pos += len; balance_dirty_pages_ratelimited(mapping); cond_resched(); @@ -709,7 +705,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter) goto unlock; if (pos > valid_size) { - ret = exfat_file_zeroed_range(file, valid_size, pos); + ret = exfat_extend_valid_size(file, pos); if (ret < 0 && ret != -ENOSPC) { exfat_err(inode->i_sb, "write: fail to zero from %llu to %llu(%zd)", @@ -743,26 +739,46 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter) return ret; } -static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma) +static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) { - int ret; + int err; + struct vm_area_struct *vma = vmf->vma; + struct file *file = vma->vm_file; struct inode *inode = file_inode(file); struct exfat_inode_info *ei = EXFAT_I(inode); - loff_t start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT); - loff_t end = min_t(loff_t, i_size_read(inode), + loff_t start, end; + + if (!inode_trylock(inode)) + return VM_FAULT_RETRY; + + start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT); + end = min_t(loff_t, i_size_read(inode), start + vma->vm_end - vma->vm_start); - if ((vma->vm_flags & VM_WRITE) && ei->valid_size < end) { - ret = exfat_file_zeroed_range(file, ei->valid_size, end); - if (ret < 0) { - exfat_err(inode->i_sb, - "mmap: fail to zero from %llu to %llu(%d)", - start, end, ret); - return ret; + if (ei->valid_size < end) { + err = exfat_extend_valid_size(file, end); + if (err < 0) { + inode_unlock(inode); + return vmf_fs_error(err); } } - return generic_file_mmap(file, vma); + inode_unlock(inode); + + return filemap_page_mkwrite(vmf); +} + +static const struct vm_operations_struct exfat_file_vm_ops = { + .fault = filemap_fault, + .map_pages = filemap_map_pages, + .page_mkwrite = exfat_page_mkwrite, +}; + +static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + file_accessed(file); + vma->vm_ops = &exfat_file_vm_ops; + return 0; } const struct file_operations exfat_file_operations = { From 09a72855bd583ef785e5c815688555d3a5bac0fe Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 27 Nov 2024 14:54:14 +0900 Subject: [PATCH 130/141] exfat: resolve memory leak from exfat_create_upcase_table() If exfat_load_upcase_table reaches end and returns -EINVAL, allocated memory doesn't get freed and while exfat_load_default_upcase_table allocates more memory, leading to a memory leak. Here's link to syzkaller crash report illustrating this issue: https://syzkaller.appspot.com/text?tag=CrashReport&x=1406c201980000 Reported-by: syzbot+e1c69cadec0f1a078e3d@syzkaller.appspotmail.com Fixes: a13d1a4de3b0 ("exfat: move freeing sbi, upcase table and dropping nls into rcu-delayed helper") Cc: stable@vger.kernel.org Signed-off-by: Daniel Yang Signed-off-by: Namjae Jeon --- nls.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nls.c b/nls.c index 335a6d2..130edf8 100644 --- a/nls.c +++ b/nls.c @@ -780,8 +780,11 @@ int exfat_create_upcase_table(struct super_block *sb) le32_to_cpu(ep->dentry.upcase.checksum)); brelse(bh); - if (ret && ret != -EIO) + if (ret && ret != -EIO) { + /* free memory from exfat_load_upcase_table call */ + exfat_free_upcase_table(sbi); goto load_default; + } /* load successfully */ return ret; From f33b7583e4e5f578ae7e1ecdfa03e71c72786050 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 27 Nov 2024 14:57:15 +0900 Subject: [PATCH 131/141] move asm/unaligned.h to linux/unaligned.h asm/unaligned.h is always an include of asm-generic/unaligned.h; might as well move that thing to linux/unaligned.h and include that - there's nothing arch-specific in that header. auto-generated by the following: for i in cache.c fatent.c nls.c; do sed -i -e s/asm/unaligned.h/linux/unaligned.h/ done for i in ; do sed -i -e s/asm-generic/unaligned.h/linux/unaligned.h/ done git mv include/asm-generic/unaligned.h include/linux/unaligned.h git mv tools/include/asm-generic/unaligned.h tools/include/linux/unaligned.h sed -i -e /unaligned.h/d include/asm-generic/Kbuild sed -i -e s/__ASM_GENERIC/__LINUX/ include/linux/unaligned.h tools/include/linux/unaligned.h Signed-off-by: Al Viro Signed-off-by: Namjae Jeon --- cache.c | 5 +++++ fatent.c | 5 +++++ nls.c | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/cache.c b/cache.c index 26792b6..6a47fbd 100644 --- a/cache.c +++ b/cache.c @@ -11,7 +11,12 @@ */ #include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#include +#else #include +#endif #include #include "exfat_raw.h" diff --git a/fatent.c b/fatent.c index 81f03ea..722a89b 100644 --- a/fatent.c +++ b/fatent.c @@ -4,7 +4,12 @@ */ #include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#include +#else #include +#endif #include #include diff --git a/nls.c b/nls.c index 130edf8..6ef9827 100644 --- a/nls.c +++ b/nls.c @@ -7,7 +7,11 @@ #include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#include +#else #include +#endif #include "exfat_raw.h" #include "exfat_fs.h" From 2d9f4317182824eb21aa5140a81dc22a166e83c3 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 14:08:54 +0900 Subject: [PATCH 132/141] exfat: fix out-of-bounds access of directory entries In the case of the directory size is greater than or equal to the cluster size, if start_clu becomes an EOF cluster(an invalid cluster) due to file system corruption, then the directory entry where ei->hint_femp.eidx hint is outside the directory, resulting in an out-of-bounds access, which may cause further file system corruption. This commit adds a check for start_clu, if it is an invalid cluster, the file or directory will be treated as empty. Cc: stable@vger.kernel.org Signed-off-by: Yuezhang Mo Co-developed-by: Namjae Jeon Signed-off-by: Namjae Jeon --- namei.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/namei.c b/namei.c index 9762e81..343922a 100644 --- a/namei.c +++ b/namei.c @@ -661,14 +661,26 @@ static int exfat_find(struct inode *dir, struct qstr *qname, info->size = le64_to_cpu(ep2->dentry.stream.valid_size); info->valid_size = le64_to_cpu(ep2->dentry.stream.valid_size); info->size = le64_to_cpu(ep2->dentry.stream.size); + + info->start_clu = le32_to_cpu(ep2->dentry.stream.start_clu); + if (!is_valid_cluster(sbi, info->start_clu) && info->size) { + exfat_warn(sb, "start_clu is invalid cluster(0x%x)", + info->start_clu); + info->size = 0; + info->valid_size = 0; + } + + if (info->valid_size > info->size) { + exfat_warn(sb, "valid_size(%lld) is greater than size(%lld)", + info->valid_size, info->size); + info->valid_size = info->size; + } + if (info->size == 0) { info->flags = ALLOC_NO_FAT_CHAIN; info->start_clu = EXFAT_EOF_CLUSTER; - } else { + } else info->flags = ep2->dentry.stream.flags; - info->start_clu = - le32_to_cpu(ep2->dentry.stream.start_clu); - } exfat_get_entry_time(sbi, &info->crtime, ep->dentry.file.create_tz, From 3112e41d8ec0b65e402ce60ba025b60bde791fc6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 27 Nov 2024 14:09:42 +0900 Subject: [PATCH 133/141] exfat: fix uninit-value in __exfat_get_dentry_set There is no check if stream size and start_clu are invalid. If start_clu is EOF cluster and stream size is 4096, It will cause uninit value access. because ei->hint_femp.eidx could be 128(if cluster size is 4K) and wrong hint will allocate next cluster. and this cluster will be same with the cluster that is allocated by exfat_extend_valid_size(). The previous patch will check invalid start_clu, but for clarity, initialize hint_femp.eidx to zero. Cc: stable@vger.kernel.org Reported-by: syzbot+01218003be74b5e1213a@syzkaller.appspotmail.com Tested-by: syzbot+01218003be74b5e1213a@syzkaller.appspotmail.com Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- namei.c | 1 + 1 file changed, 1 insertion(+) diff --git a/namei.c b/namei.c index 343922a..efef6ab 100644 --- a/namei.c +++ b/namei.c @@ -346,6 +346,7 @@ static int exfat_find_empty_entry(struct inode *inode, if (ei->start_clu == EXFAT_EOF_CLUSTER) { ei->start_clu = clu.dir; p_dir->dir = clu.dir; + hint_femp.eidx = 0; } /* append to the FAT chain */ From ec490757b30ba7a3c1d606845f9b16754f723947 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 15:01:39 +0900 Subject: [PATCH 134/141] exfat: remove unnecessary read entry in __exfat_rename() To determine whether it is a directory, there is no need to read its directory entry, just use S_ISDIR(inode->i_mode). Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- file.c | 10 ++++++++++ namei.c | 20 ++++---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/file.c b/file.c index 09093ac..c9d0b4f 100644 --- a/file.c +++ b/file.c @@ -704,6 +704,16 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter) if (ret < 0) goto unlock; + if (iocb->ki_flags & IOCB_DIRECT) { + unsigned long align = pos | iov_iter_alignment(iter); + + if (!IS_ALIGNED(align, i_blocksize(inode)) && + !IS_ALIGNED(align, bdev_logical_block_size(inode->i_sb->s_bdev))) { + ret = -EINVAL; + goto unlock; + } + } + if (pos > valid_size) { ret = exfat_extend_valid_size(file, pos); if (ret < 0 && ret != -ENOSPC) { diff --git a/namei.c b/namei.c index efef6ab..0daeedf 100644 --- a/namei.c +++ b/namei.c @@ -1216,17 +1216,12 @@ static int __exfat_rename(struct inode *old_parent_inode, int ret; int dentry; struct exfat_chain olddir, newdir; - struct exfat_chain *p_dir = NULL; struct exfat_uni_name uni_name; - struct exfat_dentry *ep; struct super_block *sb = old_parent_inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); const unsigned char *new_path = new_dentry->d_name.name; struct inode *new_inode = new_dentry->d_inode; struct exfat_inode_info *new_ei = NULL; - unsigned int new_entry_type = TYPE_UNUSED; - int new_entry = 0; - struct buffer_head *new_bh = NULL; /* check the validity of pointer parameters */ if (new_path == NULL || strlen(new_path) == 0) @@ -1252,17 +1247,8 @@ static int __exfat_rename(struct inode *old_parent_inode, goto out; } - p_dir = &(new_ei->dir); - new_entry = new_ei->entry; - ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); - if (!ep) - goto out; - - new_entry_type = exfat_get_entry_type(ep); - brelse(new_bh); - /* if new_inode exists, update ei */ - if (new_entry_type == TYPE_DIR) { + if (S_ISDIR(new_inode->i_mode)) { struct exfat_chain new_clu; new_clu.dir = new_ei->start_clu; @@ -1294,6 +1280,8 @@ static int __exfat_rename(struct inode *old_parent_inode, if (!ret && new_inode) { struct exfat_entry_set_cache es; + struct exfat_chain *p_dir = &(new_ei->dir); + int new_entry = new_ei->entry; /* delete entries of new_dir */ ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry, @@ -1310,7 +1298,7 @@ static int __exfat_rename(struct inode *old_parent_inode, goto del_out; /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */ - if (new_entry_type == TYPE_DIR && + if (S_ISDIR(new_inode->i_mode) && new_ei->start_clu != EXFAT_EOF_CLUSTER) { /* new_ei, new_clu_to_free */ struct exfat_chain new_clu_to_free; From e9113bdc1cc87411634b3d19fb6928618ce141a0 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 15:02:20 +0900 Subject: [PATCH 135/141] exfat: rename argument name for exfat_move_file and exfat_rename_file In this exfat implementation, the relationship between inode and ei is ei=EXFAT_I(inode). However, in the arguments of exfat_move_file() and exfat_rename_file(), argument 'inode' indicates the parent directory, but argument 'ei' indicates the target file to be renamed. They do not have the above relationship, which is not friendly to code readers. So this commit renames 'inode' to 'parent_inode', making the argument name match its role. Signed-off-by: Yuezhang Mo Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/namei.c b/namei.c index 0daeedf..404b54b 100644 --- a/namei.c +++ b/namei.c @@ -1080,15 +1080,15 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) return err; } -static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, +static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir, int oldentry, struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { int ret, num_new_entries; struct exfat_dentry *epold, *epnew; - struct super_block *sb = inode->i_sb; + struct super_block *sb = parent_inode->i_sb; struct exfat_entry_set_cache old_es, new_es; - int sync = IS_DIRSYNC(inode); + int sync = IS_DIRSYNC(parent_inode); if (unlikely(exfat_forced_shutdown(sb))) return -EIO; @@ -1108,7 +1108,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, if (old_es.num_entries < num_new_entries) { int newentry; - newentry = exfat_find_empty_entry(inode, p_dir, num_new_entries, + newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries, &new_es); if (newentry < 0) { ret = newentry; /* -EIO or -ENOSPC */ @@ -1132,7 +1132,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, if (ret) goto put_old_es; - exfat_remove_entries(inode, &old_es, ES_IDX_FILE); + exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE); ei->dir = *p_dir; ei->entry = newentry; } else { @@ -1141,7 +1141,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, ei->attr |= EXFAT_ATTR_ARCHIVE; } - exfat_remove_entries(inode, &old_es, ES_IDX_FIRST_FILENAME + 1); + exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1); exfat_init_ext_entry(&old_es, num_new_entries, p_uniname); } return exfat_put_dentry_set(&old_es, sync); @@ -1151,13 +1151,13 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, return ret; } -static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, +static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir, int oldentry, struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { int ret, newentry, num_new_entries; struct exfat_dentry *epmov, *epnew; - struct super_block *sb = inode->i_sb; + struct super_block *sb = parent_inode->i_sb; struct exfat_entry_set_cache mov_es, new_es; num_new_entries = exfat_calc_num_entries(p_uniname); @@ -1169,7 +1169,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, if (ret) return -EIO; - newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries, + newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries, &new_es); if (newentry < 0) { ret = newentry; /* -EIO or -ENOSPC */ @@ -1189,18 +1189,18 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, *epnew = *epmov; exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); - exfat_remove_entries(inode, &mov_es, ES_IDX_FILE); + exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE); exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, p_newdir->flags); ei->entry = newentry; - ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(inode)); + ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode)); if (ret) goto put_mov_es; - return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(inode)); + return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode)); put_mov_es: exfat_put_dentry_set(&mov_es, false); From ec7b92959692290257f4e11cfe511a14e7f379e9 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 15:03:16 +0900 Subject: [PATCH 136/141] exfat: add exfat_get_dentry_set_by_ei() helper This helper gets the directory entry set of the file for the exfat inode which has been created. It's used to remove all the instances of the pattern it replaces making the code cleaner, it's also a preparation for changing ->dir to record the cluster where the directory entry set is located and changing ->entry to record the index of the directory entry within the cluster. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 ++ inode.c | 2 +- namei.c | 53 ++++++++++++++++++----------------------------------- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 878856e..6e68ae6 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -535,6 +535,8 @@ struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, int exfat_get_dentry_set(struct exfat_entry_set_cache *es, struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned int num_entries); +#define exfat_get_dentry_set_by_ei(es, sb, ei) \ + exfat_get_dentry_set(es, sb, &(ei)->dir, (ei)->entry, ES_ALL_ENTRIES) int exfat_get_empty_dentry_set(struct exfat_entry_set_cache *es, struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned int num_entries); diff --git a/inode.c b/inode.c index fedf0c9..410c281 100644 --- a/inode.c +++ b/inode.c @@ -45,7 +45,7 @@ int __exfat_write_inode(struct inode *inode, int sync) exfat_set_volume_dirty(sb); /* get the directory entry of given file or directory */ - if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES)) + if (exfat_get_dentry_set_by_ei(&es, sb, ei)) return -EIO; ep = exfat_get_dentry_cached(&es, ES_IDX_FILE); ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM); diff --git a/namei.c b/namei.c index 404b54b..b588f8b 100644 --- a/namei.c +++ b/namei.c @@ -803,26 +803,23 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, /* remove an entry, BUT don't truncate */ static int exfat_unlink(struct inode *dir, struct dentry *dentry) { - struct exfat_chain cdir; struct super_block *sb = dir->i_sb; struct inode *inode = dentry->d_inode; struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_entry_set_cache es; - int entry, err = 0; + int err = 0; if (unlikely(exfat_forced_shutdown(sb))) return -EIO; mutex_lock(&EXFAT_SB(sb)->s_lock); - exfat_chain_dup(&cdir, &ei->dir); - entry = ei->entry; if (ei->dir.dir == DIR_DELETED) { exfat_err(sb, "abnormal access to deleted dentry"); err = -ENOENT; goto unlock; } - err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES); + err = exfat_get_dentry_set_by_ei(&es, sb, ei); if (err) { err = -EIO; goto unlock; @@ -995,21 +992,18 @@ static int exfat_check_dir_empty(struct super_block *sb, static int exfat_rmdir(struct inode *dir, struct dentry *dentry) { struct inode *inode = dentry->d_inode; - struct exfat_chain cdir, clu_to_free; + struct exfat_chain clu_to_free; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_entry_set_cache es; - int entry, err; + int err; if (unlikely(exfat_forced_shutdown(sb))) return -EIO; mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); - exfat_chain_dup(&cdir, &ei->dir); - entry = ei->entry; - if (ei->dir.dir == DIR_DELETED) { exfat_err(sb, "abnormal access to deleted dentry"); err = -ENOENT; @@ -1027,7 +1021,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) goto unlock; } - err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES); + err = exfat_get_dentry_set_by_ei(&es, sb, ei); if (err) { err = -EIO; goto unlock; @@ -1080,8 +1074,8 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) return err; } -static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir, - int oldentry, struct exfat_uni_name *p_uniname, +static int exfat_rename_file(struct inode *parent_inode, + struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { int ret, num_new_entries; @@ -1097,7 +1091,7 @@ static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_d if (num_new_entries < 0) return num_new_entries; - ret = exfat_get_dentry_set(&old_es, sb, p_dir, oldentry, ES_ALL_ENTRIES); + ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei); if (ret) { ret = -EIO; return ret; @@ -1151,21 +1145,19 @@ static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_d return ret; } -static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir, - int oldentry, struct exfat_chain *p_newdir, - struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) +static int exfat_move_file(struct inode *parent_inode, + struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname, + struct exfat_inode_info *ei) { int ret, newentry, num_new_entries; struct exfat_dentry *epmov, *epnew; - struct super_block *sb = parent_inode->i_sb; struct exfat_entry_set_cache mov_es, new_es; num_new_entries = exfat_calc_num_entries(p_uniname); if (num_new_entries < 0) return num_new_entries; - ret = exfat_get_dentry_set(&mov_es, sb, p_olddir, oldentry, - ES_ALL_ENTRIES); + ret = exfat_get_dentry_set_by_ei(&mov_es, parent_inode->i_sb, ei); if (ret) return -EIO; @@ -1214,8 +1206,7 @@ static int __exfat_rename(struct inode *old_parent_inode, struct dentry *new_dentry) { int ret; - int dentry; - struct exfat_chain olddir, newdir; + struct exfat_chain newdir; struct exfat_uni_name uni_name; struct super_block *sb = old_parent_inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -1232,11 +1223,6 @@ static int __exfat_rename(struct inode *old_parent_inode, return -ENOENT; } - exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu, - EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi), - EXFAT_I(old_parent_inode)->flags); - dentry = ei->entry; - /* check whether new dir is existing directory and empty */ if (new_inode) { ret = -EIO; @@ -1271,21 +1257,18 @@ static int __exfat_rename(struct inode *old_parent_inode, exfat_set_volume_dirty(sb); - if (olddir.dir == newdir.dir) - ret = exfat_rename_file(new_parent_inode, &olddir, dentry, + if (new_parent_inode == old_parent_inode) + ret = exfat_rename_file(new_parent_inode, &newdir, &uni_name, ei); else - ret = exfat_move_file(new_parent_inode, &olddir, dentry, - &newdir, &uni_name, ei); + ret = exfat_move_file(new_parent_inode, &newdir, + &uni_name, ei); if (!ret && new_inode) { struct exfat_entry_set_cache es; - struct exfat_chain *p_dir = &(new_ei->dir); - int new_entry = new_ei->entry; /* delete entries of new_dir */ - ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry, - ES_ALL_ENTRIES); + ret = exfat_get_dentry_set_by_ei(&es, sb, new_ei); if (ret) { ret = -EIO; goto del_out; From 1804f145a8a416da46fb946b7f3f469346c1e3aa Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 15:04:07 +0900 Subject: [PATCH 137/141] exfat: move exfat_chain_set() out of __exfat_resolve_path() __exfat_resolve_path() mixes two functions. The first one is to resolve and check if the path is valid. The second one is to output the cluster assigned to the directory. The second one is only needed when need to traverse the directory entries, and calling exfat_chain_set() so early causes p_dir to be passed as an argument multiple times, increasing the complexity of the code. This commit moves the call to exfat_chain_set() before traversing directory entries. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 60 +++++++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/namei.c b/namei.c index b588f8b..1a1d52c 100644 --- a/namei.c +++ b/namei.c @@ -312,6 +312,9 @@ static int exfat_find_empty_entry(struct inode *inode, ei->hint_femp.eidx = EXFAT_HINT_NONE; } + exfat_chain_set(p_dir, ei->start_clu, + EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); + while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir, num_entries, es)) < 0) { if (dentry == -EIO) @@ -387,14 +390,11 @@ static int exfat_find_empty_entry(struct inode *inode, * Zero if it was successful; otherwise nonzero. */ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, - struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, - int lookup) + struct exfat_uni_name *p_uniname, int lookup) { int namelen; int lossy = NLS_NAME_NO_LOSSY; struct super_block *sb = inode->i_sb; - struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct exfat_inode_info *ei = EXFAT_I(inode); int pathlen = strlen(path); /* @@ -433,24 +433,19 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, if ((lossy && !lookup) || !namelen) return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL; - exfat_chain_set(p_dir, ei->start_clu, - EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); - return 0; } static inline int exfat_resolve_path(struct inode *inode, - const unsigned char *path, struct exfat_chain *dir, - struct exfat_uni_name *uni) + const unsigned char *path, struct exfat_uni_name *uni) { - return __exfat_resolve_path(inode, path, dir, uni, 0); + return __exfat_resolve_path(inode, path, uni, 0); } static inline int exfat_resolve_path_for_lookup(struct inode *inode, - const unsigned char *path, struct exfat_chain *dir, - struct exfat_uni_name *uni) + const unsigned char *path, struct exfat_uni_name *uni) { - return __exfat_resolve_path(inode, path, dir, uni, 1); + return __exfat_resolve_path(inode, path, uni, 1); } static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info) @@ -472,7 +467,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, int clu_size = 0; unsigned int start_clu = EXFAT_FREE_CLUSTER; - ret = exfat_resolve_path(inode, path, p_dir, &uniname); + ret = exfat_resolve_path(inode, path, &uniname); if (ret) goto out; @@ -626,10 +621,13 @@ static int exfat_find(struct inode *dir, struct qstr *qname, return -ENOENT; /* check the validity of directory name in the given pathname */ - ret = exfat_resolve_path_for_lookup(dir, qname->name, &cdir, &uni_name); + ret = exfat_resolve_path_for_lookup(dir, qname->name, &uni_name); if (ret) return ret; + exfat_chain_set(&cdir, ei->start_clu, + EXFAT_B_TO_CLU(i_size_read(dir), sbi), ei->flags); + /* check the validation of hint_stat and initialize it if required */ if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { ei->hint_stat.clu = cdir.dir; @@ -1075,8 +1073,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) } static int exfat_rename_file(struct inode *parent_inode, - struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, - struct exfat_inode_info *ei) + struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { int ret, num_new_entries; struct exfat_dentry *epold, *epnew; @@ -1101,9 +1098,10 @@ static int exfat_rename_file(struct inode *parent_inode, if (old_es.num_entries < num_new_entries) { int newentry; + struct exfat_chain dir; - newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries, - &new_es); + newentry = exfat_find_empty_entry(parent_inode, &dir, + num_new_entries, &new_es); if (newentry < 0) { ret = newentry; /* -EIO or -ENOSPC */ goto put_old_es; @@ -1127,7 +1125,7 @@ static int exfat_rename_file(struct inode *parent_inode, goto put_old_es; exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE); - ei->dir = *p_dir; + ei->dir = dir; ei->entry = newentry; } else { if (exfat_get_entry_type(epold) == TYPE_FILE) { @@ -1146,12 +1144,12 @@ static int exfat_rename_file(struct inode *parent_inode, } static int exfat_move_file(struct inode *parent_inode, - struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname, - struct exfat_inode_info *ei) + struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { int ret, newentry, num_new_entries; struct exfat_dentry *epmov, *epnew; struct exfat_entry_set_cache mov_es, new_es; + struct exfat_chain newdir; num_new_entries = exfat_calc_num_entries(p_uniname); if (num_new_entries < 0) @@ -1161,8 +1159,8 @@ static int exfat_move_file(struct inode *parent_inode, if (ret) return -EIO; - newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries, - &new_es); + newentry = exfat_find_empty_entry(parent_inode, &newdir, + num_new_entries, &new_es); if (newentry < 0) { ret = newentry; /* -EIO or -ENOSPC */ goto put_mov_es; @@ -1183,9 +1181,7 @@ static int exfat_move_file(struct inode *parent_inode, exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE); - exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, - p_newdir->flags); - + ei->dir = newdir; ei->entry = newentry; ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode)); @@ -1206,7 +1202,6 @@ static int __exfat_rename(struct inode *old_parent_inode, struct dentry *new_dentry) { int ret; - struct exfat_chain newdir; struct exfat_uni_name uni_name; struct super_block *sb = old_parent_inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -1250,19 +1245,16 @@ static int __exfat_rename(struct inode *old_parent_inode, } /* check the validity of directory name in the given new pathname */ - ret = exfat_resolve_path(new_parent_inode, new_path, &newdir, - &uni_name); + ret = exfat_resolve_path(new_parent_inode, new_path, &uni_name); if (ret) goto out; exfat_set_volume_dirty(sb); if (new_parent_inode == old_parent_inode) - ret = exfat_rename_file(new_parent_inode, &newdir, - &uni_name, ei); + ret = exfat_rename_file(new_parent_inode, &uni_name, ei); else - ret = exfat_move_file(new_parent_inode, &newdir, - &uni_name, ei); + ret = exfat_move_file(new_parent_inode, &uni_name, ei); if (!ret && new_inode) { struct exfat_entry_set_cache es; From 40e2393161d508ddb7a808508965cb355c9288e1 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 15:04:59 +0900 Subject: [PATCH 138/141] exfat: remove argument 'p_dir' from exfat_add_entry() The output of argument 'p_dir' of exfat_add_entry() is not used in either exfat_mkdir() or exfat_create(), remove the argument. Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/namei.c b/namei.c index 1a1d52c..d412965 100644 --- a/namei.c +++ b/namei.c @@ -454,8 +454,7 @@ static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info) } static int exfat_add_entry(struct inode *inode, const char *path, - struct exfat_chain *p_dir, unsigned int type, - struct exfat_dir_entry *info) + unsigned int type, struct exfat_dir_entry *info) { int ret, dentry, num_entries; struct super_block *sb = inode->i_sb; @@ -478,7 +477,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, } /* exfat_find_empty_entry must be called before alloc_cluster() */ - dentry = exfat_find_empty_entry(inode, p_dir, num_entries, &es); + dentry = exfat_find_empty_entry(inode, &info->dir, num_entries, &es); if (dentry < 0) { ret = dentry; /* -EIO or -ENOSPC */ goto out; @@ -505,7 +504,6 @@ static int exfat_add_entry(struct inode *inode, const char *path, if (ret) goto out; - info->dir = *p_dir; info->entry = dentry; info->flags = ALLOC_NO_FAT_CHAIN; info->type = type; @@ -543,7 +541,6 @@ static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, { struct super_block *sb = dir->i_sb; struct inode *inode; - struct exfat_chain cdir; struct exfat_dir_entry info; loff_t i_pos; int err; @@ -554,8 +551,7 @@ static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_set_volume_dirty(sb); - err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE, - &info); + err = exfat_add_entry(dir, dentry->d_name.name, TYPE_FILE, &info); if (err) goto unlock; @@ -880,7 +876,6 @@ static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, struct super_block *sb = dir->i_sb; struct inode *inode; struct exfat_dir_entry info; - struct exfat_chain cdir; loff_t i_pos; int err; loff_t size = i_size_read(dir); @@ -890,8 +885,7 @@ static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, mutex_lock(&EXFAT_SB(sb)->s_lock); exfat_set_volume_dirty(sb); - err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR, - &info); + err = exfat_add_entry(dir, dentry->d_name.name, TYPE_DIR, &info); if (err) goto unlock; From bc00361d5a549652751ef3f8dd3b2bc2a05afdb6 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 15:05:45 +0900 Subject: [PATCH 139/141] exfat: code cleanup for exfat_readdir() For the root directory and other directories, the clusters allocated to them can be obtained from exfat_inode_info, and there is no need to distinguish them. And there is no need to initialize atime/ctime/mtime/size in exfat_readdir(), because exfat_iterate() does not use them. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/dir.c b/dir.c index c10ba2c..cf22b30 100644 --- a/dir.c +++ b/dir.c @@ -83,11 +83,8 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent if (ei->type != TYPE_DIR) return -EPERM; - if (ei->entry == -1) - exfat_chain_set(&dir, sbi->root_dir, 0, ALLOC_FAT_CHAIN); - else - exfat_chain_set(&dir, ei->start_clu, - EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); + exfat_chain_set(&dir, ei->start_clu, + EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); dentries_per_clu = sbi->dentries_per_clu; max_dentries = (unsigned int)min_t(u64, MAX_EXFAT_DENTRIES, @@ -136,21 +133,6 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent num_ext = ep->dentry.file.num_ext; dir_entry->attr = le16_to_cpu(ep->dentry.file.attr); - exfat_get_entry_time(sbi, &dir_entry->crtime, - ep->dentry.file.create_tz, - ep->dentry.file.create_time, - ep->dentry.file.create_date, - ep->dentry.file.create_time_cs); - exfat_get_entry_time(sbi, &dir_entry->mtime, - ep->dentry.file.modify_tz, - ep->dentry.file.modify_time, - ep->dentry.file.modify_date, - ep->dentry.file.modify_time_cs); - exfat_get_entry_time(sbi, &dir_entry->atime, - ep->dentry.file.access_tz, - ep->dentry.file.access_time, - ep->dentry.file.access_date, - 0); *uni_name.name = 0x0; err = exfat_get_uniname_from_ext_entry(sb, &clu, i, @@ -167,8 +149,6 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent ep = exfat_get_dentry(sb, &clu, i + 1, &bh); if (!ep) return -EIO; - dir_entry->size = - le64_to_cpu(ep->dentry.stream.valid_size); dir_entry->entry = dentry; brelse(bh); From b366b0b4a2be116d55e0b6d5e27d87d99d668ee3 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 27 Nov 2024 15:06:33 +0900 Subject: [PATCH 140/141] exfat: reduce FAT chain traversal Before this commit, ->dir and ->entry of exfat_inode_info record the first cluster of the parent directory and the directory entry index starting from this cluster. The directory entry set will be gotten during write-back-inode/rmdir/ unlink/rename. If the clusters of the parent directory are not continuous, the FAT chain will be traversed from the first cluster of the parent directory to find the cluster where ->entry is located. After this commit, ->dir records the cluster where the first directory entry in the directory entry set is located, and ->entry records the directory entry index in the cluster, so that there is almost no need to access the FAT when getting the directory entry set. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 5 +++-- exfat_fs.h | 4 ++++ namei.c | 32 +++++++++++++++++++++++++------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/dir.c b/dir.c index cf22b30..c23cb96 100644 --- a/dir.c +++ b/dir.c @@ -149,7 +149,8 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent ep = exfat_get_dentry(sb, &clu, i + 1, &bh); if (!ep) return -EIO; - dir_entry->entry = dentry; + dir_entry->entry = i; + dir_entry->dir = clu; brelse(bh); ei->hint_bmap.off = EXFAT_DEN_TO_CLU(dentry, sbi); @@ -257,7 +258,7 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) if (!nb->lfn[0]) goto end_of_dir; - i_pos = ((loff_t)ei->start_clu << 32) | (de.entry & 0xffffffff); + i_pos = ((loff_t)de.dir.dir << 32) | (de.entry & 0xffffffff); tmp = exfat_iget(sb, i_pos); if (tmp) { inum = tmp->i_ino; diff --git a/exfat_fs.h b/exfat_fs.h index 6e68ae6..ee46acb 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -221,7 +221,9 @@ struct exfat_entry_set_cache { #define IS_DYNAMIC_ES(es) ((es)->__bh != (es)->bh) struct exfat_dir_entry { + /* the cluster where file dentry is located */ struct exfat_chain dir; + /* the index of file dentry in ->dir */ int entry; unsigned int type; unsigned int start_clu; @@ -307,7 +309,9 @@ struct exfat_sb_info { * EXFAT file system inode in-memory data */ struct exfat_inode_info { + /* the cluster where file dentry is located */ struct exfat_chain dir; + /* the index of file dentry in ->dir */ int entry; unsigned int type; unsigned short attr; diff --git a/namei.c b/namei.c index d412965..002cf86 100644 --- a/namei.c +++ b/namei.c @@ -289,8 +289,22 @@ static int exfat_check_max_dentries(struct inode *inode) return 0; } -/* find empty directory entry. - * if there isn't any empty slot, expand cluster chain. +/* + * Find an empty directory entry set. + * + * If there isn't any empty slot, expand cluster chain. + * + * in: + * inode: inode of the parent directory + * num_entries: specifies how many dentries in the empty directory entry set + * + * out: + * p_dir: the cluster where the empty directory entry set is located + * es: The found empty directory entry set + * + * return: + * the directory entry index in p_dir is returned on succeeds + * -error code is returned on failure */ static int exfat_find_empty_entry(struct inode *inode, struct exfat_chain *p_dir, int num_entries, @@ -382,7 +396,10 @@ static int exfat_find_empty_entry(struct inode *inode, inode->i_blocks += sbi->cluster_size >> 9; } - return dentry; + p_dir->dir = exfat_sector_to_cluster(sbi, es->bh[0]->b_blocknr); + p_dir->size -= dentry / sbi->dentries_per_clu; + + return dentry & (sbi->dentries_per_clu - 1); } /* @@ -637,15 +654,16 @@ static int exfat_find(struct inode *dir, struct qstr *qname, if (dentry < 0) return dentry; /* -error value */ - info->dir = cdir; - info->entry = dentry; - info->num_subdirs = 0; - /* adjust cdir to the optimized value */ cdir.dir = hint_opt.clu; if (cdir.flags & ALLOC_NO_FAT_CHAIN) cdir.size -= dentry / sbi->dentries_per_clu; dentry = hint_opt.eidx; + + info->dir = cdir; + info->entry = dentry; + info->num_subdirs = 0; + if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES)) return -EIO; ep = exfat_get_dentry_cached(&es, ES_IDX_FILE); From de8180fa852610ea8c47ba66e1a7b07c9d667755 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 27 Nov 2024 15:46:22 +0900 Subject: [PATCH 141/141] exfat: fix build error with linux-5.15 kernel Signed-off-by: Namjae Jeon --- file.c | 4 ++++ super.c | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/file.c b/file.c index c9d0b4f..092bc87 100644 --- a/file.c +++ b/file.c @@ -769,7 +769,11 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) err = exfat_extend_valid_size(file, end); if (err < 0) { inode_unlock(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) return vmf_fs_error(err); +#else + return block_page_mkwrite_return(err); +#endif } } diff --git a/super.c b/super.c index 560f287..bbf72db 100644 --- a/super.c +++ b/super.c @@ -177,7 +177,9 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) int exfat_force_shutdown(struct super_block *sb, u32 flags) { +#if LINUX_VERSION_CODE > KERNEL_VERSION(6, 9, 0) int ret; +#endif struct exfat_sb_info *sbi = sb->s_fs_info; struct exfat_mount_options *opts = &sbi->options; @@ -187,10 +189,12 @@ int exfat_force_shutdown(struct super_block *sb, u32 flags) switch (flags) { case EXFAT_GOING_DOWN_DEFAULT: case EXFAT_GOING_DOWN_FULLSYNC: +#if LINUX_VERSION_CODE > KERNEL_VERSION(6, 9, 0) ret = bdev_freeze(sb->s_bdev); if (ret) return ret; bdev_thaw(sb->s_bdev); +#endif set_bit(EXFAT_FLAGS_SHUTDOWN, &sbi->s_exfat_flags); break; case EXFAT_GOING_DOWN_NOSYNC: @@ -240,7 +244,9 @@ static const struct super_operations exfat_sops = { .sync_fs = exfat_sync_fs, .statfs = exfat_statfs, .show_options = exfat_show_options, +#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 16, 0) .shutdown = exfat_shutdown, +#endif }; enum {