From bed6b023a135dcecd07cfac21ccfa4ef7b3ef7b6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 17 Oct 2022 16:35:31 +0900 Subject: [PATCH 01/57] 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 f3b001c2349d..0db1f254212f 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 02/57] 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 04cc4a413626..000000000000 --- 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 0cbaea440eb9..000000000000 --- 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 3356d15da772..000000000000 --- 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 03/57] 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 000000000000..60eb8403a1a9 --- /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 04/57] 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 07765a74251a..b56ea407907f 100644 --- a/dir.c +++ b/dir.c @@ -908,6 +908,29 @@ free_es: 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 @@ rewind: 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 @@ rewind: 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 @@ not_found: 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 05/57] 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 b56ea407907f..beb7f2c1ff59 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 @@ rewind: 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 @@ not_found: 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 3342f29a7683..23ff0c983a1b 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 06/57] 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 0db1f254212f..486132b69c51 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 07/57] 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 486132b69c51..6b035c0d076e 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 08/57] 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 beb7f2c1ff59..46df6e611f52 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 6b035c0d076e..e10d005bf400 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 09/57] 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 46df6e611f52..f663cb12688f 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 e10d005bf400..a64b687279c2 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 f3dbeb300c64..c66276ea423c 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 23ff0c983a1b..2bb79d21e5b0 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 10/57] 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 f663cb12688f..32ebb2a76d91 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 a64b687279c2..b51e72cae6a7 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 c66276ea423c..87e679d223c3 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 2bb79d21e5b0..27251b3f3017 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 11/57] 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 32ebb2a76d91..b359087e3cbc 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 87e679d223c3..93dc313a3a1c 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 27251b3f3017..f88757013f46 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 12/57] 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 93dc313a3a1c..d0bfa26c6584 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 13/57] 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 60eb8403a1a9..362f95a3fd7e 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 14/57] 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 362f95a3fd7e..0cb70a7650a1 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 15/57] 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 0cb70a7650a1..476c746ba715 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 16/57] 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 b359087e3cbc..546a31cbe724 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 b51e72cae6a7..b3d6fe6c621b 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 17/57] 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 f88757013f46..aef80072fc90 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 18/57] 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 546a31cbe724..8cbff27dbc86 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 @@ rewind: 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 b3d6fe6c621b..10621f18886b 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 aef80072fc90..8814f762232e 100644 --- a/namei.c +++ b/namei.c @@ -647,7 +647,7 @@ unlock: 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 19/57] 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 10621f18886b..fcfddcd44532 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 6e37277bbd84..efef7a9caad8 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 d0bfa26c6584..4bab1e4f5e56 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 20/57] 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 fcfddcd44532..42a9a46d7c69 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 efef7a9caad8..51de3b3781fa 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 4bab1e4f5e56..af305df857df 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 21/57] 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 42a9a46d7c69..02f9529ba543 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 22/57] 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 8cbff27dbc86..a324d2be6a58 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 23/57] 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 a324d2be6a58..e24a0a5bafa4 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 24/57] 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 e24a0a5bafa4..a73cb344915f 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 25/57] 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 02f9529ba543..1bea841bee12 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 26/57] 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 51de3b3781fa..a0388530cffb 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 af305df857df..bce152cd386c 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 8814f762232e..a08278e7a45a 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 c5d55b0308a9..665c296e4ad4 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 27/57] 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 a73cb344915f..f167f68b547d 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 @@ release_fbh: 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) + break; + case ES_MODE_GET_BENIGN_SEC_ENTRY: + /* Assume unreconized benign secondary entry */ + if (!(type & TYPE_BENIGN_SEC)) return false; - if ((type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_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 1bea841bee12..1b3bfd717a83 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 7f39b1c6469c..0ece2e43cf49 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 28/57] 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 bce152cd386c..4d4449e1df3d 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 29/57] 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 ded465bcf84b..e22a77b7d54c 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 30/57] 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 e22a77b7d54c..f46bbce781bb 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 31/57] 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 f46bbce781bb..c13a04e94ebc 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 32/57] 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 1b3bfd717a83..54d5314778c0 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 a0388530cffb..8a04b03f7483 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 33/57] 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 54d5314778c0..b79f2fe2bea5 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 8a04b03f7483..a08076f44100 100644 --- a/file.c +++ b/file.c @@ -237,9 +237,15 @@ write_size: } #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 34/57] 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 a08278e7a45a..c7662737a9bf 100644 --- a/namei.c +++ b/namei.c @@ -579,8 +579,13 @@ out: } #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 35/57] 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 c7662737a9bf..f590de360e4d 100644 --- a/namei.c +++ b/namei.c @@ -918,8 +918,13 @@ unlock: } #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 36/57] 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 f590de360e4d..e6971c758892 100644 --- a/namei.c +++ b/namei.c @@ -1412,10 +1412,17 @@ out: } #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 37/57] 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 2d3636dc5b8c..b3e5e9ebabc8 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 38/57] 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 a08076f44100..e35998f5aa8c 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 39/57] 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 30c2414d859c..21a865e0ac60 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 40/57] 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 476c746ba715..375c800ac23c 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 41/57] 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 f167f68b547d..fd721eda2f41 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 @@ rewind: 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 42/57] 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 fd721eda2f41..bf8eb5d1b1c2 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 @@ get_new: 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 @@ end_of_dir: 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 43/57] 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 21a865e0ac60..c226d5d1c36b 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 44/57] 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 bf8eb5d1b1c2..a9dc79e0e6d4 100644 --- a/dir.c +++ b/dir.c @@ -307,10 +307,17 @@ out: 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 45/57] 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 b79f2fe2bea5..97ee6843c17e 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 e35998f5aa8c..216c38b981e1 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 @@ out: 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 46/57] 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 e6971c758892..3429a8e47495 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 47/57] 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 a9dc79e0e6d4..f8a04e16dc88 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 97ee6843c17e..311d9f28e25f 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 3429a8e47495..d43af646e229 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 665c296e4ad4..89f60d722c99 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 48/57] 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 375c800ac23c..6587fd98bd41 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 49/57] 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 | 17 +++++++++-------- file.c | 16 +++++++++------- inode.c | 6 +++--- namei.c | 16 ++++++++-------- super.c | 4 ++-- 7 files changed, 41 insertions(+), 44 deletions(-) diff --git a/dir.c b/dir.c index f8a04e16dc88..4d5b863b4fde 100644 --- a/dir.c +++ b/dir.c @@ -288,7 +288,7 @@ get_new: 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 311d9f28e25f..9c2266afe3f6 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 0ece2e43cf49..971a1ccd0e89 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 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 ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ - ATTR_SUBDIR | ATTR_ARCHIVE) +#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 216c38b981e1..59848fa2793d 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 4d4449e1df3d..e4dffd734ba4 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 d43af646e229..5b2204cb2e4b 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 89f60d722c99..caf8fd4acf33 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 50/57] 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 b3e5e9ebabc8..e298907a829a 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 51/57] 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 5b2204cb2e4b..01d1aefa1670 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 52/57] 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 01d1aefa1670..ba6c604a48dd 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 53/57] 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 59848fa2793d..c82e5792e504 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 54/57] 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 c82e5792e504..2b74bd03e55e 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 e4dffd734ba4..89e341e74640 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 ba6c604a48dd..0dd33a0cfddc 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 caf8fd4acf33..0b09959c26e6 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 55/57] 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 9c2266afe3f6..124dfd599c11 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 2b74bd03e55e..72879e405531 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 89e341e74640..7d7f2cff8a05 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 e7ae6f629d6e..a744a1fb1e33 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 0dd33a0cfddc..ef9a6df20998 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 0b09959c26e6..decf1c68323b 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 56/57] 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 7d7f2cff8a05..479db4299c83 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 57/57] 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 72879e405531..749c349cbfba 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)