Merge branch 'master' of https://github.com/namjaejeon/linux-exfat-oot into android13-5.4-lahaina

* 'master' of https://github.com/namjaejeon/linux-exfat-oot:
  exfat: add necessary header for vmalloc
  exfat: release s_lock before calling dir_emit()
  exfat: check if filename entries exceeds max filename length
  exfat: github action: make space for running xfstests
  exfat: use kvmalloc_array/kvfree instead of kmalloc_array/kfree
  exfat: splice: Use filemap_splice_read() instead of generic_file_splice_read()
  exfat: fs: build the legacy direct I/O code conditionally
  exfat: fs: port ->rename() to pass mnt_idmap
  exfat: fs: port ->mkdir() to pass mnt_idmap
  exfat: fs: port ->create() to pass mnt_idmap
  exfat: fs: port ->getattr() to pass mnt_idmap
  exfat: fs: port ->setattr() to pass mnt_idmap
  exfat: fix the newly allocated clusters are not freed in error handling
  exfat: don't print error log in normal case
  exfat: remove unneeded code from exfat_alloc_cluster()
  exfat: remove ->writepage
  exfat: handle unreconized benign secondary entries
  exfat: fix inode->i_blocks for non-512 byte sector size device
  exfat: redefine DIR_DELETED as the bad cluster number
  exfat: fix reporting fs error when reading dir beyond EOF
  exfat: fix unexpected EOF while reading dir
  exfat: reuse exfat_find_location() to simplify exfat_get_dentry_set()
  exfat: fix overflow in sector and cluster conversion
  exfat: remove i_size_write() from __exfat_truncate()
  exfat: remove argument 'size' from exfat_truncate()
  exfat: remove unnecessary arguments from exfat_find_dir_entry()
  exfat: remove unneeded codes from __exfat_rename()
  exfat: remove call ilog2() from exfat_readdir()
  exfat: remove generic/286
  exfat: fix python package installation failure
  exfat: github actions: add apt-get update command
  exfat: treewide: use get_random_u32() when possible
  exfat: replace magic numbers with Macros
  exfat: rename exfat_free_dentry_set() to exfat_put_dentry_set()
  exfat: move exfat_entry_set_cache from heap to stack
  exfat: support dynamic allocate bh for exfat_entry_set_cache
  exfat: reduce the size of exfat_entry_set_cache
  exfat: add SECTOR_SIZE macro
  exfat: hint the empty entry which at the end of cluster chain
  exfat: simplify empty entry hint
  exfat: add auto-test using github action
  exfat: remove travis-CI test
  exfat: release 6.0.0 version
  exfat: fix overflow for large capacity partition
  exfat: add auto build-test and simple stability test using travis-CI
  exfat: Drop superfluous new line for error messages
  exfat: Downgrade ENAMETOOLONG error message to debug messages
  exfat: Expand exfat_err() and co directly to pr_*() macro
  exfat: Define NLS_NAME_* as bit flags explicitly
  exfat: Return ENAMETOOLONG consistently for oversized paths
  exfat: simplified by using round_up()
  fs: Convert mpage_readpage to mpage_read_folio
  fs: Remove flags parameter from aops->write_begin
  fs: Remove aop flags parameter from cont_write_begin()
  block: add a bdev_discard_granularity helper
  block: remove QUEUE_FLAG_DISCARD
  exfat: remove duplicate write inode for extending dir/file
  exfat: remove duplicate write inode for truncating file
  exfat: reuse __exfat_write_inode() to update directory entry
  exfat: use updated exfat_chain directly during renaming

Change-Id: Ib68d90de9ea596296407f446fa611525034db193
This commit is contained in:
Michael Bestas 2023-08-25 12:15:07 +03:00
commit 1cbc6feae2
No known key found for this signature in database
GPG Key ID: CC95044519BE6669
15 changed files with 704 additions and 493 deletions

196
fs/exfat/.github/workflows/c-cpp.yml vendored Normal file
View File

@ -0,0 +1,196 @@
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 update
sudo apt-get install libelf-dev wget tar gzip python2.7
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz
tar xf linux-4.1.36.tar.gz
mv linux-4.1.36 linux-stable
- 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
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
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 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
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/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

View File

@ -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]);

View File

@ -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

View File

@ -3,6 +3,7 @@
config EXFAT_FS config EXFAT_FS
tristate "exFAT filesystem support" tristate "exFAT filesystem support"
select NLS select NLS
select LEGACY_DIRECT_IO
help help
This allows you to mount devices formatted with the exFAT file system. This allows you to mount devices formatted with the exFAT file system.
exFAT is typically used on SD-Cards or USB sticks. exFAT is typically used on SD-Cards or USB sticks.

View File

@ -12,6 +12,7 @@
#else #else
#include <linux/sched.h> #include <linux/sched.h>
#endif #endif
#include <linux/vmalloc.h>
#include "exfat_raw.h" #include "exfat_raw.h"
#include "exfat_fs.h" #include "exfat_fs.h"
@ -75,8 +76,12 @@ static int exfat_allocate_bitmap(struct super_block *sb,
} }
sbi->map_sectors = ((need_map_size - 1) >> sbi->map_sectors = ((need_map_size - 1) >>
(sb->s_blocksize_bits)) + 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); sizeof(struct buffer_head *), GFP_KERNEL);
#else
sbi->vol_amap = vmalloc(sbi->map_sectors * sizeof(struct buffer_head *));
#endif
if (!sbi->vol_amap) if (!sbi->vol_amap)
return -ENOMEM; return -ENOMEM;
@ -90,7 +95,7 @@ static int exfat_allocate_bitmap(struct super_block *sb,
while (j < i) while (j < i)
brelse(sbi->vol_amap[j++]); brelse(sbi->vol_amap[j++]);
kfree(sbi->vol_amap); kvfree(sbi->vol_amap);
sbi->vol_amap = NULL; sbi->vol_amap = NULL;
return -EIO; return -EIO;
} }
@ -144,7 +149,7 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi)
for (i = 0; i < sbi->map_sectors; i++) for (i = 0; i < sbi->map_sectors; i++)
__brelse(sbi->vol_amap[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) int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync)

View File

@ -30,15 +30,16 @@ 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) struct exfat_chain *p_dir, int entry, unsigned short *uniname)
{ {
int i; int i, err;
struct exfat_entry_set_cache *es; struct exfat_entry_set_cache es;
unsigned int uni_len = 0, len;
es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES); err = exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES);
if (!es) if (err)
return; return err;
/* /*
* First entry : file entry * First entry : file entry
@ -46,24 +47,28 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb,
* Third entry : first file-name entry * Third entry : first file-name entry
* So, the index of first file-name dentry should start from 2. * 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); struct exfat_dentry *ep = exfat_get_dentry_cached(&es, i);
/* end of name entry */ /* end of name entry */
if (exfat_get_entry_type(ep) != TYPE_EXTEND) if (exfat_get_entry_type(ep) != TYPE_EXTEND)
break; 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; uniname += EXFAT_FILE_NAME_LEN;
} }
exfat_free_dentry_set(es, false); exfat_put_dentry_set(&es, false);
return 0;
} }
/* read a directory entry from the opened directory */ /* read a directory entry from the opened directory */
static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_entry *dir_entry) 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, err;
unsigned int type, clu_offset, max_dentries; unsigned int type, clu_offset, max_dentries;
struct exfat_chain dir, clu; struct exfat_chain dir, clu;
struct exfat_uni_name uni_name; struct exfat_uni_name uni_name;
@ -85,11 +90,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); EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
dentries_per_clu = sbi->dentries_per_clu; 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, 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); exfat_chain_dup(&clu, &dir);
if (clu.flags == ALLOC_NO_FAT_CHAIN) { if (clu.flags == ALLOC_NO_FAT_CHAIN) {
@ -103,7 +107,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
clu.dir = ei->hint_bmap.clu; 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))) if (exfat_get_next_cluster(sb, &(clu.dir)))
return -EIO; return -EIO;
@ -149,8 +153,12 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
0); 0);
*uni_name.name = 0x0; *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); uni_name.name);
if (err) {
brelse(bh);
continue;
}
exfat_utf16_to_nls(sb, &uni_name, exfat_utf16_to_nls(sb, &uni_name,
dir_entry->namebuf.lfn, dir_entry->namebuf.lfn,
dir_entry->namebuf.lfnbuf_len); dir_entry->namebuf.lfnbuf_len);
@ -164,7 +172,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
dir_entry->entry = dentry; dir_entry->entry = dentry;
brelse(bh); 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; ei->hint_bmap.clu = clu.dir;
*cpos = EXFAT_DEN_TO_B(dentry + 1 + num_ext); *cpos = EXFAT_DEN_TO_B(dentry + 1 + num_ext);
@ -211,7 +219,10 @@ static void exfat_free_namebuf(struct exfat_dentry_namebuf *nb)
exfat_init_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) #define ITER_POS_FILLED_DOTS (2)
static int exfat_iterate(struct file *filp, struct dir_context *ctx) static int exfat_iterate(struct file *filp, struct dir_context *ctx)
{ {
@ -226,35 +237,33 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx)
int err = 0, fake_offset = 0; int err = 0, fake_offset = 0;
exfat_init_namebuf(nb); exfat_init_namebuf(nb);
mutex_lock(&EXFAT_SB(sb)->s_lock);
cpos = ctx->pos; cpos = ctx->pos;
if (!dir_emit_dots(filp, ctx)) if (!dir_emit_dots(filp, ctx))
goto unlock; goto out;
if (ctx->pos == ITER_POS_FILLED_DOTS) { if (ctx->pos == ITER_POS_FILLED_DOTS) {
cpos = 0; cpos = 0;
fake_offset = 1; fake_offset = 1;
} }
if (cpos & (DENTRY_SIZE - 1)) { cpos = round_up(cpos, DENTRY_SIZE);
err = -ENOENT;
goto unlock;
}
/* name buffer should be allocated before use */ /* name buffer should be allocated before use */
err = exfat_alloc_namebuf(nb); err = exfat_alloc_namebuf(nb);
if (err) if (err)
goto unlock; goto out;
get_new: get_new:
mutex_lock(&EXFAT_SB(sb)->s_lock);
if (ei->flags == ALLOC_NO_FAT_CHAIN && cpos >= i_size_read(inode)) if (ei->flags == ALLOC_NO_FAT_CHAIN && cpos >= i_size_read(inode))
goto end_of_dir; goto end_of_dir;
err = exfat_readdir(inode, &cpos, &de); err = exfat_readdir(inode, &cpos, &de);
if (err) { if (err) {
/* /*
* At least we tried to read a sector. Move cpos to next sector * At least we tried to read a sector.
* position (should be aligned). * Move cpos to next sector position (should be aligned).
*/ */
if (err == -EIO) { if (err == -EIO) {
cpos += 1 << (sb->s_blocksize_bits); cpos += 1 << (sb->s_blocksize_bits);
@ -277,16 +286,10 @@ get_new:
inum = iunique(sb, EXFAT_ROOT_INO); 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); mutex_unlock(&EXFAT_SB(sb)->s_lock);
if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum,
(de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) (de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG))
goto out_unlocked; goto out;
mutex_lock(&EXFAT_SB(sb)->s_lock);
ctx->pos = cpos; ctx->pos = cpos;
goto get_new; goto get_new;
@ -294,9 +297,8 @@ end_of_dir:
if (!cpos && fake_offset) if (!cpos && fake_offset)
cpos = ITER_POS_FILLED_DOTS; cpos = ITER_POS_FILLED_DOTS;
ctx->pos = cpos; ctx->pos = cpos;
unlock:
mutex_unlock(&EXFAT_SB(sb)->s_lock); mutex_unlock(&EXFAT_SB(sb)->s_lock);
out_unlocked: out:
/* /*
* To improve performance, free namebuf after unlock sb_lock. * To improve performance, free namebuf after unlock sb_lock.
* If namebuf is not allocated, this function do nothing * If namebuf is not allocated, this function do nothing
@ -338,7 +340,7 @@ int exfat_calc_num_entries(struct exfat_uni_name *p_uniname)
return -EINVAL; return -EINVAL;
/* 1 file entry + 1 stream entry + name entries */ /* 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) unsigned int exfat_get_entry_type(struct exfat_dentry *ep)
@ -381,6 +383,12 @@ unsigned int exfat_get_entry_type(struct exfat_dentry *ep)
return TYPE_ACL; return TYPE_ACL;
return TYPE_CRITICAL_SEC; 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; return TYPE_BENIGN_SEC;
} }
@ -534,6 +542,25 @@ release_fbh:
return ret; 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 exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
int entry, int num_entries, struct exfat_uni_name *p_uniname) int entry, int num_entries, struct exfat_uni_name *p_uniname)
{ {
@ -566,6 +593,9 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,
if (!ep) if (!ep)
return -EIO; 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_init_name_entry(ep, uniname);
exfat_update_bh(bh, sync); exfat_update_bh(bh, sync);
brelse(bh); brelse(bh);
@ -589,6 +619,9 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir,
if (!ep) if (!ep)
return -EIO; 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_set_entry_type(ep, TYPE_DELETED);
exfat_update_bh(bh, IS_DIRSYNC(inode)); exfat_update_bh(bh, IS_DIRSYNC(inode));
brelse(bh); brelse(bh);
@ -603,18 +636,18 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es)
unsigned short chksum = 0; unsigned short chksum = 0;
struct exfat_dentry *ep; 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); ep = exfat_get_dentry_cached(es, i);
chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum,
chksum_type); chksum_type);
chksum_type = CS_DEFAULT; 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); ep->dentry.file.checksum = cpu_to_le16(chksum);
es->modified = true; 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; int i, err = 0;
@ -626,7 +659,10 @@ int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync)
bforget(es->bh[i]); bforget(es->bh[i]);
else else
brelse(es->bh[i]); brelse(es->bh[i]);
kfree(es);
if (IS_DYNAMIC_ES(es))
kfree(es->bh);
return err; return err;
} }
@ -754,6 +790,7 @@ enum exfat_validate_dentry_mode {
ES_MODE_GET_STRM_ENTRY, ES_MODE_GET_STRM_ENTRY,
ES_MODE_GET_NAME_ENTRY, ES_MODE_GET_NAME_ENTRY,
ES_MODE_GET_CRITICAL_SEC_ENTRY, ES_MODE_GET_CRITICAL_SEC_ENTRY,
ES_MODE_GET_BENIGN_SEC_ENTRY,
}; };
static bool exfat_validate_entry(unsigned int type, static bool exfat_validate_entry(unsigned int type,
@ -767,36 +804,33 @@ static bool exfat_validate_entry(unsigned int type,
if (type != TYPE_FILE && type != TYPE_DIR) if (type != TYPE_FILE && type != TYPE_DIR)
return false; return false;
*mode = ES_MODE_GET_FILE_ENTRY; *mode = ES_MODE_GET_FILE_ENTRY;
return true; break;
case ES_MODE_GET_FILE_ENTRY: case ES_MODE_GET_FILE_ENTRY:
if (type != TYPE_STREAM) if (type != TYPE_STREAM)
return false; return false;
*mode = ES_MODE_GET_STRM_ENTRY; *mode = ES_MODE_GET_STRM_ENTRY;
return true; break;
case ES_MODE_GET_STRM_ENTRY: case ES_MODE_GET_STRM_ENTRY:
if (type != TYPE_EXTEND) if (type != TYPE_EXTEND)
return false; return false;
*mode = ES_MODE_GET_NAME_ENTRY; *mode = ES_MODE_GET_NAME_ENTRY;
return true; break;
case ES_MODE_GET_NAME_ENTRY: 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; return false;
if (type != TYPE_EXTEND) { break;
if (!(type & TYPE_CRITICAL_SEC)) case ES_MODE_GET_BENIGN_SEC_ENTRY:
return false; /* Assume unreconized benign secondary entry */
*mode = ES_MODE_GET_CRITICAL_SEC_ENTRY; if (!(type & TYPE_BENIGN_SEC))
}
return true;
case ES_MODE_GET_CRITICAL_SEC_ENTRY:
if (type == TYPE_EXTEND || type == TYPE_STREAM)
return false; return false;
if ((type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC) break;
return false;
return true;
default: default:
WARN_ON_ONCE(1);
return false; return false;
} }
return true;
} }
struct exfat_dentry *exfat_get_dentry_cached( struct exfat_dentry *exfat_get_dentry_cached(
@ -823,14 +857,14 @@ struct exfat_dentry *exfat_get_dentry_cached(
* pointer of entry set on success, * pointer of entry set on success,
* NULL on failure. * NULL on failure.
*/ */
struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, int exfat_get_dentry_set(struct exfat_entry_set_cache *es,
struct exfat_chain *p_dir, int entry, unsigned int type) struct super_block *sb, struct exfat_chain *p_dir, int entry,
unsigned int type)
{ {
int ret, i, num_bh; int ret, i, num_bh;
unsigned int off, byte_offset, clu = 0; unsigned int off;
sector_t sec; sector_t sec;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_entry_set_cache *es;
struct exfat_dentry *ep; struct exfat_dentry *ep;
int num_entries; int num_entries;
enum exfat_validate_dentry_mode mode = ES_MODE_STARTED; enum exfat_validate_dentry_mode mode = ES_MODE_STARTED;
@ -838,52 +872,51 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb,
if (p_dir->dir == DIR_DELETED) { if (p_dir->dir == DIR_DELETED) {
exfat_err(sb, "access to deleted dentry"); exfat_err(sb, "access to deleted dentry");
return NULL; return -EIO;
} }
byte_offset = EXFAT_DEN_TO_B(entry); ret = exfat_find_location(sb, p_dir, entry, &sec, &off);
ret = exfat_walk_fat_chain(sb, p_dir, byte_offset, &clu);
if (ret) if (ret)
return NULL; return ret;
es = kzalloc(sizeof(*es), GFP_KERNEL); memset(es, 0, sizeof(*es));
if (!es)
return NULL;
es->sb = sb; es->sb = sb;
es->modified = false; 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->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); bh = sb_bread(sb, sec);
if (!bh) if (!bh)
goto free_es; return -EIO;
es->bh[es->num_bh++] = bh; 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)) if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode))
goto free_es; goto put_es;
num_entries = type == ES_ALL_ENTRIES ? num_entries = type == ES_ALL_ENTRIES ?
ep->dentry.file.num_ext + 1 : type; ep->dentry.file.num_ext + 1 : type;
es->num_entries = num_entries; es->num_entries = num_entries;
num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); 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);
return -ENOMEM;
}
es->bh[0] = bh;
}
for (i = 1; i < num_bh; i++) { for (i = 1; i < num_bh; i++) {
/* get the next sector */ /* get the next sector */
if (exfat_is_last_sector_in_cluster(sbi, sec)) { 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) if (p_dir->flags == ALLOC_NO_FAT_CHAIN)
clu++; clu++;
else if (exfat_get_next_cluster(sb, &clu)) else if (exfat_get_next_cluster(sb, &clu))
goto free_es; goto put_es;
sec = exfat_cluster_to_sector(sbi, clu); sec = exfat_cluster_to_sector(sbi, clu);
} else { } else {
sec++; sec++;
@ -891,21 +924,51 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb,
bh = sb_bread(sb, sec); bh = sb_bread(sb, sec);
if (!bh) if (!bh)
goto free_es; goto put_es;
es->bh[es->num_bh++] = bh; es->bh[es->num_bh++] = bh;
} }
/* validate cached dentries */ /* 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); ep = exfat_get_dentry_cached(es, i);
if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode))
goto free_es; goto put_es;
} }
return es; return 0;
free_es: put_es:
exfat_free_dentry_set(es, false); exfat_put_dentry_set(es, false);
return NULL; return -EIO;
}
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, 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;
}
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;
}
} }
enum { enum {
@ -928,17 +991,21 @@ enum {
*/ */
int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, 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, 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 i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len;
int order, step, name_len = 0; int order, step, name_len = 0;
int dentries_per_clu, num_empty = 0; int dentries_per_clu;
unsigned int entry_type; unsigned int entry_type;
unsigned short *uniname = NULL; unsigned short *uniname = NULL;
struct exfat_chain clu; struct exfat_chain clu;
struct exfat_hint *hint_stat = &ei->hint_stat; struct exfat_hint *hint_stat = &ei->hint_stat;
struct exfat_hint_femp candi_empty; struct exfat_hint_femp candi_empty;
struct exfat_sb_info *sbi = EXFAT_SB(sb); 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; dentries_per_clu = sbi->dentries_per_clu;
@ -950,10 +1017,13 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
end_eidx = dentry; end_eidx = dentry;
} }
candi_empty.eidx = EXFAT_HINT_NONE; exfat_reset_empty_hint(&ei->hint_femp);
rewind: rewind:
order = 0; order = 0;
step = DIRENT_STEP_FILE; step = DIRENT_STEP_FILE;
exfat_reset_empty_hint(&candi_empty);
while (clu.dir != EXFAT_EOF_CLUSTER) { while (clu.dir != EXFAT_EOF_CLUSTER) {
i = dentry & (dentries_per_clu - 1); i = dentry & (dentries_per_clu - 1);
for (; i < dentries_per_clu; i++, dentry++) { for (; i < dentries_per_clu; i++, dentry++) {
@ -973,26 +1043,9 @@ rewind:
entry_type == TYPE_DELETED) { entry_type == TYPE_DELETED) {
step = DIRENT_STEP_FILE; step = DIRENT_STEP_FILE;
num_empty++; exfat_set_empty_hint(ei, &candi_empty, &clu,
if (candi_empty.eidx == EXFAT_HINT_NONE && dentry, num_entries,
num_empty == 1) { entry_type);
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;
}
brelse(bh); brelse(bh);
if (entry_type == TYPE_UNUSED) if (entry_type == TYPE_UNUSED)
@ -1000,17 +1053,14 @@ rewind:
continue; continue;
} }
num_empty = 0; exfat_reset_empty_hint(&candi_empty);
candi_empty.eidx = EXFAT_HINT_NONE;
if (entry_type == TYPE_FILE || entry_type == TYPE_DIR) { if (entry_type == TYPE_FILE || entry_type == TYPE_DIR) {
step = DIRENT_STEP_FILE; step = DIRENT_STEP_FILE;
hint_opt->clu = clu.dir; hint_opt->clu = clu.dir;
hint_opt->eidx = i; hint_opt->eidx = i;
if (type == TYPE_ALL || type == entry_type) { num_ext = ep->dentry.file.num_ext;
num_ext = ep->dentry.file.num_ext; step = DIRENT_STEP_STRM;
step = DIRENT_STEP_STRM;
}
brelse(bh); brelse(bh);
continue; continue;
} }
@ -1041,7 +1091,8 @@ rewind:
if (entry_type == TYPE_EXTEND) { if (entry_type == TYPE_EXTEND) {
unsigned short entry_uniname[16], unichar; 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; step = DIRENT_STEP_FILE;
continue; continue;
} }
@ -1101,12 +1152,19 @@ not_found:
rewind = 1; rewind = 1;
dentry = 0; dentry = 0;
clu.dir = p_dir->dir; clu.dir = p_dir->dir;
/* reset empty hint */
num_empty = 0;
candi_empty.eidx = EXFAT_HINT_NONE;
goto rewind; 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 */ /* initialized hint_stat */
hint_stat->clu = p_dir->dir; hint_stat->clu = p_dir->dir;
hint_stat->eidx = 0; hint_stat->eidx = 0;
@ -1154,10 +1212,8 @@ int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir,
type = exfat_get_entry_type(ext_ep); type = exfat_get_entry_type(ext_ep);
brelse(bh); brelse(bh);
if (type == TYPE_EXTEND || type == TYPE_STREAM) if (type & TYPE_CRITICAL_SEC || type & TYPE_BENIGN_SEC)
count++; count++;
else
break;
} }
return count; return count;
} }

View File

@ -10,6 +10,11 @@
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/ratelimit.h> #include <linux/ratelimit.h>
#include <linux/nls.h> #include <linux/nls.h>
#include <linux/blkdev.h>
#ifndef SECTOR_SIZE
#define SECTOR_SIZE 512
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)
#include <linux/magic.h> #include <linux/magic.h>
@ -17,7 +22,7 @@
#define EXFAT_SUPER_MAGIC 0x2011BAB0UL #define EXFAT_SUPER_MAGIC 0x2011BAB0UL
#endif #endif
#define EXFAT_VERSION "5.19.1" #define EXFAT_VERSION "6.0.0"
#define EXFAT_ROOT_INO 1 #define EXFAT_ROOT_INO 1
@ -36,9 +41,9 @@ enum exfat_error_mode {
* exfat nls lossy flag * exfat nls lossy flag
*/ */
enum { enum {
NLS_NAME_NO_LOSSY, /* no lossy */ NLS_NAME_NO_LOSSY = 0, /* no lossy */
NLS_NAME_LOSSY, /* just detected incorrect filename(s) */ NLS_NAME_LOSSY = 1 << 0, /* just detected incorrect filename(s) */
NLS_NAME_OVERLEN, /* the length is over than its limit */ NLS_NAME_OVERLEN = 1 << 1, /* the length is over than its limit */
}; };
#define EXFAT_HASH_BITS 8 #define EXFAT_HASH_BITS 8
@ -50,7 +55,15 @@ enum {
#define ES_2_ENTRIES 2 #define ES_2_ENTRIES 2
#define ES_ALL_ENTRIES 0 #define ES_ALL_ENTRIES 0
#define DIR_DELETED 0xFFFF0321 #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 0xFFFFFFF7
/* type values */ /* type values */
#define TYPE_UNUSED 0x0000 #define TYPE_UNUSED 0x0000
@ -71,15 +84,13 @@ enum {
#define TYPE_PADDING 0x0402 #define TYPE_PADDING 0x0402
#define TYPE_ACLTAB 0x0403 #define TYPE_ACLTAB 0x0403
#define TYPE_BENIGN_SEC 0x0800 #define TYPE_BENIGN_SEC 0x0800
#define TYPE_ALL 0x0FFF #define TYPE_VENDOR_EXT 0x0801
#define TYPE_VENDOR_ALLOC 0x0802
#define MAX_CHARSET_SIZE 6 /* max size of multi-byte character */ #define MAX_CHARSET_SIZE 6 /* max size of multi-byte character */
#define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */
#define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH + 1) * MAX_CHARSET_SIZE) #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_HINT_NONE -1
#define EXFAT_MIN_SUBDIR 2 #define EXFAT_MIN_SUBDIR 2
@ -104,11 +115,17 @@ enum {
/* /*
* helpers for block size to dentry size conversion. * 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_B_TO_DEN(b) ((b) >> DENTRY_SIZE_BITS)
#define EXFAT_DEN_TO_B(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. * helpers for fat entry.
*/ */
@ -134,6 +151,17 @@ enum {
#define BITS_PER_BYTE_MASK 0x7 #define BITS_PER_BYTE_MASK 0x7
#define IGNORED_BITS_REMAINED(clu, clu_base) ((1 << ((clu) - (clu_base))) - 1) #define IGNORED_BITS_REMAINED(clu, clu_base) ((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 { struct exfat_dentry_namebuf {
char *lfn; char *lfn;
int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */
@ -175,13 +203,16 @@ struct exfat_hint {
struct exfat_entry_set_cache { struct exfat_entry_set_cache {
struct super_block *sb; struct super_block *sb;
bool modified;
unsigned int start_off; unsigned int start_off;
int num_bh; 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; unsigned int num_entries;
bool modified;
}; };
#define IS_DYNAMIC_ES(es) ((es)->__bh != (es)->bh)
struct exfat_dir_entry { struct exfat_dir_entry {
struct exfat_chain dir; struct exfat_chain dir;
int entry; int entry;
@ -394,7 +425,7 @@ static inline sector_t exfat_cluster_to_sector(struct exfat_sb_info *sbi,
sbi->data_start_sector; 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) sector_t sec)
{ {
return ((sec - sbi->data_start_sector) >> sbi->sect_per_clus_bits) + return ((sec - sbi->data_start_sector) >> sbi->sect_per_clus_bits) +
@ -442,15 +473,23 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range);
/* file.c */ /* file.c */
extern const struct file_operations exfat_file_operations; 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, loff_t size); void exfat_truncate(struct inode *inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
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, int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
struct iattr *attr); struct iattr *attr);
int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path,
struct kstat *stat, unsigned int request_mask, struct kstat *stat, unsigned int request_mask,
unsigned int query_flags); unsigned int query_flags);
#endif
#else #else
int exfat_setattr(struct dentry *dentry, struct iattr *attr); int exfat_setattr(struct dentry *dentry, struct iattr *attr);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
@ -496,15 +535,16 @@ 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_calc_num_entries(struct exfat_uni_name *p_uniname);
int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, 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, 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); int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu);
struct exfat_dentry *exfat_get_dentry(struct super_block *sb, struct exfat_dentry *exfat_get_dentry(struct super_block *sb,
struct exfat_chain *p_dir, int entry, struct buffer_head **bh); struct exfat_chain *p_dir, int entry, struct buffer_head **bh);
struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es,
int num); int num);
struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, int exfat_get_dentry_set(struct exfat_entry_set_cache *es,
struct exfat_chain *p_dir, int entry, unsigned int type); struct super_block *sb, struct exfat_chain *p_dir, int entry,
int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); unsigned int type);
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); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir);
/* inode.c */ /* inode.c */
@ -515,6 +555,7 @@ struct inode *exfat_build_inode(struct super_block *sb,
void exfat_hash_inode(struct inode *inode, loff_t i_pos); void exfat_hash_inode(struct inode *inode, loff_t i_pos);
void exfat_unhash_inode(struct inode *inode); void exfat_unhash_inode(struct inode *inode);
struct inode *exfat_iget(struct super_block *sb, loff_t i_pos); struct inode *exfat_iget(struct super_block *sb, loff_t i_pos);
int __exfat_write_inode(struct inode *inode, int sync);
int exfat_write_inode(struct inode *inode, struct writeback_control *wbc); int exfat_write_inode(struct inode *inode, struct writeback_control *wbc);
void exfat_evict_inode(struct inode *inode); void exfat_evict_inode(struct inode *inode);
int exfat_block_truncate_page(struct inode *inode, loff_t from); int exfat_block_truncate_page(struct inode *inode, loff_t from);
@ -540,14 +581,16 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...)
#define exfat_fs_error_ratelimit(sb, fmt, args...) \ #define exfat_fs_error_ratelimit(sb, fmt, args...) \
__exfat_fs_error(sb, __ratelimit(&EXFAT_SB(sb)->ratelimit), \ __exfat_fs_error(sb, __ratelimit(&EXFAT_SB(sb)->ratelimit), \
fmt, ## args) fmt, ## args)
void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...)
__printf(3, 4) __cold; /* expand to pr_*() with prefix */
#define exfat_err(sb, fmt, ...) \ #define exfat_err(sb, fmt, ...) \
exfat_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__) pr_err("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
#define exfat_warn(sb, fmt, ...) \ #define exfat_warn(sb, fmt, ...) \
exfat_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__) pr_warn("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
#define exfat_info(sb, fmt, ...) \ #define exfat_info(sb, fmt, ...) \
exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) pr_info("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
#define exfat_debug(sb, fmt, ...) \
pr_debug("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)
void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts,

View File

@ -27,6 +27,7 @@
((sbi)->num_clusters - EXFAT_RESERVED_CLUSTERS) ((sbi)->num_clusters - EXFAT_RESERVED_CLUSTERS)
/* AllocationPossible and NoFatChain field in GeneralSecondaryFlags Field */ /* AllocationPossible and NoFatChain field in GeneralSecondaryFlags Field */
#define ALLOC_POSSIBLE 0x01
#define ALLOC_FAT_CHAIN 0x01 #define ALLOC_FAT_CHAIN 0x01
#define ALLOC_NO_FAT_CHAIN 0x03 #define ALLOC_NO_FAT_CHAIN 0x03
@ -50,6 +51,8 @@
#define EXFAT_STREAM 0xC0 /* stream entry */ #define EXFAT_STREAM 0xC0 /* stream entry */
#define EXFAT_NAME 0xC1 /* file name entry */ #define EXFAT_NAME 0xC1 /* file name entry */
#define EXFAT_ACL 0xC2 /* stream 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_CRITICAL_PRI(x) (x < 0xA0)
#define IS_EXFAT_BENIGN_PRI(x) (x < 0xC0) #define IS_EXFAT_BENIGN_PRI(x) (x < 0xC0)
@ -155,6 +158,24 @@ struct exfat_dentry {
__le32 start_clu; __le32 start_clu;
__le64 size; __le64 size;
} __packed upcase; /* up-case table directory entry */ } __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 dentry;
} __packed; } __packed;

View File

@ -278,8 +278,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu)
struct super_block *sb = dir->i_sb; struct super_block *sb = dir->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct buffer_head *bh; struct buffer_head *bh;
sector_t blknr, last_blknr; sector_t blknr, last_blknr, i;
int i;
blknr = exfat_cluster_to_sector(sbi, clu); blknr = exfat_cluster_to_sector(sbi, clu);
last_blknr = blknr + sbi->sect_per_clus; last_blknr = blknr + sbi->sect_per_clus;
@ -323,7 +322,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
struct exfat_chain *p_chain, bool sync_bmap) struct exfat_chain *p_chain, bool sync_bmap)
{ {
int ret = -ENOSPC; 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; unsigned int hint_clu, new_clu, last_clu = EXFAT_EOF_CLUSTER;
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
@ -346,7 +345,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
/* find new cluster */ /* find new cluster */
if (hint_clu == EXFAT_EOF_CLUSTER) { if (hint_clu == EXFAT_EOF_CLUSTER) {
if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) { if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) {
exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)\n", exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)",
sbi->clu_srch_ptr); sbi->clu_srch_ptr);
sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER;
} }
@ -360,17 +359,11 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
/* check cluster validation */ /* check cluster validation */
if (!is_valid_cluster(sbi, hint_clu)) { if (!is_valid_cluster(sbi, hint_clu)) {
exfat_err(sb, "hint_cluster is invalid (%u)", if (hint_clu != sbi->num_clusters)
hint_clu); exfat_err(sb, "hint_cluster is invalid (%u), rewind to the first cluster",
hint_clu);
hint_clu = EXFAT_FIRST_CLUSTER; hint_clu = EXFAT_FIRST_CLUSTER;
if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { p_chain->flags = ALLOC_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->dir = EXFAT_EOF_CLUSTER; p_chain->dir = EXFAT_EOF_CLUSTER;
@ -380,7 +373,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
if (new_clu != hint_clu && if (new_clu != hint_clu &&
p_chain->flags == ALLOC_NO_FAT_CHAIN) { p_chain->flags == ALLOC_NO_FAT_CHAIN) {
if (exfat_chain_cont_cluster(sb, p_chain->dir, if (exfat_chain_cont_cluster(sb, p_chain->dir,
num_clusters)) { p_chain->size)) {
ret = -EIO; ret = -EIO;
goto free_cluster; goto free_cluster;
} }
@ -393,8 +386,6 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
goto free_cluster; goto free_cluster;
} }
num_clusters++;
/* update FAT table */ /* update FAT table */
if (p_chain->flags == ALLOC_FAT_CHAIN) { if (p_chain->flags == ALLOC_FAT_CHAIN) {
if (exfat_ent_set(sb, new_clu, EXFAT_EOF_CLUSTER)) { if (exfat_ent_set(sb, new_clu, EXFAT_EOF_CLUSTER)) {
@ -411,13 +402,14 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
goto free_cluster; goto free_cluster;
} }
} }
p_chain->size++;
last_clu = new_clu; last_clu = new_clu;
if (--num_alloc == 0) { if (p_chain->size == num_alloc) {
sbi->clu_srch_ptr = hint_clu; 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); mutex_unlock(&sbi->bitmap_lock);
return 0; return 0;
} }
@ -428,7 +420,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (p_chain->flags == ALLOC_NO_FAT_CHAIN) {
if (exfat_chain_cont_cluster(sb, p_chain->dir, if (exfat_chain_cont_cluster(sb, p_chain->dir,
num_clusters)) { p_chain->size)) {
ret = -EIO; ret = -EIO;
goto free_cluster; goto free_cluster;
} }
@ -437,8 +429,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
} }
} }
free_cluster: free_cluster:
if (num_clusters) __exfat_free_cluster(inode, p_chain);
__exfat_free_cluster(inode, p_chain);
unlock: unlock:
mutex_unlock(&sbi->bitmap_lock); mutex_unlock(&sbi->bitmap_lock);
return ret; return ret;

View File

@ -100,7 +100,7 @@ static int exfat_sanitize_mode(const struct exfat_sb_info *sbi,
} }
/* resize the file length */ /* 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 num_clusters_new, num_clusters_phys;
unsigned int last_clu = EXFAT_FREE_CLUSTER; unsigned int last_clu = EXFAT_FREE_CLUSTER;
@ -108,7 +108,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size)
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
int evict = (ei->dir.dir == DIR_DELETED) ? 1 : 0;
/* check if the given file ID is opened */ /* check if the given file ID is opened */
if (ei->type != TYPE_FILE && ei->type != TYPE_DIR) if (ei->type != TYPE_FILE && ei->type != TYPE_DIR)
@ -121,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); 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 * Truncate FAT chain num_clusters after the first cluster
* num_clusters = min(new, phys); * num_clusters = min(new, phys);
@ -151,63 +150,22 @@ int __exfat_truncate(struct inode *inode, loff_t new_size)
ei->start_clu = EXFAT_EOF_CLUSTER; ei->start_clu = EXFAT_EOF_CLUSTER;
} }
i_size_write(inode, new_size);
if (ei->type == TYPE_FILE) if (ei->type == TYPE_FILE)
ei->attr |= ATTR_ARCHIVE; ei->attr |= ATTR_ARCHIVE;
/* update the directory entry */ /*
if (!evict) { * update the directory entry
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) *
struct timespec64 ts; * If the directory entry is updated by mark_inode_dirty(), the
#else * directory entry will be written after a writeback cycle of
struct timespec ts; * updating the bitmap/FAT, which may result in clusters being
#endif * freed but referenced by the directory entry in the event of a
struct exfat_dentry *ep, *ep2; * sudden power failure.
struct exfat_entry_set_cache *es; * __exfat_write_inode() is called for directory entry, bitmap
int err; * and FAT to be written in a same writeback.
*/
es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, if (__exfat_write_inode(inode, inode_needs_sync(inode)))
ES_ALL_ENTRIES); return -EIO;
if (!es)
return -EIO;
ep = exfat_get_dentry_cached(es, 0);
ep2 = exfat_get_dentry_cached(es, 1);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
ts = current_time(inode);
#else
ts = CURRENT_TIME_SEC;
#endif
exfat_set_entry_time(sbi, &ts,
&ep->dentry.file.modify_tz,
&ep->dentry.file.modify_time,
&ep->dentry.file.modify_date,
&ep->dentry.file.modify_time_cs);
ep->dentry.file.attr = cpu_to_le16(ei->attr);
/* File size should be zero if there is no cluster allocated */
if (ei->start_clu == EXFAT_EOF_CLUSTER) {
ep2->dentry.stream.valid_size = 0;
ep2->dentry.stream.size = 0;
} else {
ep2->dentry.stream.valid_size = cpu_to_le64(new_size);
ep2->dentry.stream.size = ep2->dentry.stream.valid_size;
}
if (new_size == 0) {
/* Any directory can not be truncated to zero */
WARN_ON(ei->type != TYPE_FILE);
ep2->dentry.stream.flags = ALLOC_FAT_CHAIN;
ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER;
}
exfat_update_dir_chksum_with_entry_set(es);
err = exfat_free_dentry_set(es, inode_needs_sync(inode));
if (err)
return err;
}
/* cut off from the FAT chain */ /* cut off from the FAT chain */
if (ei->flags == ALLOC_FAT_CHAIN && last_clu != EXFAT_FREE_CLUSTER && if (ei->flags == ALLOC_FAT_CHAIN && last_clu != EXFAT_FREE_CLUSTER &&
@ -236,7 +194,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size)
return 0; 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 super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
@ -258,22 +216,11 @@ void exfat_truncate(struct inode *inode, loff_t size)
goto write_size; goto write_size;
} }
err = __exfat_truncate(inode, i_size_read(inode)); err = __exfat_truncate(inode);
if (err) if (err)
goto write_size; goto write_size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
inode->i_ctime = inode->i_mtime = current_time(inode);
#else
inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
#endif
if (IS_DIRSYNC(inode))
exfat_sync_inode(inode);
else
mark_inode_dirty(inode);
inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) &
~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits;
write_size: write_size:
aligned_size = i_size_read(inode); aligned_size = i_size_read(inode);
if (aligned_size & (blocksize - 1)) { if (aligned_size & (blocksize - 1)) {
@ -290,9 +237,15 @@ write_size:
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #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, int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path,
struct kstat *stat, unsigned int request_mask, struct kstat *stat, unsigned int request_mask,
unsigned int query_flags) unsigned int query_flags)
#endif
#else #else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
int exfat_getattr(const struct path *path, struct kstat *stat, int exfat_getattr(const struct path *path, struct kstat *stat,
@ -311,7 +264,11 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry,
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #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); generic_fillattr(&init_user_ns, inode, stat);
#endif
#else #else
generic_fillattr(inode, stat); generic_fillattr(inode, stat);
#endif #endif
@ -326,8 +283,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(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, int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
struct iattr *attr) struct iattr *attr)
#endif
#else #else
int exfat_setattr(struct dentry *dentry, struct iattr *attr) int exfat_setattr(struct dentry *dentry, struct iattr *attr)
#endif #endif
@ -357,7 +319,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr)
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37))) || \ (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37))) || \
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0))
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 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); error = setattr_prepare(&init_user_ns, dentry, attr);
#endif
#else #else
error = setattr_prepare(dentry, attr); error = setattr_prepare(dentry, attr);
#endif #endif
@ -387,6 +353,24 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr)
attr->ia_valid &= ~ATTR_MODE; attr->ia_valid &= ~ATTR_MODE;
} }
if (attr->ia_valid & ATTR_SIZE)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
inode->i_mtime = inode->i_ctime = current_time(inode);
#else
inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
setattr_copy(&nop_mnt_idmap, inode, attr);
#else
setattr_copy(&init_user_ns, inode, attr);
#endif
#else
setattr_copy(inode, attr);
#endif
exfat_truncate_atime(&inode->i_atime);
if (attr->ia_valid & ATTR_SIZE) { if (attr->ia_valid & ATTR_SIZE) {
error = exfat_block_truncate_page(inode, attr->ia_size); error = exfat_block_truncate_page(inode, attr->ia_size);
if (error) if (error)
@ -394,39 +378,46 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr)
down_write(&EXFAT_I(inode)->truncate_lock); down_write(&EXFAT_I(inode)->truncate_lock);
truncate_setsize(inode, attr->ia_size); truncate_setsize(inode, attr->ia_size);
exfat_truncate(inode, attr->ia_size); /*
* __exfat_write_inode() is called from exfat_truncate(), inode
* is already written by it, so mark_inode_dirty() is unneeded.
*/
exfat_truncate(inode);
up_write(&EXFAT_I(inode)->truncate_lock); up_write(&EXFAT_I(inode)->truncate_lock);
} } else
mark_inode_dirty(inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
setattr_copy(&init_user_ns, inode, attr);
#else
setattr_copy(inode, attr);
#endif
exfat_truncate_atime(&inode->i_atime);
mark_inode_dirty(inode);
out: out:
return error; return error;
} }
static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
{ {
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0)
struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev); struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev);
#endif
struct fstrim_range range; struct fstrim_range range;
int ret = 0; int ret = 0;
if (!capable(CAP_SYS_ADMIN)) if (!capable(CAP_SYS_ADMIN))
return -EPERM; return -EPERM;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
if (!bdev_max_discard_sectors(inode->i_sb->s_bdev))
#else
if (!blk_queue_discard(q)) if (!blk_queue_discard(q))
#endif
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range))) if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range)))
return -EFAULT; return -EFAULT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
range.minlen = max_t(unsigned int, range.minlen,
bdev_discard_granularity(inode->i_sb->s_bdev));
#else
range.minlen = max_t(unsigned int, range.minlen, range.minlen = max_t(unsigned int, range.minlen,
q->limits.discard_granularity); q->limits.discard_granularity);
#endif
ret = exfat_trim_fs(inode, &range); ret = exfat_trim_fs(inode, &range);
if (ret < 0) if (ret < 0)
@ -492,7 +483,11 @@ const struct file_operations exfat_file_operations = {
#endif #endif
.mmap = generic_file_mmap, .mmap = generic_file_mmap,
.fsync = exfat_file_fsync, .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, .splice_read = generic_file_splice_read,
#endif
.splice_write = iter_file_splice_write, .splice_write = iter_file_splice_write,
}; };

View File

@ -19,11 +19,11 @@
#include "exfat_raw.h" #include "exfat_raw.h"
#include "exfat_fs.h" #include "exfat_fs.h"
static int __exfat_write_inode(struct inode *inode, int sync) int __exfat_write_inode(struct inode *inode, int sync)
{ {
unsigned long long on_disk_size; unsigned long long on_disk_size;
struct exfat_dentry *ep, *ep2; 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 super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
@ -44,11 +44,10 @@ static int __exfat_write_inode(struct inode *inode, int sync)
exfat_set_volume_dirty(sb); exfat_set_volume_dirty(sb);
/* get the directory entry of given file or directory */ /* get the directory entry of given file or directory */
es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES); if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES))
if (!es)
return -EIO; return -EIO;
ep = exfat_get_dentry_cached(es, 0); ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
ep2 = exfat_get_dentry_cached(es, 1); ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode));
@ -77,9 +76,16 @@ static int __exfat_write_inode(struct inode *inode, int sync)
ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size); ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size);
ep2->dentry.stream.size = ep2->dentry.stream.valid_size; ep2->dentry.stream.size = ep2->dentry.stream.valid_size;
if (on_disk_size) {
ep2->dentry.stream.flags = ei->flags;
ep2->dentry.stream.start_clu = cpu_to_le32(ei->start_clu);
} else {
ep2->dentry.stream.flags = ALLOC_FAT_CHAIN;
ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER;
}
exfat_update_dir_chksum_with_entry_set(es); 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) int exfat_write_inode(struct inode *inode, struct writeback_control *wbc)
@ -107,7 +113,7 @@ void exfat_sync_inode(struct inode *inode)
static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int *clu, int create) unsigned int *clu, int create)
{ {
int ret, modified = false; int ret;
unsigned int last_clu; unsigned int last_clu;
struct exfat_chain new_clu; struct exfat_chain new_clu;
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
@ -198,7 +204,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
if (new_clu.flags == ALLOC_FAT_CHAIN) if (new_clu.flags == ALLOC_FAT_CHAIN)
ei->flags = ALLOC_FAT_CHAIN; ei->flags = ALLOC_FAT_CHAIN;
ei->start_clu = new_clu.dir; ei->start_clu = new_clu.dir;
modified = true;
} else { } else {
if (new_clu.flags != ei->flags) { if (new_clu.flags != ei->flags) {
/* no-fat-chain bit is disabled, /* no-fat-chain bit is disabled,
@ -208,7 +213,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
exfat_chain_cont_cluster(sb, ei->start_clu, exfat_chain_cont_cluster(sb, ei->start_clu,
num_clusters); num_clusters);
ei->flags = ALLOC_FAT_CHAIN; ei->flags = ALLOC_FAT_CHAIN;
modified = true;
} }
if (new_clu.flags == ALLOC_FAT_CHAIN) if (new_clu.flags == ALLOC_FAT_CHAIN)
if (exfat_ent_set(sb, last_clu, new_clu.dir)) if (exfat_ent_set(sb, last_clu, new_clu.dir))
@ -218,35 +222,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
num_clusters += num_to_be_allocated; num_clusters += num_to_be_allocated;
*clu = new_clu.dir; *clu = new_clu.dir;
if (ei->dir.dir != DIR_DELETED && modified) { inode->i_blocks += EXFAT_CLU_TO_B(num_to_be_allocated, sbi) >> 9;
struct exfat_dentry *ep;
struct exfat_entry_set_cache *es;
int err;
es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry,
ES_ALL_ENTRIES);
if (!es)
return -EIO;
/* get stream entry */
ep = exfat_get_dentry_cached(es, 1);
/* update directory entry */
ep->dentry.stream.flags = ei->flags;
ep->dentry.stream.start_clu =
cpu_to_le32(ei->start_clu);
ep->dentry.stream.valid_size =
cpu_to_le64(i_size_read(inode));
ep->dentry.stream.size =
ep->dentry.stream.valid_size;
exfat_update_dir_chksum_with_entry_set(es);
err = exfat_free_dentry_set(es, inode_needs_sync(inode));
if (err)
return err;
} /* end of if != DIR_DELETED */
inode->i_blocks +=
num_to_be_allocated << sbi->sect_per_clus_bits;
/* /*
* Move *clu pointer along FAT chains (hole care) because the * Move *clu pointer along FAT chains (hole care) because the
@ -359,10 +335,17 @@ unlock_ret:
return err; return err;
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
static int exfat_read_folio(struct file *file, struct folio *folio)
{
return mpage_read_folio(folio, exfat_get_block);
}
#else
static int exfat_readpage(struct file *file, struct page *page) static int exfat_readpage(struct file *file, struct page *page)
{ {
return mpage_readpage(page, exfat_get_block); return mpage_readpage(page, exfat_get_block);
} }
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
static void exfat_readahead(struct readahead_control *rac) static void exfat_readahead(struct readahead_control *rac)
@ -377,10 +360,12 @@ static int exfat_readpages(struct file *file, struct address_space *mapping,
} }
#endif #endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0)
static int exfat_writepage(struct page *page, struct writeback_control *wbc) static int exfat_writepage(struct page *page, struct writeback_control *wbc)
{ {
return block_write_full_page(page, exfat_get_block, wbc); return block_write_full_page(page, exfat_get_block, wbc);
} }
#endif
static int exfat_writepages(struct address_space *mapping, static int exfat_writepages(struct address_space *mapping,
struct writeback_control *wbc) struct writeback_control *wbc)
@ -394,21 +379,37 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to)
if (to > i_size_read(inode)) { if (to > i_size_read(inode)) {
truncate_pagecache(inode, i_size_read(inode)); truncate_pagecache(inode, i_size_read(inode));
exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
inode->i_mtime = inode->i_ctime = current_time(inode);
#else
inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC;
#endif
exfat_truncate(inode);
} }
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
static int exfat_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len,
struct page **pagep, void **fsdata)
#else
static int exfat_write_begin(struct file *file, struct address_space *mapping, static int exfat_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len, unsigned int flags, loff_t pos, unsigned int len, unsigned int flags,
struct page **pagep, void **fsdata) struct page **pagep, void **fsdata)
#endif
{ {
int ret; int ret;
*pagep = NULL; *pagep = NULL;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
ret = cont_write_begin(file, mapping, pos, len, pagep, fsdata,
exfat_get_block,
&EXFAT_I(mapping->host)->i_size_ondisk);
#else
ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata, ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata,
exfat_get_block, exfat_get_block,
&EXFAT_I(mapping->host)->i_size_ondisk); &EXFAT_I(mapping->host)->i_size_ondisk);
#endif
if (ret < 0) if (ret < 0)
exfat_write_failed(mapping, pos+len); exfat_write_failed(mapping, pos+len);
@ -522,18 +523,29 @@ static const struct address_space_operations exfat_aops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0)
.invalidate_folio = block_invalidate_folio, .invalidate_folio = block_invalidate_folio,
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
.read_folio = exfat_read_folio,
#else
.readpage = exfat_readpage, .readpage = exfat_readpage,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
.readahead = exfat_readahead, .readahead = exfat_readahead,
#else #else
.readpages = exfat_readpages, .readpages = exfat_readpages,
#endif #endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0)
.writepage = exfat_writepage, .writepage = exfat_writepage,
#endif
.writepages = exfat_writepages, .writepages = exfat_writepages,
.write_begin = exfat_write_begin, .write_begin = exfat_write_begin,
.write_end = exfat_write_end, .write_end = exfat_write_end,
.direct_IO = exfat_direct_IO, .direct_IO = exfat_direct_IO,
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0)
.bmap = exfat_aop_bmap .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) static inline unsigned long exfat_hash(loff_t i_pos)
@ -611,7 +623,11 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)
#else #else
inode->i_version++; inode->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
inode->i_generation = get_random_u32();
#else
inode->i_generation = prandom_u32(); inode->i_generation = prandom_u32();
#endif
if (info->attr & ATTR_SUBDIR) { /* directory */ if (info->attr & ATTR_SUBDIR) { /* directory */
inode->i_generation &= ~1; inode->i_generation &= ~1;
@ -641,8 +657,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)
exfat_save_attr(inode, info->attr); exfat_save_attr(inode, info->attr);
inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits;
inode->i_mtime = info->mtime; inode->i_mtime = info->mtime;
inode->i_ctime = info->mtime; inode->i_ctime = info->mtime;
ei->i_crtime = info->crtime; ei->i_crtime = info->crtime;
@ -690,7 +705,7 @@ void exfat_evict_inode(struct inode *inode)
if (!inode->i_nlink) { if (!inode->i_nlink) {
i_size_write(inode, 0); i_size_write(inode, 0);
mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); 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); mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
} }

View File

@ -52,23 +52,6 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...)
} }
} }
/*
* exfat_msg() - print preformated EXFAT specific messages.
* All logs except what uses exfat_fs_error() should be written by exfat_msg()
*/
void exfat_msg(struct super_block *sb, const char *level, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
/* level means KERN_ pacility level */
printk("%sexFAT-fs (%s): %pV\n", level, sb->s_id, &vaf);
va_end(args);
}
#define SECS_PER_MIN (60) #define SECS_PER_MIN (60)
#define TIMEZONE_SEC(x) ((x) * 15 * SECS_PER_MIN) #define TIMEZONE_SEC(x) ((x) * 15 * SECS_PER_MIN)

View File

@ -251,11 +251,18 @@ static int exfat_search_empty_slot(struct super_block *sb,
if (hint_femp->eidx != EXFAT_HINT_NONE) { if (hint_femp->eidx != EXFAT_HINT_NONE) {
dentry = hint_femp->eidx; 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); exfat_chain_dup(&clu, &hint_femp->cur);
} else { } else {
exfat_chain_dup(&clu, p_dir); 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; return -ENOSPC;
} }
@ -345,7 +358,6 @@ static int exfat_find_empty_entry(struct inode *inode,
unsigned int ret, last_clu; unsigned int ret, last_clu;
loff_t size = 0; loff_t size = 0;
struct exfat_chain clu; struct exfat_chain clu;
struct exfat_dentry *ep = NULL;
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
@ -397,44 +409,21 @@ static int exfat_find_empty_entry(struct inode *inode,
if (exfat_ent_set(sb, last_clu, clu.dir)) if (exfat_ent_set(sb, last_clu, clu.dir))
return -EIO; return -EIO;
if (hint_femp.eidx == EXFAT_HINT_NONE) { if (hint_femp.cur.dir == EXFAT_EOF_CLUSTER)
/* 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;
exfat_chain_set(&hint_femp.cur, clu.dir, 0, clu.flags); exfat_chain_set(&hint_femp.cur, clu.dir, 0, clu.flags);
}
hint_femp.count += sbi->dentries_per_clu;
hint_femp.cur.size++; hint_femp.cur.size++;
p_dir->size++; p_dir->size++;
size = EXFAT_CLU_TO_B(p_dir->size, sbi); size = EXFAT_CLU_TO_B(p_dir->size, sbi);
/* update the directory entry */
if (p_dir->dir != sbi->root_dir) {
struct buffer_head *bh;
ep = exfat_get_dentry(sb,
&(ei->dir), ei->entry + 1, &bh);
if (!ep)
return -EIO;
ep->dentry.stream.valid_size = cpu_to_le64(size);
ep->dentry.stream.size = ep->dentry.stream.valid_size;
ep->dentry.stream.flags = p_dir->flags;
exfat_update_bh(bh, IS_DIRSYNC(inode));
brelse(bh);
if (exfat_update_dir_chksum(inode, &(ei->dir),
ei->entry))
return -EIO;
}
/* directory inode should be updated in here */ /* directory inode should be updated in here */
i_size_write(inode, size); i_size_write(inode, size);
ei->i_size_ondisk += sbi->cluster_size; ei->i_size_ondisk += sbi->cluster_size;
ei->i_size_aligned += sbi->cluster_size; ei->i_size_aligned += sbi->cluster_size;
ei->flags = p_dir->flags; ei->flags = p_dir->flags;
inode->i_blocks += 1 << sbi->sect_per_clus_bits; inode->i_blocks += sbi->cluster_size >> 9;
} }
return dentry; return dentry;
@ -489,7 +478,7 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
return namelen; /* return error value */ return namelen; /* return error value */
if ((lossy && !lookup) || !namelen) if ((lossy && !lookup) || !namelen)
return -EINVAL; return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL;
exfat_chain_set(p_dir, ei->start_clu, exfat_chain_set(p_dir, ei->start_clu,
EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
@ -590,8 +579,13 @@ out:
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #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, static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl) struct dentry *dentry, umode_t mode, bool excl)
#endif
#else #else
static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode,
bool excl) bool excl)
@ -658,14 +652,14 @@ unlock:
static int exfat_find(struct inode *dir, struct qstr *qname, static int exfat_find(struct inode *dir, struct qstr *qname,
struct exfat_dir_entry *info) struct exfat_dir_entry *info)
{ {
int ret, dentry, num_entries, count; int ret, dentry, count;
struct exfat_chain cdir; struct exfat_chain cdir;
struct exfat_uni_name uni_name; struct exfat_uni_name uni_name;
struct super_block *sb = dir->i_sb; struct super_block *sb = dir->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(dir); struct exfat_inode_info *ei = EXFAT_I(dir);
struct exfat_dentry *ep, *ep2; 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 */ /* for optimized dir & entry to prevent long traverse of cluster chain */
struct exfat_hint hint_opt; struct exfat_hint hint_opt;
@ -677,10 +671,6 @@ static int exfat_find(struct inode *dir, struct qstr *qname,
if (ret) if (ret)
return 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 */ /* check the validation of hint_stat and initialize it if required */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) {
@ -698,9 +688,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname,
} }
/* search the file name for directories */ /* search the file name for directories */
dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, &hint_opt);
num_entries, TYPE_ALL, &hint_opt);
if (dentry < 0) if (dentry < 0)
return dentry; /* -error value */ return dentry; /* -error value */
@ -713,11 +701,10 @@ static int exfat_find(struct inode *dir, struct qstr *qname,
if (cdir.flags & ALLOC_NO_FAT_CHAIN) if (cdir.flags & ALLOC_NO_FAT_CHAIN)
cdir.size -= dentry / sbi->dentries_per_clu; cdir.size -= dentry / sbi->dentries_per_clu;
dentry = hint_opt.eidx; dentry = hint_opt.eidx;
es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES))
if (!es)
return -EIO; return -EIO;
ep = exfat_get_dentry_cached(es, 0); ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
ep2 = exfat_get_dentry_cached(es, 1); ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
info->type = exfat_get_entry_type(ep); info->type = exfat_get_entry_type(ep);
info->attr = le16_to_cpu(ep->dentry.file.attr); info->attr = le16_to_cpu(ep->dentry.file.attr);
@ -746,7 +733,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname,
ep->dentry.file.access_time, ep->dentry.file.access_time,
ep->dentry.file.access_date, ep->dentry.file.access_date,
0); 0);
exfat_free_dentry_set(es, false); exfat_put_dentry_set(&es, false);
if (ei->start_clu == EXFAT_FREE_CLUSTER) { if (ei->start_clu == EXFAT_FREE_CLUSTER) {
exfat_fs_error(sb, exfat_fs_error(sb,
@ -931,8 +918,13 @@ unlock:
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #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, static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode) struct dentry *dentry, umode_t mode)
#endif
#else #else
static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
#endif #endif
@ -1303,7 +1295,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
struct exfat_inode_info *new_ei = NULL; struct exfat_inode_info *new_ei = NULL;
unsigned int new_entry_type = TYPE_UNUSED; unsigned int new_entry_type = TYPE_UNUSED;
int new_entry = 0; int new_entry = 0;
struct buffer_head *old_bh, *new_bh = NULL; struct buffer_head *new_bh = NULL;
/* check the validity of pointer parameters */ /* check the validity of pointer parameters */
if (new_path == NULL || strlen(new_path) == 0) if (new_path == NULL || strlen(new_path) == 0)
@ -1314,16 +1306,11 @@ static int __exfat_rename(struct inode *old_parent_inode,
return -ENOENT; return -ENOENT;
} }
exfat_chain_dup(&olddir, &ei->dir); exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu,
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi),
EXFAT_I(old_parent_inode)->flags);
dentry = ei->entry; 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 */ /* check whether new dir is existing directory and empty */
if (new_inode) { if (new_inode) {
ret = -EIO; ret = -EIO;
@ -1425,10 +1412,17 @@ out:
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #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, static int exfat_rename(struct user_namespace *mnt_userns,
struct inode *old_dir, struct dentry *old_dentry, struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry, struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags) unsigned int flags)
#endif
#else #else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry,

View File

@ -513,7 +513,7 @@ static int exfat_utf8_to_utf16(struct super_block *sb,
} }
if (unilen > MAX_NAME_LENGTH) { if (unilen > MAX_NAME_LENGTH) {
exfat_err(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", exfat_debug(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d",
__func__, len, unilen, MAX_NAME_LENGTH); __func__, len, unilen, MAX_NAME_LENGTH);
return -ENAMETOOLONG; return -ENAMETOOLONG;
} }
@ -679,7 +679,7 @@ static int exfat_load_upcase_table(struct super_block *sb,
bh = sb_bread(sb, sector); bh = sb_bread(sb, sector);
if (!bh) { if (!bh) {
exfat_err(sb, "failed to read sector(0x%llx)\n", exfat_err(sb, "failed to read sector(0x%llx)",
(unsigned long long)sector); (unsigned long long)sector);
ret = -EIO; ret = -EIO;
goto free_table; goto free_table;

View File

@ -547,9 +547,9 @@ static int parse_options(struct super_block *sb, char *options, int silent,
break; break;
default: default:
if (!silent) { if (!silent) {
exfat_msg(sb, KERN_ERR, exfat_err(sb,
"unrecognized mount option \"%s\" or missing value", "unrecognized mount option \"%s\" or missing value",
p); p);
} }
return -EINVAL; return -EINVAL;
} }
@ -559,14 +559,21 @@ out:
if (opts->allow_utime == -1) if (opts->allow_utime == -1)
opts->allow_utime = ~opts->fs_dmask & (0022); opts->allow_utime = ~opts->fs_dmask & (0022);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) {
exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard");
opts->discard = 0;
}
#else
if (opts->discard) { if (opts->discard) {
struct request_queue *q = bdev_get_queue(sb->s_bdev); struct request_queue *q = bdev_get_queue(sb->s_bdev);
if (!blk_queue_discard(q)) if (!blk_queue_discard(q)) {
exfat_msg(sb, KERN_WARNING, exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard");
"mounting with \"discard\" option, but the device does not support discard"); opts->discard = 0;
opts->discard = 0; }
} }
#endif
return 0; return 0;
} }
@ -625,8 +632,7 @@ static int exfat_read_root(struct inode *inode)
inode->i_op = &exfat_dir_inode_operations; inode->i_op = &exfat_dir_inode_operations;
inode->i_fop = &exfat_dir_operations; inode->i_fop = &exfat_dir_operations;
inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits;
ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff;
ei->i_size_aligned = i_size_read(inode); ei->i_size_aligned = i_size_read(inode);
ei->i_size_ondisk = i_size_read(inode); ei->i_size_ondisk = i_size_read(inode);
@ -721,7 +727,7 @@ static int exfat_read_boot_sector(struct super_block *sb)
*/ */
if (p_boot->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS || if (p_boot->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS ||
p_boot->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) { p_boot->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) {
exfat_err(sb, "bogus sector size bits : %u\n", exfat_err(sb, "bogus sector size bits : %u",
p_boot->sect_size_bits); p_boot->sect_size_bits);
return -EINVAL; return -EINVAL;
} }
@ -730,7 +736,7 @@ static int exfat_read_boot_sector(struct super_block *sb)
* sect_per_clus_bits could be at least 0 and at most 25 - sect_size_bits. * sect_per_clus_bits could be at least 0 and at most 25 - sect_size_bits.
*/ */
if (p_boot->sect_per_clus_bits > EXFAT_MAX_SECT_PER_CLUS_BITS(p_boot)) { if (p_boot->sect_per_clus_bits > EXFAT_MAX_SECT_PER_CLUS_BITS(p_boot)) {
exfat_err(sb, "bogus sectors bits per cluster : %u\n", exfat_err(sb, "bogus sectors bits per cluster : %u",
p_boot->sect_per_clus_bits); p_boot->sect_per_clus_bits);
return -EINVAL; return -EINVAL;
} }
@ -896,6 +902,12 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent)
if (opts->allow_utime == (unsigned short)-1) if (opts->allow_utime == (unsigned short)-1)
opts->allow_utime = ~opts->fs_dmask & 0022; opts->allow_utime = ~opts->fs_dmask & 0022;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) {
exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard");
opts->discard = 0;
}
#else
if (opts->discard) { if (opts->discard) {
struct request_queue *q = bdev_get_queue(sb->s_bdev); struct request_queue *q = bdev_get_queue(sb->s_bdev);
@ -904,6 +916,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent)
opts->discard = 0; opts->discard = 0;
} }
} }
#endif
#else #else
struct exfat_sb_info *sbi; struct exfat_sb_info *sbi;
@ -924,7 +937,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent)
DEFAULT_RATELIMIT_BURST); DEFAULT_RATELIMIT_BURST);
err = parse_options(sb, data, silent, &sbi->options); err = parse_options(sb, data, silent, &sbi->options);
if (err) { if (err) {
exfat_msg(sb, KERN_ERR, "failed to parse options"); exfat_err(sb, "failed to parse options");
goto check_nls_io; goto check_nls_io;
} }
#endif #endif