diff --git a/fs/exfat/.travis_cmd_wrapper.pl b/fs/exfat/.travis_cmd_wrapper.pl new file mode 100755 index 000000000000..0cbaea440eb9 --- /dev/null +++ b/fs/exfat/.travis_cmd_wrapper.pl @@ -0,0 +1,65 @@ +#!/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/fs/exfat/.travis_get_mainline_kernel b/fs/exfat/.travis_get_mainline_kernel new file mode 100755 index 000000000000..3356d15da772 --- /dev/null +++ b/fs/exfat/.travis_get_mainline_kernel @@ -0,0 +1,37 @@ +#!/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 diff --git a/fs/exfat/Kconfig b/fs/exfat/Kconfig index 5a65071b5ecf..2d3636dc5b8c 100644 --- a/fs/exfat/Kconfig +++ b/fs/exfat/Kconfig @@ -16,7 +16,6 @@ config EXFAT_DEFAULT_IOCHARSET depends on EXFAT_FS help Set this to the default input/output character set to use for - converting between the encoding that is used for user visible - filenames and the UTF-16 character encoding that the exFAT - filesystem uses. This can be overridden with the "iocharset" mount - option for the exFAT filesystems. + converting between the encoding is used for user visible filename and + UTF-16 character that exfat filesystem use, and can be overridden with + the "iocharset" mount option for exFAT filesystems. diff --git a/fs/exfat/Makefile b/fs/exfat/Makefile index ed51926a4971..b1acf144466a 100644 --- a/fs/exfat/Makefile +++ b/fs/exfat/Makefile @@ -2,7 +2,39 @@ # # Makefile for the linux exFAT filesystem support. # +ifneq ($(KERNELRELEASE),) obj-$(CONFIG_EXFAT_FS) += exfat.o exfat-y := inode.o namei.o dir.o super.o fatent.o cache.o nls.o misc.o \ file.o balloc.o +else +# Called from external kernel module build + +KERNELRELEASE ?= $(shell uname -r) +KDIR ?= /lib/modules/${KERNELRELEASE}/build +MDIR ?= /lib/modules/${KERNELRELEASE} +PWD := $(shell pwd) + +export CONFIG_EXFAT_FS := m + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean + +help: + $(MAKE) -C $(KDIR) M=$(PWD) help + +install: exfat.ko + rm -f ${MDIR}/kernel/fs/exfat/exfat.ko + install -m644 -b -D exfat.ko ${MDIR}/kernel/fs/exfat/exfat.ko + depmod -aq + +uninstall: + rm -rf ${MDIR}/kernel/fs/exfat + depmod -aq + +endif + +.PHONY : all clean install uninstall diff --git a/fs/exfat/README.md b/fs/exfat/README.md new file mode 100644 index 000000000000..cee3699ed9be --- /dev/null +++ b/fs/exfat/README.md @@ -0,0 +1,53 @@ +## exFAT filesystem +This is the exfat filesystem for support from the linux 4.1 kernel +to the latest kernel. + +## Installing as a stand-alone module + +Install prerequisite package for Fedora, RHEL: +``` + yum install kernel-devel-$(uname -r) +``` + +Build step: +``` + make + sudo make install +``` + +To load the driver manually, run this as root: +``` + modprobe exfat +``` + + +## Installing as a part of the kernel + +1. Let's take [linux] as the path to your kernel source dir. +``` + cd [linux] + cp -ar exfat [linux]/fs/ +``` + +2. edit [linux]/fs/Kconfig +``` + source "fs/fat/Kconfig" + +source "fs/exfat/Kconfig" + source "fs/ntfs/Kconfig" +``` + +3. edit [linux]/fs/Makefile +``` + obj-$(CONFIG_FAT_FS) += fat/ + +obj-$(CONFIG_EXFAT_FS) += exfat/ + obj-$(CONFIG_BFS_FS) += bfs/ +``` +4. make menuconfig and set exfat +``` + File systems ---> + DOS/FAT/NT Filesystems ---> + exFAT filesystem support + (utf8) Default iocharset for exFAT +``` + +build your kernel diff --git a/fs/exfat/balloc.c b/fs/exfat/balloc.c index 777d8f65c8cf..30c2414d859c 100644 --- a/fs/exfat/balloc.c +++ b/fs/exfat/balloc.c @@ -3,9 +3,15 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include #include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include +#else +#include +#endif #include "exfat_raw.h" #include "exfat_fs.h" @@ -105,7 +111,7 @@ int exfat_load_bitmap(struct super_block *sb) struct exfat_dentry *ep; struct buffer_head *bh; - ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); + ep = exfat_get_dentry(sb, &clu, i, &bh); if (!ep) return -EIO; @@ -141,32 +147,26 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) kfree(sbi->vol_amap); } -/* - * If the value of "clu" is 0, it means cluster 2 which is the first cluster of - * the cluster heap. - */ -int exfat_set_bitmap(struct inode *inode, unsigned int clu) +int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync) { int i, b; unsigned int ent_idx; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); - WARN_ON(clu < EXFAT_FIRST_CLUSTER); + if (!is_valid_cluster(sbi, clu)) + return -EINVAL; + ent_idx = CLUSTER_TO_BITMAP_ENT(clu); i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); set_bit_le(b, sbi->vol_amap[i]->b_data); - exfat_update_bh(sbi->vol_amap[i], IS_DIRSYNC(inode)); + exfat_update_bh(sbi->vol_amap[i], sync); return 0; } -/* - * If the value of "clu" is 0, it means cluster 2 which is the first cluster of - * the cluster heap. - */ -void exfat_clear_bitmap(struct inode *inode, unsigned int clu) +void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync) { int i, b; unsigned int ent_idx; @@ -174,20 +174,21 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu) struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_mount_options *opts = &sbi->options; - WARN_ON(clu < EXFAT_FIRST_CLUSTER); + if (!is_valid_cluster(sbi, clu)) + return; + ent_idx = CLUSTER_TO_BITMAP_ENT(clu); i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); clear_bit_le(b, sbi->vol_amap[i]->b_data); - exfat_update_bh(sbi->vol_amap[i], IS_DIRSYNC(inode)); + exfat_update_bh(sbi->vol_amap[i], sync); if (opts->discard) { int ret_discard; ret_discard = sb_issue_discard(sb, - exfat_cluster_to_sector(sbi, clu + - EXFAT_RESERVED_CLUSTERS), + exfat_cluster_to_sector(sbi, clu), (1 << sbi->sect_per_clus_bits), GFP_NOFS, 0); if (ret_discard == -EOPNOTSUPP) { @@ -272,4 +273,84 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count) *ret_count = count; return 0; -} \ No newline at end of file +} + +int exfat_trim_fs(struct inode *inode, struct fstrim_range *range) +{ + unsigned int trim_begin, trim_end, count, next_free_clu; + u64 clu_start, clu_end, trim_minlen, trimmed_total = 0; + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + int err = 0; + + clu_start = max_t(u64, range->start >> sbi->cluster_size_bits, + EXFAT_FIRST_CLUSTER); + clu_end = clu_start + (range->len >> sbi->cluster_size_bits) - 1; + trim_minlen = range->minlen >> sbi->cluster_size_bits; + + if (clu_start >= sbi->num_clusters || range->len < sbi->cluster_size) + return -EINVAL; + + if (clu_end >= sbi->num_clusters) + clu_end = sbi->num_clusters - 1; + + mutex_lock(&sbi->bitmap_lock); + + trim_begin = trim_end = exfat_find_free_bitmap(sb, clu_start); + if (trim_begin == EXFAT_EOF_CLUSTER) + goto unlock; + + next_free_clu = exfat_find_free_bitmap(sb, trim_end + 1); + if (next_free_clu == EXFAT_EOF_CLUSTER) + goto unlock; + + do { + if (next_free_clu == trim_end + 1) { + /* extend trim range for continuous free cluster */ + trim_end++; + } else { + /* trim current range if it's larger than trim_minlen */ + count = trim_end - trim_begin + 1; + if (count >= trim_minlen) { + err = sb_issue_discard(sb, + exfat_cluster_to_sector(sbi, trim_begin), + count * sbi->sect_per_clus, GFP_NOFS, 0); + if (err) + goto unlock; + + trimmed_total += count; + } + + /* set next start point of the free hole */ + trim_begin = trim_end = next_free_clu; + } + + if (next_free_clu >= clu_end) + break; + + if (fatal_signal_pending(current)) { + err = -ERESTARTSYS; + goto unlock; + } + + next_free_clu = exfat_find_free_bitmap(sb, next_free_clu + 1); + } while (next_free_clu != EXFAT_EOF_CLUSTER && + next_free_clu > trim_end); + + /* try to trim remainder */ + count = trim_end - trim_begin + 1; + if (count >= trim_minlen) { + err = sb_issue_discard(sb, exfat_cluster_to_sector(sbi, trim_begin), + count * sbi->sect_per_clus, GFP_NOFS, 0); + if (err) + goto unlock; + + trimmed_total += count; + } + +unlock: + mutex_unlock(&sbi->bitmap_lock); + range->len = trimmed_total << sbi->cluster_size_bits; + + return err; +} diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index 242306194bd0..5a2f119b7e8c 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -311,4 +311,4 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, exfat_cache_add(inode, &cid); return 0; -} \ No newline at end of file +} diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index 0043a315401c..07765a74251a 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -3,7 +3,9 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include #include +#include #include #include @@ -62,8 +64,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, 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; - unsigned int type, clu_offset; - sector_t sector; + unsigned int type, clu_offset, max_dentries; struct exfat_chain dir, clu; struct exfat_uni_name uni_name; struct exfat_dentry *ep; @@ -85,6 +86,8 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent 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); clu_offset = dentry >> dentries_per_clu_bits; exfat_chain_dup(&clu, &dir); @@ -108,11 +111,11 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent } } - while (clu.dir != EXFAT_EOF_CLUSTER) { + while (clu.dir != EXFAT_EOF_CLUSTER && dentry < max_dentries) { i = dentry & (dentries_per_clu - 1); for ( ; i < dentries_per_clu; i++, dentry++) { - ep = exfat_get_dentry(sb, &clu, i, &bh, §or); + ep = exfat_get_dentry(sb, &clu, i, &bh); if (!ep) return -EIO; @@ -146,14 +149,14 @@ 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, &dir, dentry, + exfat_get_uniname_from_ext_entry(sb, &clu, i, uni_name.name); exfat_utf16_to_nls(sb, &uni_name, dir_entry->namebuf.lfn, dir_entry->namebuf.lfnbuf_len); brelse(bh); - ep = exfat_get_dentry(sb, &clu, i + 1, &bh, NULL); + ep = exfat_get_dentry(sb, &clu, i + 1, &bh); if (!ep) return -EIO; dir_entry->size = @@ -244,7 +247,7 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) if (err) goto unlock; get_new: - if (cpos >= i_size_read(inode)) + if (ei->flags == ALLOC_NO_FAT_CHAIN && cpos >= i_size_read(inode)) goto end_of_dir; err = exfat_readdir(inode, &cpos, &de); @@ -306,6 +309,10 @@ const struct file_operations exfat_dir_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate = exfat_iterate, + .unlocked_ioctl = exfat_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = exfat_compat_ioctl, +#endif .fsync = exfat_file_fsync, }; @@ -315,7 +322,7 @@ int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu) exfat_chain_set(clu, EXFAT_EOF_CLUSTER, 0, ALLOC_NO_FAT_CHAIN); - ret = exfat_alloc_cluster(inode, 1, clu); + ret = exfat_alloc_cluster(inode, 1, clu, IS_DIRSYNC(inode)); if (ret) return ret; @@ -437,16 +444,25 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, { struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct timespec64 ts = current_time(inode); - sector_t sector; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + struct timespec64 ts; +#else + struct timespec ts; +#endif struct exfat_dentry *ep; struct buffer_head *bh; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + ts = current_time(inode); +#else + ts = CURRENT_TIME_SEC; +#endif + /* * We cannot use exfat_get_dentry_set here because file ep is not * initialized yet. */ - ep = exfat_get_dentry(sb, p_dir, entry, &bh, §or); + ep = exfat_get_dentry(sb, p_dir, entry, &bh); if (!ep) return -EIO; @@ -470,7 +486,7 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, exfat_update_bh(bh, IS_DIRSYNC(inode)); brelse(bh); - ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh, §or); + ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh); if (!ep) return -EIO; @@ -489,12 +505,11 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, struct super_block *sb = inode->i_sb; int ret = 0; int i, num_entries; - sector_t sector; u16 chksum; struct exfat_dentry *ep, *fep; struct buffer_head *fbh, *bh; - fep = exfat_get_dentry(sb, p_dir, entry, &fbh, §or); + fep = exfat_get_dentry(sb, p_dir, entry, &fbh); if (!fep) return -EIO; @@ -502,7 +517,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, chksum = exfat_calc_chksum16(fep, DENTRY_SIZE, 0, CS_DIR_ENTRY); for (i = 1; i < num_entries; i++) { - ep = exfat_get_dentry(sb, p_dir, entry + i, &bh, NULL); + ep = exfat_get_dentry(sb, p_dir, entry + i, &bh); if (!ep) { ret = -EIO; goto release_fbh; @@ -524,13 +539,12 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, { struct super_block *sb = inode->i_sb; int i; - sector_t sector; unsigned short *uniname = p_uniname->name; struct exfat_dentry *ep; struct buffer_head *bh; int sync = IS_DIRSYNC(inode); - ep = exfat_get_dentry(sb, p_dir, entry, &bh, §or); + ep = exfat_get_dentry(sb, p_dir, entry, &bh); if (!ep) return -EIO; @@ -538,7 +552,7 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, exfat_update_bh(bh, sync); brelse(bh); - ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh, §or); + ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh); if (!ep) return -EIO; @@ -548,7 +562,7 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, brelse(bh); for (i = EXFAT_FIRST_CLUSTER; i < num_entries; i++) { - ep = exfat_get_dentry(sb, p_dir, entry + i, &bh, §or); + ep = exfat_get_dentry(sb, p_dir, entry + i, &bh); if (!ep) return -EIO; @@ -567,12 +581,11 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, { struct super_block *sb = inode->i_sb; int i; - sector_t sector; struct exfat_dentry *ep; struct buffer_head *bh; for (i = order; i < num_entries; i++) { - ep = exfat_get_dentry(sb, p_dir, entry + i, &bh, §or); + ep = exfat_get_dentry(sb, p_dir, entry + i, &bh); if (!ep) return -EIO; @@ -649,8 +662,8 @@ static int exfat_walk_fat_chain(struct super_block *sb, return 0; } -int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir, - int entry, sector_t *sector, int *offset) +static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir, + int entry, sector_t *sector, int *offset) { int ret; unsigned int off, clu = 0; @@ -710,8 +723,7 @@ static int exfat_dir_readahead(struct super_block *sb, sector_t sec) } struct exfat_dentry *exfat_get_dentry(struct super_block *sb, - struct exfat_chain *p_dir, int entry, struct buffer_head **bh, - sector_t *sector) + struct exfat_chain *p_dir, int entry, struct buffer_head **bh) { unsigned int dentries_per_page = EXFAT_B_TO_DEN(PAGE_SIZE); int off; @@ -733,8 +745,6 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb, if (!*bh) return NULL; - if (sector) - *sector = sec; return (struct exfat_dentry *)((*bh)->b_data + off); } @@ -885,7 +895,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, es->bh[es->num_bh++] = bh; } - /* validiate cached dentries */ + /* validate cached dentries */ for (i = 1; i < num_entries; i++) { ep = exfat_get_dentry_cached(es, i); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) @@ -906,14 +916,19 @@ enum { }; /* - * return values: - * >= 0 : return dir entiry position with the name in dir - * -ENOENT : entry with the name does not exist - * -EIO : I/O error + * @ei: inode info of parent directory + * @p_dir: directory structure of parent directory + * @num_entries:entry size of p_uniname + * @hint_opt: If p_uniname is found, filled with optimized dir/entry + * for traversing cluster chain. + * @return: + * >= 0: file directory entry position where the name exists + * -ENOENT: entry with the name does not exist + * -EIO: I/O error */ 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) + int num_entries, unsigned int type, struct exfat_hint *hint_opt) { int i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; int order, step, name_len = 0; @@ -948,7 +963,7 @@ rewind: if (rewind && dentry == end_eidx) goto not_found; - ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); + ep = exfat_get_dentry(sb, &clu, i, &bh); if (!ep) return -EIO; @@ -990,6 +1005,8 @@ rewind: if (entry_type == TYPE_FILE || entry_type == TYPE_DIR) { 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; @@ -1131,7 +1148,7 @@ int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir, struct buffer_head *bh; for (i = 0, entry++; i < ep->dentry.file.num_ext; i++, entry++) { - ext_ep = exfat_get_dentry(sb, p_dir, entry, &bh, NULL); + ext_ep = exfat_get_dentry(sb, p_dir, entry, &bh); if (!ext_ep) return -EIO; @@ -1161,7 +1178,7 @@ int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir) while (clu.dir != EXFAT_EOF_CLUSTER) { for (i = 0; i < dentries_per_clu; i++) { - ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); + ep = exfat_get_dentry(sb, &clu, i, &bh); if (!ep) return -EIO; entry_type = exfat_get_entry_type(ep); @@ -1186,4 +1203,4 @@ int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir) } return count; -} \ No newline at end of file +} diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 37ea726bcf8a..6b88270feaed 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -6,11 +6,19 @@ #ifndef _EXFAT_FS_H #define _EXFAT_FS_H +#include #include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) +#include +#else #define EXFAT_SUPER_MAGIC 0x2011BAB0UL +#endif + +#define EXFAT_VERSION "5.19.1" + #define EXFAT_ROOT_INO 1 #define EXFAT_CLUSTERS_UNTRACKED (~0u) @@ -183,9 +191,15 @@ struct exfat_dir_entry { unsigned short attr; loff_t size; unsigned int num_subdirs; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) struct timespec64 atime; struct timespec64 mtime; struct timespec64 crtime; +#else + struct timespec atime; + struct timespec mtime; + struct timespec crtime; +#endif struct exfat_dentry_namebuf namebuf; }; @@ -204,7 +218,9 @@ struct exfat_mount_options { /* on error: continue, panic, remount-ro */ enum exfat_error_mode errors; unsigned utf8:1, /* Use of UTF-8 character set */ - discard:1; /* Issue discard requests on deletions */ + sys_tz:1, /* Use local timezone */ + discard:1, /* Issue discard requests on deletions */ + keep_last_dots:1; /* Keep trailing periods in paths */ int time_offset; /* Offset of timestamps from UTC (in minutes) */ }; @@ -238,6 +254,7 @@ struct exfat_sb_info { unsigned int used_clusters; /* number of used clusters */ struct mutex s_lock; /* superblock lock */ + struct mutex bitmap_lock; /* bitmap lock */ struct exfat_mount_options options; struct nls_table *nls_io; /* Charset used for input and display */ struct ratelimit_state ratelimit; @@ -294,7 +311,11 @@ struct exfat_inode_info { struct rw_semaphore truncate_lock; struct inode vfs_inode; /* File creation time */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) struct timespec64 i_crtime; +#else + struct timespec i_crtime; +#endif }; static inline struct exfat_sb_info *EXFAT_SB(struct super_block *sb) @@ -380,6 +401,12 @@ static inline int exfat_sector_to_cluster(struct exfat_sb_info *sbi, EXFAT_RESERVED_CLUSTERS; } +static inline bool is_valid_cluster(struct exfat_sb_info *sbi, + unsigned int clus) +{ + return clus >= EXFAT_FIRST_CLUSTER && clus < sbi->num_clusters; +} + /* super.c */ int exfat_set_volume_dirty(struct super_block *sb); int exfat_clear_volume_dirty(struct super_block *sb); @@ -388,7 +415,7 @@ int exfat_clear_volume_dirty(struct super_block *sb); #define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu) int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, - struct exfat_chain *p_chain); + struct exfat_chain *p_chain, bool sync_bmap); int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain); int exfat_ent_get(struct super_block *sb, unsigned int loc, unsigned int *content); @@ -407,19 +434,38 @@ int exfat_count_num_clusters(struct super_block *sb, /* balloc.c */ int exfat_load_bitmap(struct super_block *sb); void exfat_free_bitmap(struct exfat_sb_info *sbi); -int exfat_set_bitmap(struct inode *inode, unsigned int clu); -void exfat_clear_bitmap(struct inode *inode, unsigned int clu); +int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync); +void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync); unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu); int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count); +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); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr); +int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags); +#else int exfat_setattr(struct dentry *dentry, struct iattr *attr); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) int exfat_getattr(const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); +#else +int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *stat); +#endif +#endif int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); +long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); +long exfat_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg); + /* namei.c */ extern const struct dentry_operations exfat_dentry_ops; @@ -450,13 +496,10 @@ 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); + int num_entries, unsigned int type, struct exfat_hint *hint_opt); int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu); -int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir, - int entry, sector_t *sector, int *offset); struct exfat_dentry *exfat_get_dentry(struct super_block *sb, - struct exfat_chain *p_dir, int entry, struct buffer_head **bh, - sector_t *sector); + 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, @@ -506,11 +549,19 @@ void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) #define exfat_info(sb, fmt, ...) \ exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_cs); void exfat_truncate_atime(struct timespec64 *ts); void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); +#else +void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, + u8 tz, __le16 time, __le16 date, u8 time_cs); +void exfat_truncate_atime(struct timespec *ts); +void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, + u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); +#endif u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type); u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type); void exfat_update_bh(struct buffer_head *bh, int sync); @@ -519,4 +570,4 @@ void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, unsigned int size, unsigned char flags); void exfat_chain_dup(struct exfat_chain *dup, struct exfat_chain *ec); -#endif /* !_EXFAT_FS_H */ \ No newline at end of file +#endif /* !_EXFAT_FS_H */ diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h index 3528e8bfba57..7f39b1c6469c 100644 --- a/fs/exfat/exfat_raw.h +++ b/fs/exfat/exfat_raw.h @@ -77,6 +77,10 @@ #define EXFAT_FILE_NAME_LEN 15 +#define EXFAT_MIN_SECT_SIZE_BITS 9 +#define EXFAT_MAX_SECT_SIZE_BITS 12 +#define EXFAT_MAX_SECT_PER_CLUS_BITS(x) (25 - (x)->sect_size_bits) + /* EXFAT: Main and Backup Boot Sector (512 bytes) */ struct boot_sector { __u8 jmp_boot[BOOTSEC_JUMP_BOOT_LEN]; @@ -161,4 +165,4 @@ struct exfat_dentry { /* Dec 31 GMT 23:59:59 2107 */ #define EXFAT_MAX_TIMESTAMP_SECS 4354819199LL -#endif /* !_EXFAT_RAW_H */ \ No newline at end of file +#endif /* !_EXFAT_RAW_H */ diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index 8a68c984a8a9..b247cbdf987d 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -26,7 +27,11 @@ static int exfat_mirror_bh(struct super_block *sb, sector_t sec, memcpy(c_bh->b_data, bh->b_data, sb->s_blocksize); set_buffer_uptodate(c_bh); mark_buffer_dirty(c_bh); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) if (sb->s_flags & SB_SYNCHRONOUS) +#else + if (sb->s_flags & MS_SYNCHRONOUS) +#endif err = sync_dirty_buffer(c_bh); brelse(c_bh); } @@ -75,20 +80,16 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, fat_entry = (__le32 *)&(bh->b_data[off]); *fat_entry = cpu_to_le32(content); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); +#else + exfat_update_bh(bh, sb->s_flags & MS_SYNCHRONOUS); +#endif exfat_mirror_bh(sb, sec, bh); brelse(bh); return 0; } -static inline bool is_valid_cluster(struct exfat_sb_info *sbi, - unsigned int clus) -{ - if (clus < EXFAT_FIRST_CLUSTER || sbi->num_clusters <= clus) - return false; - return true; -} - int exfat_ent_get(struct super_block *sb, unsigned int loc, unsigned int *content) { @@ -151,12 +152,14 @@ int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, return 0; } -int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) +/* This function must be called with bitmap_lock held */ +static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) { - unsigned int num_clusters = 0; - unsigned int clu; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); + int cur_cmap_i, next_cmap_i; + unsigned int num_clusters = 0; + unsigned int clu; /* invalid cluster number */ if (p_chain->dir == EXFAT_FREE_CLUSTER || @@ -176,21 +179,51 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) clu = p_chain->dir; - if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { - do { - exfat_clear_bitmap(inode, clu); - clu++; + cur_cmap_i = next_cmap_i = + BITMAP_OFFSET_SECTOR_INDEX(sb, CLUSTER_TO_BITMAP_ENT(clu)); + if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { + unsigned int last_cluster = p_chain->dir + p_chain->size - 1; + do { + bool sync = false; + + if (clu < last_cluster) + next_cmap_i = + BITMAP_OFFSET_SECTOR_INDEX(sb, CLUSTER_TO_BITMAP_ENT(clu+1)); + + /* flush bitmap only if index would be changed or for last cluster */ + if (clu == last_cluster || cur_cmap_i != next_cmap_i) { + sync = true; + cur_cmap_i = next_cmap_i; + } + + exfat_clear_bitmap(inode, clu, (sync && IS_DIRSYNC(inode))); + clu++; num_clusters++; } while (num_clusters < p_chain->size); } else { do { - exfat_clear_bitmap(inode, clu); + bool sync = false; + unsigned int n_clu = clu; + int err = exfat_get_next_cluster(sb, &n_clu); - if (exfat_get_next_cluster(sb, &clu)) - goto dec_used_clus; + if (err || n_clu == EXFAT_EOF_CLUSTER) + sync = true; + else + next_cmap_i = + BITMAP_OFFSET_SECTOR_INDEX(sb, CLUSTER_TO_BITMAP_ENT(n_clu)); + if (cur_cmap_i != next_cmap_i) { + sync = true; + cur_cmap_i = next_cmap_i; + } + + exfat_clear_bitmap(inode, clu, (sync && IS_DIRSYNC(inode))); + clu = n_clu; num_clusters++; + + if (err) + goto dec_used_clus; } while (clu != EXFAT_EOF_CLUSTER); } @@ -199,6 +232,17 @@ dec_used_clus: return 0; } +int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) +{ + int ret = 0; + + mutex_lock(&EXFAT_SB(inode->i_sb)->bitmap_lock); + ret = __exfat_free_cluster(inode, p_chain); + mutex_unlock(&EXFAT_SB(inode->i_sb)->bitmap_lock); + + return ret; +} + int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, unsigned int *ret_clu) { @@ -233,10 +277,9 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) { struct super_block *sb = dir->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct buffer_head *bhs[MAX_BUF_PER_PAGE]; - int nr_bhs = MAX_BUF_PER_PAGE; + struct buffer_head *bh; sector_t blknr, last_blknr; - int err, i, n; + int i; blknr = exfat_cluster_to_sector(sbi, clu); last_blknr = blknr + sbi->sect_per_clus; @@ -250,34 +293,34 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) } /* Zeroing the unused blocks on this cluster */ - while (blknr < last_blknr) { - for (n = 0; n < nr_bhs && blknr < last_blknr; n++, blknr++) { - bhs[n] = sb_getblk(sb, blknr); - if (!bhs[n]) { - err = -ENOMEM; - goto release_bhs; - } - memset(bhs[n]->b_data, 0, sb->s_blocksize); - } + for (i = blknr; i < last_blknr; i++) { + bh = sb_getblk(sb, i); + if (!bh) + return -ENOMEM; - err = exfat_update_bhs(bhs, n, IS_DIRSYNC(dir)); - if (err) - goto release_bhs; - - for (i = 0; i < n; i++) - brelse(bhs[i]); + memset(bh->b_data, 0, sb->s_blocksize); + set_buffer_uptodate(bh); + mark_buffer_dirty(bh); + brelse(bh); } - return 0; -release_bhs: - exfat_err(sb, "failed zeroed sect %llu\n", (unsigned long long)blknr); - for (i = 0; i < n; i++) - bforget(bhs[i]); - return err; + if (IS_DIRSYNC(dir)) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + return sync_blockdev_range(sb->s_bdev, + EXFAT_BLK_TO_B(blknr, sb), + EXFAT_BLK_TO_B(last_blknr, sb) - 1); +#else + return filemap_write_and_wait_range(sb->s_bdev->bd_inode->i_mapping, + EXFAT_BLK_TO_B(blknr, sb), + EXFAT_BLK_TO_B(last_blknr, sb) - 1); +#endif + + + return 0; } int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, - struct exfat_chain *p_chain) + struct exfat_chain *p_chain, bool sync_bmap) { int ret = -ENOSPC; unsigned int num_clusters = 0, total_cnt; @@ -297,6 +340,8 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, if (num_alloc > total_cnt - sbi->used_clusters) return -ENOSPC; + mutex_lock(&sbi->bitmap_lock); + hint_clu = p_chain->dir; /* find new cluster */ if (hint_clu == EXFAT_EOF_CLUSTER) { @@ -307,8 +352,10 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } hint_clu = exfat_find_free_bitmap(sb, sbi->clu_srch_ptr); - if (hint_clu == EXFAT_EOF_CLUSTER) - return -ENOSPC; + if (hint_clu == EXFAT_EOF_CLUSTER) { + ret = -ENOSPC; + goto unlock; + } } /* check cluster validation */ @@ -318,8 +365,10 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, hint_clu = EXFAT_FIRST_CLUSTER; if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) - return -EIO; + num_clusters)) { + ret = -EIO; + goto unlock; + } p_chain->flags = ALLOC_FAT_CHAIN; } } @@ -339,7 +388,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } /* update allocation bitmap */ - if (exfat_set_bitmap(inode, new_clu)) { + if (exfat_set_bitmap(inode, new_clu, sync_bmap)) { ret = -EIO; goto free_cluster; } @@ -369,6 +418,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, sbi->used_clusters += num_clusters; p_chain->size += num_clusters; + mutex_unlock(&sbi->bitmap_lock); return 0; } @@ -388,7 +438,9 @@ 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; } @@ -421,4 +473,4 @@ int exfat_count_num_clusters(struct super_block *sb, *ret_count = count; return 0; -} \ No newline at end of file +} diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 96b55de65aac..ebea67c61da9 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -3,9 +3,13 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include #include -#include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include +#endif +#include #include #include "exfat_raw.h" @@ -21,7 +25,11 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) if (err) return err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_ctime = inode->i_mtime = current_time(inode); +#else + inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; +#endif mark_inode_dirty(inode); if (!IS_SYNC(inode)) @@ -109,8 +117,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) exfat_set_volume_dirty(sb); num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi); - num_clusters_phys = - EXFAT_B_TO_CLU_ROUND_UP(EXFAT_I(inode)->i_size_ondisk, sbi); + num_clusters_phys = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi); exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags); @@ -151,7 +158,11 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) /* update the directory entry */ if (!evict) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) struct timespec64 ts; +#else + struct timespec ts; +#endif struct exfat_dentry *ep, *ep2; struct exfat_entry_set_cache *es; int err; @@ -163,7 +174,11 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) 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, @@ -218,8 +233,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) if (exfat_free_cluster(inode, &clu)) return -EIO; - exfat_clear_volume_dirty(sb); - return 0; } @@ -227,12 +240,17 @@ void exfat_truncate(struct inode *inode, loff_t size) { struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *ei = EXFAT_I(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 72) unsigned int blocksize = i_blocksize(inode); +#else + unsigned int blocksize = 1 << inode->i_blkbits; +#endif loff_t aligned_size; int err; mutex_lock(&sbi->s_lock); - if (EXFAT_I(inode)->start_clu == 0) { + if (ei->start_clu == 0) { /* * Empty start_clu != ~0 (not allocated) */ @@ -244,14 +262,18 @@ void exfat_truncate(struct inode *inode, loff_t size) if (err) goto write_size; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_ctime = inode->i_mtime = current_time(inode); +#else + inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; +#endif if (IS_DIRSYNC(inode)) exfat_sync_inode(inode); else mark_inode_dirty(inode); inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~(sbi->cluster_size - 1)) >> inode->i_blkbits; + ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; write_size: aligned_size = i_size_read(inode); if (aligned_size & (blocksize - 1)) { @@ -259,30 +281,56 @@ write_size: aligned_size++; } - if (EXFAT_I(inode)->i_size_ondisk > i_size_read(inode)) - EXFAT_I(inode)->i_size_ondisk = aligned_size; + if (ei->i_size_ondisk > i_size_read(inode)) + ei->i_size_ondisk = aligned_size; - if (EXFAT_I(inode)->i_size_aligned > i_size_read(inode)) - EXFAT_I(inode)->i_size_aligned = aligned_size; + if (ei->i_size_aligned > i_size_read(inode)) + ei->i_size_aligned = aligned_size; mutex_unlock(&sbi->s_lock); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags) +#else +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) int exfat_getattr(const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags) +#else +int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *stat) +#endif +#endif { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) struct inode *inode = d_backing_inode(path->dentry); struct exfat_inode_info *ei = EXFAT_I(inode); +#else + struct inode *inode = d_inode(dentry); +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + generic_fillattr(&init_user_ns, inode, stat); +#else generic_fillattr(inode, stat); +#endif exfat_truncate_atime(&stat->atime); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) stat->result_mask |= STATX_BTIME; stat->btime.tv_sec = ei->i_crtime.tv_sec; stat->btime.tv_nsec = ei->i_crtime.tv_nsec; +#endif stat->blksize = EXFAT_SB(inode->i_sb)->cluster_size; return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr) +#else int exfat_setattr(struct dentry *dentry, struct iattr *attr) +#endif { struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb); struct inode *inode = dentry->d_inode; @@ -305,7 +353,17 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) ATTR_TIMES_SET); } +#if ((LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)) && \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37))) || \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + error = setattr_prepare(&init_user_ns, dentry, attr); +#else error = setattr_prepare(dentry, attr); +#endif +#else + error = inode_change_ok(inode, attr); +#endif attr->ia_valid = ia_valid; if (error) goto out; @@ -340,7 +398,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) up_write(&EXFAT_I(inode)->truncate_lock); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + setattr_copy(&init_user_ns, inode, attr); +#else setattr_copy(inode, attr); +#endif exfat_truncate_atime(&inode->i_atime); mark_inode_dirty(inode); @@ -348,6 +410,54 @@ out: return error; } +static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) +{ + struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev); + struct fstrim_range range; + int ret = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!blk_queue_discard(q)) + return -EOPNOTSUPP; + + if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range))) + return -EFAULT; + + range.minlen = max_t(unsigned int, range.minlen, + q->limits.discard_granularity); + + ret = exfat_trim_fs(inode, &range); + if (ret < 0) + return ret; + + if (copy_to_user((struct fstrim_range __user *)arg, &range, sizeof(range))) + return -EFAULT; + + return 0; +} + +long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + + switch (cmd) { + case FITRIM: + return exfat_ioctl_fitrim(inode, arg); + default: + return -ENOTTY; + } +} + +#ifdef CONFIG_COMPAT +long exfat_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + return exfat_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) { struct inode *inode = filp->f_mapping->host; @@ -361,13 +471,25 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) if (err) return err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + return blkdev_issue_flush(inode->i_sb->s_bdev); +#else +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL); +#else return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL); +#endif +#endif } const struct file_operations exfat_file_operations = { .llseek = generic_file_llseek, .read_iter = generic_file_read_iter, .write_iter = generic_file_write_iter, + .unlocked_ioctl = exfat_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = exfat_compat_ioctl, +#endif .mmap = generic_file_mmap, .fsync = exfat_file_fsync, .splice_read = generic_file_splice_read, @@ -377,4 +499,4 @@ const struct file_operations exfat_file_operations = { const struct inode_operations exfat_file_inode_operations = { .setattr = exfat_setattr, .getattr = exfat_getattr, -}; \ No newline at end of file +}; diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 7fe77ed0ad8b..bd04c160848f 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -3,6 +3,7 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include #include #include #include @@ -12,8 +13,9 @@ #include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #include - +#endif #include "exfat_raw.h" #include "exfat_fs.h" @@ -31,7 +33,7 @@ static int __exfat_write_inode(struct inode *inode, int sync) return 0; /* - * If the indode is already unlinked, there is no need for updating it. + * If the inode is already unlinked, there is no need for updating it. */ if (ei->dir.dir == DIR_DELETED) return 0; @@ -114,10 +116,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, unsigned int local_clu_offset = clu_offset; unsigned int num_to_be_allocated = 0, num_clusters = 0; - if (EXFAT_I(inode)->i_size_ondisk > 0) + if (ei->i_size_ondisk > 0) num_clusters = - EXFAT_B_TO_CLU_ROUND_UP(EXFAT_I(inode)->i_size_ondisk, - sbi); + EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi); if (clu_offset >= num_clusters) num_to_be_allocated = clu_offset - num_clusters + 1; @@ -179,7 +180,8 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, return -EIO; } - ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu); + ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu, + inode_needs_sync(inode)); if (ret) return ret; @@ -362,11 +364,18 @@ static int exfat_readpage(struct file *file, struct page *page) return mpage_readpage(page, exfat_get_block); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) +static void exfat_readahead(struct readahead_control *rac) +{ + mpage_readahead(rac, exfat_get_block); +} +#else static int exfat_readpages(struct file *file, struct address_space *mapping, struct list_head *pages, unsigned int nr_pages) { return mpage_readpages(mapping, pages, nr_pages, exfat_get_block); } +#endif static int exfat_writepage(struct page *page, struct writeback_control *wbc) { @@ -416,10 +425,10 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, err = generic_write_end(file, mapping, pos, len, copied, pagep, fsdata); - if (EXFAT_I(inode)->i_size_aligned < i_size_read(inode)) { + if (ei->i_size_aligned < i_size_read(inode)) { exfat_fs_error(inode->i_sb, "invalid size(size(%llu) > aligned(%llu)\n", - i_size_read(inode), EXFAT_I(inode)->i_size_aligned); + i_size_read(inode), ei->i_size_aligned); return -EIO; } @@ -427,7 +436,11 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, exfat_write_failed(mapping, pos+len); if (!(err < 0) && !(ei->attr & 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; mark_inode_dirty(inode); } @@ -435,7 +448,13 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, return err; } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) +#else +static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter, + loff_t offset) +#endif { struct address_space *mapping = iocb->ki_filp->f_mapping; struct inode *inode = mapping->host; @@ -461,7 +480,11 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) * Need to use the DIO_LOCKING for avoiding the race * condition of exfat_get_block() and ->truncate(). */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) ret = blockdev_direct_IO(iocb, inode, iter, exfat_get_block); +#else + ret = blockdev_direct_IO(iocb, inode, iter, offset, exfat_get_block); +#endif if (ret < 0 && (rw & WRITE)) exfat_write_failed(mapping, size); return ret; @@ -491,8 +514,20 @@ int exfat_block_truncate_page(struct inode *inode, loff_t from) } static const struct address_space_operations exfat_aops = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + .dirty_folio = block_dirty_folio, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) + .set_page_dirty = __set_page_dirty_buffers, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + .invalidate_folio = block_invalidate_folio, +#endif .readpage = exfat_readpage, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + .readahead = exfat_readahead, +#else .readpages = exfat_readpages, +#endif .writepage = exfat_writepage, .writepages = exfat_writepages, .write_begin = exfat_write_begin, @@ -571,7 +606,11 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) inode->i_uid = sbi->options.fs_uid; inode->i_gid = sbi->options.fs_gid; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); +#else + inode->i_version++; +#endif inode->i_generation = prandom_u32(); if (info->attr & ATTR_SUBDIR) { /* directory */ @@ -603,7 +642,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) exfat_save_attr(inode, info->attr); inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~(sbi->cluster_size - 1)) >> inode->i_blkbits; + ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; inode->i_mtime = info->mtime; inode->i_ctime = info->mtime; ei->i_crtime = info->crtime; @@ -627,7 +666,11 @@ struct inode *exfat_build_inode(struct super_block *sb, goto out; } inode->i_ino = iunique(sb, EXFAT_ROOT_INO); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_set_iversion(inode, 1); +#else + inode->i_version = 1; +#endif err = exfat_fill_inode(inode, info); if (err) { iput(inode); @@ -655,4 +698,4 @@ void exfat_evict_inode(struct inode *inode) clear_inode(inode); exfat_cache_inval_inode(inode); exfat_unhash_inode(inode); -} \ No newline at end of file +} diff --git a/fs/exfat/misc.c b/fs/exfat/misc.c index 3bc39716186d..1b75c8b97f7d 100644 --- a/fs/exfat/misc.c +++ b/fs/exfat/misc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -39,8 +40,14 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) if (opts->errors == EXFAT_ERRORS_PANIC) { panic("exFAT-fs (%s): fs panic from previous error\n", sb->s_id); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) } else if (opts->errors == EXFAT_ERRORS_RO && !sb_rdonly(sb)) { sb->s_flags |= SB_RDONLY; +#else + } else if (opts->errors == EXFAT_ERRORS_RO && + !(sb->s_flags & MS_RDONLY)) { + sb->s_flags |= MS_RDONLY; +#endif exfat_err(sb, "Filesystem has been set read-only"); } } @@ -65,7 +72,11 @@ void exfat_msg(struct super_block *sb, const char *level, const char *fmt, ...) #define SECS_PER_MIN (60) #define TIMEZONE_SEC(x) ((x) * 15 * SECS_PER_MIN) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) static void exfat_adjust_tz(struct timespec64 *ts, u8 tz_off) +#else +static void exfat_adjust_tz(struct timespec *ts, u8 tz_off) +#endif { if (tz_off <= 0x3F) ts->tv_sec -= TIMEZONE_SEC(tz_off); @@ -73,9 +84,21 @@ static void exfat_adjust_tz(struct timespec64 *ts, u8 tz_off) ts->tv_sec += TIMEZONE_SEC(0x80 - tz_off); } +static inline int exfat_tz_offset(struct exfat_sb_info *sbi) +{ + if (sbi->options.sys_tz) + return -sys_tz.tz_minuteswest; + return sbi->options.time_offset; +} + /* Convert a EXFAT time/date pair to a UNIX date (seconds since 1 1 70). */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_cs) +#else +void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, + u8 tz, __le16 time, __le16 date, u8 time_cs) +#endif { u16 t = le16_to_cpu(time); u16 d = le16_to_cpu(date); @@ -95,18 +118,39 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, /* Adjust timezone to UTC0. */ exfat_adjust_tz(ts, tz & ~EXFAT_TZ_VALID); else - /* Convert from local time to UTC using time_offset. */ - ts->tv_sec -= sbi->options.time_offset * SECS_PER_MIN; + ts->tv_sec -= exfat_tz_offset(sbi) * SECS_PER_MIN; } /* Convert linear UNIX date to a EXFAT time/date pair. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs) +#else +#undef EXFAT_MAX_TIMESTAMP_SECS +#define EXFAT_MAX_TIMESTAMP_SECS 0xffffffff +void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, + u8 *tz, __le16 *time, __le16 *date, u8 *time_cs) +#endif { struct tm tm; u16 t, d; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) + if (ts->tv_sec < EXFAT_MIN_TIMESTAMP_SECS) { + ts->tv_sec = EXFAT_MIN_TIMESTAMP_SECS; + ts->tv_nsec = 0; + } + else if (ts->tv_sec > EXFAT_MAX_TIMESTAMP_SECS) { + ts->tv_sec = EXFAT_MAX_TIMESTAMP_SECS; + ts->tv_nsec = 0; + } +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) time64_to_tm(ts->tv_sec, 0, &tm); +#else + time_to_tm(ts->tv_sec, 0, &tm); +#endif t = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); d = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; @@ -125,12 +169,12 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, *tz = EXFAT_TZ_VALID; } -/* - * The timestamp for access_time has double seconds granularity. - * (There is no 10msIncrement field for access_time unlike create/modify_time) - * atime also has only a 2-second resolution. - */ +/* atime has only a 2-second resolution */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_truncate_atime(struct timespec64 *ts) +#else +void exfat_truncate_atime(struct timespec *ts) +#endif { ts->tv_sec = round_down(ts->tv_sec, 2); ts->tv_nsec = 0; @@ -180,12 +224,16 @@ int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync) set_buffer_uptodate(bhs[i]); mark_buffer_dirty(bhs[i]); if (sync) - write_dirty_buffer(bhs[i], 0); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + write_dirty_buffer(bhs[i], REQ_SYNC); +#else + write_dirty_buffer(bhs[i], WRITE_SYNC); +#endif } for (i = 0; i < nr_bhs && sync; i++) { wait_on_buffer(bhs[i]); - if (!buffer_uptodate(bhs[i])) + if (!err && !buffer_uptodate(bhs[i])) err = -EIO; } return err; @@ -202,4 +250,4 @@ void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, void exfat_chain_dup(struct exfat_chain *dup, struct exfat_chain *ec) { return exfat_chain_set(dup, ec->dir, ec->size, ec->flags); -} \ No newline at end of file +} diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index adf962a67f4a..cae4ec4ac4d0 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -3,7 +3,10 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #include +#endif #include #include #include @@ -33,7 +36,7 @@ static inline void exfat_d_version_set(struct dentry *dentry, */ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags) { - int ret; + int ret = 1; if (flags & LOOKUP_RCU) return -ECHILD; @@ -59,17 +62,25 @@ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags) return 0; spin_lock(&dentry->d_lock); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) ret = inode_eq_iversion(d_inode(dentry->d_parent), exfat_d_version(dentry)); +#else + if (dentry->d_parent->d_inode->i_version != exfat_d_version(dentry)) + ret = 0; +#endif spin_unlock(&dentry->d_lock); return ret; } -/* returns the length of a struct qstr, ignoring trailing dots */ -static unsigned int exfat_striptail_len(unsigned int len, const char *name) +/* returns the length of a struct qstr, ignoring trailing dots if necessary */ +static unsigned int exfat_striptail_len(unsigned int len, const char *name, + bool keep_last_dots) { - while (len && name[len - 1] == '.') - len--; + if (!keep_last_dots) { + while (len && name[len - 1] == '.') + len--; + } return len; } @@ -83,8 +94,13 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr) struct super_block *sb = dentry->d_sb; struct nls_table *t = EXFAT_SB(sb)->nls_io; const unsigned char *name = qstr->name; - unsigned int len = exfat_striptail_len(qstr->len, qstr->name); + unsigned int len = exfat_striptail_len(qstr->len, qstr->name, + EXFAT_SB(sb)->options.keep_last_dots); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) unsigned long hash = init_name_hash(dentry); +#else + unsigned long hash = init_name_hash(); +#endif int i, charlen; wchar_t c; @@ -99,13 +115,20 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr) return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) static int exfat_d_cmp(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name) +#else +static int exfat_d_cmp(const struct dentry *parent, const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +#endif { struct super_block *sb = dentry->d_sb; struct nls_table *t = EXFAT_SB(sb)->nls_io; - unsigned int alen = exfat_striptail_len(name->len, name->name); - unsigned int blen = exfat_striptail_len(len, str); + unsigned int alen = exfat_striptail_len(name->len, name->name, + EXFAT_SB(sb)->options.keep_last_dots); + unsigned int blen = exfat_striptail_len(len, str, + EXFAT_SB(sb)->options.keep_last_dots); wchar_t c1, c2; int charlen, i; @@ -136,8 +159,13 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr) { struct super_block *sb = dentry->d_sb; const unsigned char *name = qstr->name; - unsigned int len = exfat_striptail_len(qstr->len, qstr->name); + unsigned int len = exfat_striptail_len(qstr->len, qstr->name, + EXFAT_SB(sb)->options.keep_last_dots); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) unsigned long hash = init_name_hash(dentry); +#else + unsigned long hash = init_name_hash(); +#endif int i, charlen; unicode_t u; @@ -157,12 +185,21 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr) return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name) +#else +static int exfat_utf8_d_cmp(const struct dentry *parent, + const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name) +#endif { struct super_block *sb = dentry->d_sb; - unsigned int alen = exfat_striptail_len(name->len, name->name); - unsigned int blen = exfat_striptail_len(len, str); + unsigned int alen = exfat_striptail_len(name->len, name->name, + EXFAT_SB(sb)->options.keep_last_dots); + unsigned int blen = exfat_striptail_len(len, str, + EXFAT_SB(sb)->options.keep_last_dots); + unicode_t u_a, u_b; int charlen, i; @@ -229,7 +266,7 @@ static int exfat_search_empty_slot(struct super_block *sb, i = dentry & (dentries_per_clu - 1); for (; i < dentries_per_clu; i++, dentry++) { - ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); + ep = exfat_get_dentry(sb, &clu, i, &bh); if (!ep) return -EIO; type = exfat_get_entry_type(ep); @@ -306,7 +343,6 @@ static int exfat_find_empty_entry(struct inode *inode, { int dentry; unsigned int ret, last_clu; - sector_t sector; loff_t size = 0; struct exfat_chain clu; struct exfat_dentry *ep = NULL; @@ -340,7 +376,7 @@ static int exfat_find_empty_entry(struct inode *inode, exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags); /* allocate a cluster */ - ret = exfat_alloc_cluster(inode, 1, &clu); + ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode)); if (ret) return ret; @@ -379,7 +415,7 @@ static int exfat_find_empty_entry(struct inode *inode, struct buffer_head *bh; ep = exfat_get_dentry(sb, - &(ei->dir), ei->entry + 1, &bh, §or); + &(ei->dir), ei->entry + 1, &bh); if (!ep) return -EIO; @@ -395,9 +431,9 @@ static int exfat_find_empty_entry(struct inode *inode, /* directory inode should be updated in here */ i_size_write(inode, size); - EXFAT_I(inode)->i_size_ondisk += sbi->cluster_size; - EXFAT_I(inode)->i_size_aligned += sbi->cluster_size; - EXFAT_I(inode)->flags = p_dir->flags; + 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; } @@ -417,13 +453,25 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); + int pathlen = strlen(path); - /* strip all trailing periods */ - namelen = exfat_striptail_len(strlen(path), path); + /* + * get the length of the pathname excluding + * trailing periods, if any. + */ + namelen = exfat_striptail_len(pathlen, path, false); + if (EXFAT_SB(sb)->options.keep_last_dots) { + /* + * Do not allow the creation of files with names + * ending with period(s). + */ + if (!lookup && (namelen < pathlen)) + return -EINVAL; + namelen = pathlen; + } if (!namelen) return -ENOENT; - - if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE)) + if (pathlen > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE)) return -ENAMETOOLONG; /* @@ -541,8 +589,13 @@ out: return ret; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +#else static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) +#endif { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -555,12 +608,19 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, exfat_set_volume_dirty(sb); err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE, &info); - exfat_clear_volume_dirty(sb); if (err) goto unlock; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); +#else + dir->i_version++; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) dir->i_ctime = dir->i_mtime = current_time(dir); +#else + dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; +#endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else @@ -572,9 +632,19 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, if (err) goto unlock; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); +#else + inode->i_version++; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); +#else + inode->i_mtime = inode->i_atime = inode->i_ctime = + EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; +#endif exfat_truncate_atime(&inode->i_atime); /* timestamp is already written, so mark_inode_dirty() is unneeded. */ @@ -596,6 +666,8 @@ static int exfat_find(struct inode *dir, struct qstr *qname, struct exfat_inode_info *ei = EXFAT_I(dir); struct exfat_dentry *ep, *ep2; struct exfat_entry_set_cache *es; + /* for optimized dir & entry to prevent long traverse of cluster chain */ + struct exfat_hint hint_opt; if (qname->len == 0) return -ENOENT; @@ -610,16 +682,24 @@ static int exfat_find(struct inode *dir, struct qstr *qname, 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)) { +#else + if (ei->version != (dir->i_version & 0xffffffff)) { +#endif ei->hint_stat.clu = cdir.dir; ei->hint_stat.eidx = 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) ei->version = (inode_peek_iversion_raw(dir) & 0xffffffff); +#else + ei->version = (dir->i_version & 0xffffffff); +#endif ei->hint_femp.eidx = EXFAT_HINT_NONE; } /* search the file name for directories */ dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, - num_entries, TYPE_ALL); + num_entries, TYPE_ALL, &hint_opt); if (dentry < 0) return dentry; /* -error value */ @@ -628,6 +708,11 @@ static int exfat_find(struct inode *dir, struct qstr *qname, info->entry = dentry; info->num_subdirs = 0; + /* adjust cdir to the optimized value */ + cdir.dir = hint_opt.clu; + if (cdir.flags & ALLOC_NO_FAT_CHAIN) + cdir.size -= dentry / sbi->dentries_per_clu; + dentry = hint_opt.eidx; es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); if (!es) return -EIO; @@ -755,7 +840,11 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, out: mutex_unlock(&EXFAT_SB(sb)->s_lock); if (!inode) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); +#else + exfat_d_version_set(dentry, dir->i_version); +#endif return d_splice_alias(inode, dentry); unlock: @@ -772,7 +861,6 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) struct inode *inode = dentry->d_inode; struct exfat_inode_info *ei = EXFAT_I(inode); struct buffer_head *bh; - sector_t sector; int num_entries, entry, err = 0; mutex_lock(&EXFAT_SB(sb)->s_lock); @@ -784,7 +872,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) goto unlock; } - ep = exfat_get_dentry(sb, &cdir, entry, &bh, §or); + ep = exfat_get_dentry(sb, &cdir, entry, &bh); if (!ep) { err = -EIO; goto unlock; @@ -807,10 +895,17 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) /* This doesn't modify ei */ ei->dir.dir = DIR_DELETED; - exfat_clear_volume_dirty(sb); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); +#else + dir->i_version++; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) dir->i_mtime = dir->i_atime = current_time(dir); +#else + dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; +#endif exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); @@ -818,16 +913,29 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) mark_inode_dirty(dir); clear_nlink(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_atime = current_time(inode); +#else + inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; +#endif exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); +#else + exfat_d_version_set(dentry, dir->i_version); +#endif unlock: mutex_unlock(&EXFAT_SB(sb)->s_lock); return err; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, umode_t mode) +#else static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +#endif { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -840,12 +948,20 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) exfat_set_volume_dirty(sb); err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR, &info); - exfat_clear_volume_dirty(sb); if (err) goto unlock; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); +#else + dir->i_version++; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) dir->i_ctime = dir->i_mtime = current_time(dir); +#else + dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; +#endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else @@ -858,9 +974,18 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) if (err) goto unlock; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); +#else + inode->i_version++; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); +#else + inode->i_mtime = inode->i_atime = inode->i_ctime = + EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; +#endif exfat_truncate_atime(&inode->i_atime); /* timestamp is already written, so mark_inode_dirty() is unneeded. */ @@ -887,7 +1012,7 @@ static int exfat_check_dir_empty(struct super_block *sb, while (clu.dir != EXFAT_EOF_CLUSTER) { for (i = 0; i < dentries_per_clu; i++) { - ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); + ep = exfat_get_dentry(sb, &clu, i, &bh); if (!ep) return -EIO; type = exfat_get_entry_type(ep); @@ -924,7 +1049,6 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); struct buffer_head *bh; - sector_t sector; int num_entries, entry, err; mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); @@ -949,7 +1073,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) goto unlock; } - ep = exfat_get_dentry(sb, &cdir, entry, &bh, §or); + ep = exfat_get_dentry(sb, &cdir, entry, &bh); if (!ep) { err = -EIO; goto unlock; @@ -971,10 +1095,17 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) goto unlock; } ei->dir.dir = DIR_DELETED; - exfat_clear_volume_dirty(sb); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(dir); +#else + dir->i_version++; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) dir->i_mtime = dir->i_atime = current_time(dir); +#else + dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; +#endif exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); @@ -983,10 +1114,18 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) drop_nlink(dir); clear_nlink(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_atime = current_time(inode); +#else + inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; +#endif exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); +#else + exfat_d_version_set(dentry, dir->i_version); +#endif unlock: mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); return err; @@ -997,13 +1136,12 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, struct exfat_inode_info *ei) { int ret, num_old_entries, num_new_entries; - sector_t sector_old, sector_new; struct exfat_dentry *epold, *epnew; struct super_block *sb = inode->i_sb; struct buffer_head *new_bh, *old_bh; int sync = IS_DIRSYNC(inode); - epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh, §or_old); + epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh); if (!epold) return -EIO; @@ -1024,8 +1162,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, if (newentry < 0) return newentry; /* -EIO or -ENOSPC */ - epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh, - §or_new); + epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh); if (!epnew) return -EIO; @@ -1038,12 +1175,10 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, brelse(old_bh); brelse(new_bh); - epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh, - §or_old); + epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh); if (!epold) return -EIO; - epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh, - §or_new); + epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh); if (!epnew) { brelse(old_bh); return -EIO; @@ -1061,6 +1196,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, exfat_remove_entries(inode, p_dir, oldentry, 0, num_old_entries); + ei->dir = *p_dir; ei->entry = newentry; } else { if (exfat_get_entry_type(epold) == TYPE_FILE) { @@ -1085,12 +1221,11 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) { int ret, newentry, num_new_entries, num_old_entries; - sector_t sector_mov, sector_new; struct exfat_dentry *epmov, *epnew; struct super_block *sb = inode->i_sb; struct buffer_head *mov_bh, *new_bh; - epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh, §or_mov); + epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh); if (!epmov) return -EIO; @@ -1108,7 +1243,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, if (newentry < 0) return newentry; /* -EIO or -ENOSPC */ - epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh, §or_new); + epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh); if (!epnew) return -EIO; @@ -1121,12 +1256,10 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, brelse(mov_bh); brelse(new_bh); - epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh, - §or_mov); + epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh); if (!epmov) return -EIO; - epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh, - §or_new); + epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh); if (!epnew) { brelse(mov_bh); return -EIO; @@ -1151,28 +1284,6 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, return 0; } -static void exfat_update_parent_info(struct exfat_inode_info *ei, - struct inode *parent_inode) -{ - struct exfat_sb_info *sbi = EXFAT_SB(parent_inode->i_sb); - struct exfat_inode_info *parent_ei = EXFAT_I(parent_inode); - loff_t parent_isize = i_size_read(parent_inode); - - /* - * the problem that struct exfat_inode_info caches wrong parent info. - * - * because of flag-mismatch of ei->dir, - * there is abnormal traversing cluster chain. - */ - if (unlikely(parent_ei->flags != ei->dir.flags || - parent_isize != EXFAT_CLU_TO_B(ei->dir.size, sbi) || - parent_ei->start_clu != ei->dir.dir)) { - exfat_chain_set(&ei->dir, parent_ei->start_clu, - EXFAT_B_TO_CLU_ROUND_UP(parent_isize, sbi), - parent_ei->flags); - } -} - /* rename or move a old file into a new file */ static int __exfat_rename(struct inode *old_parent_inode, struct exfat_inode_info *ei, struct inode *new_parent_inode, @@ -1203,12 +1314,10 @@ static int __exfat_rename(struct inode *old_parent_inode, return -ENOENT; } - exfat_update_parent_info(ei, old_parent_inode); - exfat_chain_dup(&olddir, &ei->dir); dentry = ei->entry; - ep = exfat_get_dentry(sb, &olddir, dentry, &old_bh, NULL); + ep = exfat_get_dentry(sb, &olddir, dentry, &old_bh); if (!ep) { ret = -EIO; goto out; @@ -1225,11 +1334,9 @@ static int __exfat_rename(struct inode *old_parent_inode, goto out; } - exfat_update_parent_info(new_ei, new_parent_inode); - p_dir = &(new_ei->dir); new_entry = new_ei->entry; - ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh, NULL); + ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); if (!ep) goto out; @@ -1269,7 +1376,7 @@ static int __exfat_rename(struct inode *old_parent_inode, if (!ret && new_inode) { /* delete entries of new_dir */ - ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh, NULL); + ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); if (!ep) { ret = -EIO; goto del_out; @@ -1313,20 +1420,32 @@ del_out: */ new_ei->dir.dir = DIR_DELETED; } - exfat_clear_volume_dirty(sb); out: return ret; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +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) +#else +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) +#else +static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +#endif +#endif { struct inode *old_inode, *new_inode; struct super_block *sb = old_dir->i_sb; loff_t i_pos; int err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) /* * The VFS already checks for existence, so for local filesystems * the RENAME_NOREPLACE implementation is equivalent to plain rename. @@ -1334,6 +1453,7 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, */ if (flags & ~RENAME_NOREPLACE) return -EINVAL; +#endif mutex_lock(&EXFAT_SB(sb)->s_lock); old_inode = old_dentry->d_inode; @@ -1343,9 +1463,18 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, if (err) goto unlock; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(new_dir); +#else + new_dir->i_version++; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = 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_SEC; +#endif exfat_truncate_atime(&new_dir->i_atime); if (IS_DIRSYNC(new_dir)) exfat_sync_inode(new_dir); @@ -1367,8 +1496,16 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, inc_nlink(new_dir); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(old_dir); +#else + old_dir->i_version++; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); +#else + old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC; +#endif if (IS_DIRSYNC(old_dir)) exfat_sync_inode(old_dir); else @@ -1386,8 +1523,13 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, exfat_warn(sb, "abnormal access to an inode dropped"); WARN_ON(new_inode->i_nlink == 0); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = current_time(new_inode); +#else + new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = + CURRENT_TIME_SEC; +#endif } unlock: @@ -1404,4 +1546,4 @@ const struct inode_operations exfat_dir_inode_operations = { .rename = exfat_rename, .setattr = exfat_setattr, .getattr = exfat_getattr, -}; \ No newline at end of file +}; diff --git a/fs/exfat/nls.c b/fs/exfat/nls.c index 314d5407a1be..ef74f1639638 100644 --- a/fs/exfat/nls.c +++ b/fs/exfat/nls.c @@ -3,9 +3,13 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include #include #include #include +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 18, 0) +#include +#endif #include #include "exfat_raw.h" @@ -659,7 +663,11 @@ static int exfat_load_upcase_table(struct super_block *sb, unsigned char skip = false; unsigned short *upcase_table; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) upcase_table = kvcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); +#else + upcase_table = vzalloc(UTBL_COUNT * sizeof(unsigned short)); +#endif if (!upcase_table) return -ENOMEM; @@ -715,7 +723,11 @@ static int exfat_load_default_upcase_table(struct super_block *sb) unsigned short uni = 0, *upcase_table; unsigned int index = 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) upcase_table = kvcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); +#else + upcase_table = vzalloc(UTBL_COUNT * sizeof(unsigned short)); +#endif if (!upcase_table) return -ENOMEM; @@ -761,7 +773,7 @@ int exfat_create_upcase_table(struct super_block *sb) while (clu.dir != EXFAT_EOF_CLUSTER) { for (i = 0; i < sbi->dentries_per_clu; i++) { - ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); + ep = exfat_get_dentry(sb, &clu, i, &bh); if (!ep) return -EIO; @@ -803,5 +815,9 @@ load_default: void exfat_free_upcase_table(struct exfat_sb_info *sbi) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) kvfree(sbi->vol_utbl); +#else + vfree(sbi->vol_utbl); +#endif } diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 190f5257248d..6837e042be7f 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -3,8 +3,14 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) #include #include +#else +#include +#include +#endif #include #include #include @@ -14,13 +20,20 @@ #include #include #include -#include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#include +#endif + #include "exfat_raw.h" #include "exfat_fs.h" +#ifndef CONFIG_EXFAT_DEFAULT_IOCHARSET /* if Kconfig lacked iocharset */ +#define CONFIG_EXFAT_DEFAULT_IOCHARSET "utf8" +#endif + static char exfat_default_iocharset[] = CONFIG_EXFAT_DEFAULT_IOCHARSET; static struct kmem_cache *exfat_inode_cachep; @@ -100,7 +113,6 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags) { struct exfat_sb_info *sbi = EXFAT_SB(sb); struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; - bool sync; /* retain persistent-flags */ new_flags |= sbi->vol_flags_persistent; @@ -114,21 +126,24 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags) /* skip updating volume dirty flag, * if this volume has been mounted with read-only */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) if (sb_rdonly(sb)) +#else + if (sb->s_flags & MS_RDONLY) +#endif return 0; p_boot->vol_flags = cpu_to_le16(new_flags); - if ((new_flags & VOLUME_DIRTY) && !buffer_dirty(sbi->boot_bh)) - sync = true; - else - sync = false; - set_buffer_uptodate(sbi->boot_bh); mark_buffer_dirty(sbi->boot_bh); - if (sync) - sync_dirty_buffer(sbi->boot_bh); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + __sync_dirty_buffer(sbi->boot_bh, REQ_SYNC | REQ_FUA | REQ_PREFLUSH); +#else + __sync_dirty_buffer(sbi->boot_bh, WRITE_FLUSH_FUA); +#endif + return 0; } @@ -174,7 +189,11 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",errors=remount-ro"); if (opts->discard) seq_puts(m, ",discard"); - if (opts->time_offset) + if (opts->keep_last_dots) + seq_puts(m, ",keep_last_dots"); + if (opts->sys_tz) + seq_puts(m, ",sys_tz"); + else if (opts->time_offset) seq_printf(m, ",time_offset=%d", opts->time_offset); return 0; } @@ -183,7 +202,11 @@ static struct inode *exfat_alloc_inode(struct super_block *sb) { struct exfat_inode_info *ei; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) + ei = alloc_inode_sb(sb, exfat_inode_cachep, GFP_NOFS); +#else ei = kmem_cache_alloc(exfat_inode_cachep, GFP_NOFS); +#endif if (!ei) return NULL; @@ -191,14 +214,48 @@ static struct inode *exfat_alloc_inode(struct super_block *sb) return &ei->vfs_inode; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) static void exfat_free_inode(struct inode *inode) { kmem_cache_free(exfat_inode_cachep, EXFAT_I(inode)); } +#else +static void exfat_i_callback(struct rcu_head *head) +{ + struct inode *inode = container_of(head, struct inode, i_rcu); + kmem_cache_free(exfat_inode_cachep, EXFAT_I(inode)); +} + +static void exfat_destroy_inode(struct inode *inode) +{ + call_rcu(&inode->i_rcu, exfat_i_callback); +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) +static int exfat_remount(struct super_block *sb, int *flags, char *data) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) + *flags |= SB_NODIRATIME; +#else + *flags |= MS_NODIRATIME; +#endif + + sync_filesystem(sb); + return 0; +} +#endif static const struct super_operations exfat_sops = { .alloc_inode = exfat_alloc_inode, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) .free_inode = exfat_free_inode, +#else + .destroy_inode = exfat_destroy_inode, +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) + .remount_fs = exfat_remount, +#endif .write_inode = exfat_write_inode, .evict_inode = exfat_evict_inode, .put_super = exfat_put_super, @@ -207,6 +264,7 @@ static const struct super_operations exfat_sops = { .show_options = exfat_show_options, }; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) enum { Opt_uid, Opt_gid, @@ -217,10 +275,38 @@ enum { Opt_charset, Opt_errors, Opt_discard, + Opt_keep_last_dots, + Opt_sys_tz, Opt_time_offset, + + /* Deprecated options */ + Opt_utf8, + Opt_debug, + Opt_namecase, + Opt_codepage, }; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) +static const struct constant_table exfat_param_enums[] = { + { "continue", EXFAT_ERRORS_CONT }, + { "panic", EXFAT_ERRORS_PANIC }, + { "remount-ro", EXFAT_ERRORS_RO }, + {} +}; +#else +static const struct fs_parameter_enum exfat_param_enums[] = { + { Opt_errors, "continue", EXFAT_ERRORS_CONT }, + { Opt_errors, "panic", EXFAT_ERRORS_PANIC }, + { Opt_errors, "remount-ro", EXFAT_ERRORS_RO }, + {} +}; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) +static const struct fs_parameter_spec exfat_parameters[] = { +#else static const struct fs_parameter_spec exfat_param_specs[] = { +#endif fsparam_u32("uid", Opt_uid), fsparam_u32("gid", Opt_gid), fsparam_u32oct("umask", Opt_umask), @@ -228,24 +314,33 @@ static const struct fs_parameter_spec exfat_param_specs[] = { fsparam_u32oct("fmask", Opt_fmask), fsparam_u32oct("allow_utime", Opt_allow_utime), fsparam_string("iocharset", Opt_charset), +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) + fsparam_enum("errors", Opt_errors, exfat_param_enums), +#else fsparam_enum("errors", Opt_errors), +#endif fsparam_flag("discard", Opt_discard), + fsparam_flag("keep_last_dots", Opt_keep_last_dots), + fsparam_flag("sys_tz", Opt_sys_tz), fsparam_s32("time_offset", Opt_time_offset), + __fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated, + NULL), + __fsparam(NULL, "debug", Opt_debug, fs_param_deprecated, + NULL), + __fsparam(fs_param_is_u32, "namecase", Opt_namecase, + fs_param_deprecated, NULL), + __fsparam(fs_param_is_u32, "codepage", Opt_codepage, + fs_param_deprecated, NULL), {} }; -static const struct fs_parameter_enum exfat_param_enums[] = { - { Opt_errors, "continue", EXFAT_ERRORS_CONT }, - { Opt_errors, "panic", EXFAT_ERRORS_PANIC }, - { Opt_errors, "remount-ro", EXFAT_ERRORS_RO }, - {} -}; - +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 6, 0) static const struct fs_parameter_description exfat_parameters = { .name = "exfat", .specs = exfat_param_specs, .enums = exfat_param_enums, }; +#endif static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) { @@ -254,7 +349,11 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) struct fs_parse_result result; int opt; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) + opt = fs_parse(fc, exfat_parameters, param, &result); +#else opt = fs_parse(fc, &exfat_parameters, param, &result); +#endif if (opt < 0) return opt; @@ -289,6 +388,12 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) case Opt_discard: opts->discard = 1; break; + case Opt_keep_last_dots: + opts->keep_last_dots = 1; + break; + case Opt_sys_tz: + opts->sys_tz = 1; + break; case Opt_time_offset: /* * Make the limit 24 just in case someone invents something @@ -298,12 +403,175 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) return -EINVAL; opts->time_offset = result.int_32; break; + case Opt_utf8: + case Opt_debug: + case Opt_namecase: + case Opt_codepage: + break; default: return -EINVAL; } return 0; } +#else +enum { + Opt_uid, + Opt_gid, + Opt_umask, + Opt_dmask, + Opt_fmask, + Opt_allow_utime, + Opt_charset, + Opt_err_cont, + Opt_err_panic, + Opt_err_ro, + Opt_err, + Opt_discard, + Opt_time_offset, + + /* Deprecated options */ + Opt_utf8, + Opt_debug, + Opt_namecase, + Opt_codepage, + Opt_fs, +}; + +static const match_table_t exfat_tokens = { + {Opt_uid, "uid=%u"}, + {Opt_gid, "gid=%u"}, + {Opt_umask, "umask=%o"}, + {Opt_dmask, "dmask=%o"}, + {Opt_fmask, "fmask=%o"}, + {Opt_allow_utime, "allow_utime=%o"}, + {Opt_charset, "iocharset=%s"}, + {Opt_err_cont, "errors=continue"}, + {Opt_err_panic, "errors=panic"}, + {Opt_err_ro, "errors=remount-ro"}, + {Opt_discard, "discard"}, + {Opt_codepage, "codepage=%u"}, + {Opt_namecase, "namecase=%u"}, + {Opt_debug, "debug"}, + {Opt_utf8, "utf8"}, + {Opt_err, NULL} +}; + +static int parse_options(struct super_block *sb, char *options, int silent, + struct exfat_mount_options *opts) +{ + char *p; + substring_t args[MAX_OPT_ARGS]; + int option; + char *tmpstr; + + opts->fs_uid = current_uid(); + opts->fs_gid = current_gid(); + opts->fs_fmask = opts->fs_dmask = current->fs->umask; + opts->allow_utime = -1; + opts->iocharset = exfat_default_iocharset; + opts->utf8 = 0; + opts->errors = EXFAT_ERRORS_RO; + opts->discard = 0; + + if (!options) + goto out; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + + if (!*p) + continue; + token = match_token(p, exfat_tokens, args); + switch (token) { + case Opt_uid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_uid = make_kuid(current_user_ns(), option); + break; + case Opt_gid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_gid = make_kgid(current_user_ns(), option); + break; + case Opt_umask: + case Opt_dmask: + case Opt_fmask: + if (match_octal(&args[0], &option)) + return 0; + if (token != Opt_dmask) + opts->fs_fmask = option; + if (token != Opt_fmask) + opts->fs_dmask = option; + break; + case Opt_allow_utime: + if (match_octal(&args[0], &option)) + return 0; + opts->allow_utime = option & (0022); + break; + case Opt_charset: + if (opts->iocharset != exfat_default_iocharset) + kfree(opts->iocharset); + tmpstr = match_strdup(&args[0]); + if (!tmpstr) + return -ENOMEM; + opts->iocharset = tmpstr; + break; + case Opt_err_cont: + opts->errors = EXFAT_ERRORS_CONT; + break; + case Opt_err_panic: + opts->errors = EXFAT_ERRORS_PANIC; + break; + case Opt_err_ro: + opts->errors = EXFAT_ERRORS_RO; + break; + case Opt_discard: + opts->discard = 1; + break; + case Opt_time_offset: + if (match_int(&args[0], &option)) + return -EINVAL; + /* + * Make the limit 24 just in case someone + * invents something unusual. + */ + if (option < -24 * 60 || option > 24 * 60) + return -EINVAL; + opts->time_offset = option; + break; + case Opt_utf8: + case Opt_debug: + case Opt_namecase: + case Opt_codepage: + break; + default: + if (!silent) { + exfat_msg(sb, KERN_ERR, + "unrecognized mount option \"%s\" or missing value", + p); + } + return -EINVAL; + } + } + +out: + if (opts->allow_utime == -1) + opts->allow_utime = ~opts->fs_dmask & (0022); + + if (opts->discard) { + struct request_queue *q = bdev_get_queue(sb->s_bdev); + + if (!blk_queue_discard(q)) + exfat_msg(sb, KERN_WARNING, + "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } + + return 0; +} +#endif + static void exfat_hash_init(struct super_block *sb) { @@ -346,21 +614,31 @@ static int exfat_read_root(struct inode *inode) inode->i_uid = sbi->options.fs_uid; inode->i_gid = sbi->options.fs_gid; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_inc_iversion(inode); +#else + inode->i_version++; +#endif inode->i_generation = 0; inode->i_mode = exfat_make_mode(sbi, ATTR_SUBDIR, 0777); inode->i_op = &exfat_dir_inode_operations; inode->i_fop = &exfat_dir_operations; - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) - & ~(sbi->cluster_size - 1)) >> inode->i_blkbits; - EXFAT_I(inode)->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; - EXFAT_I(inode)->i_size_aligned = i_size_read(inode); - EXFAT_I(inode)->i_size_ondisk = i_size_read(inode); + inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & + ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; + ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; + ei->i_size_aligned = i_size_read(inode); + ei->i_size_ondisk = i_size_read(inode); exfat_save_attr(inode, 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); +#else + inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = + CURRENT_TIME_SEC; +#endif exfat_truncate_atime(&inode->i_atime); return 0; } @@ -369,8 +647,7 @@ static int exfat_calibrate_blocksize(struct super_block *sb, int logical_sect) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - if (!is_power_of_2(logical_sect) || - logical_sect < 512 || logical_sect > 4096) { + if (!is_power_of_2(logical_sect)) { exfat_err(sb, "bogus logical sector size %u", logical_sect); return -EIO; } @@ -439,6 +716,25 @@ static int exfat_read_boot_sector(struct super_block *sb) return -EINVAL; } + /* + * sect_size_bits could be at least 9 and at most 12. + */ + if (p_boot->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS || + p_boot->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) { + exfat_err(sb, "bogus sector size bits : %u\n", + p_boot->sect_size_bits); + return -EINVAL; + } + + /* + * sect_per_clus_bits could be at least 0 and at most 25 - sect_size_bits. + */ + if (p_boot->sect_per_clus_bits > EXFAT_MAX_SECT_PER_CLUS_BITS(p_boot)) { + exfat_err(sb, "bogus sectors bits per cluster : %u\n", + p_boot->sect_per_clus_bits); + return -EINVAL; + } + sbi->sect_per_clus = 1 << p_boot->sect_per_clus_bits; sbi->sect_per_clus_bits = p_boot->sect_per_clus_bits; sbi->cluster_size_bits = p_boot->sect_per_clus_bits + @@ -465,16 +761,19 @@ static int exfat_read_boot_sector(struct super_block *sb) sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; /* check consistencies */ - if (sbi->num_FAT_sectors << p_boot->sect_size_bits < - sbi->num_clusters * 4) { + if ((u64)sbi->num_FAT_sectors << p_boot->sect_size_bits < + (u64)sbi->num_clusters * 4) { exfat_err(sb, "bogus fat length"); return -EINVAL; } + if (sbi->data_start_sector < - sbi->FAT1_start_sector + sbi->num_FAT_sectors * p_boot->num_fats) { + (u64)sbi->FAT1_start_sector + + (u64)sbi->num_FAT_sectors * p_boot->num_fats) { exfat_err(sb, "bogus data start sector"); return -EINVAL; } + if (sbi->vol_flags & VOLUME_DIRTY) exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); if (sbi->vol_flags & MEDIA_FAILURE) @@ -582,12 +881,17 @@ free_bh: return ret; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) +#else +static int exfat_fill_super(struct super_block *sb, void *data, int silent) +#endif { - struct exfat_sb_info *sbi = sb->s_fs_info; - struct exfat_mount_options *opts = &sbi->options; struct inode *root_inode; int err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) + struct exfat_sb_info *sbi = sb->s_fs_info; + struct exfat_mount_options *opts = &sbi->options; if (opts->allow_utime == (unsigned short)-1) opts->allow_utime = ~opts->fs_dmask & 0022; @@ -600,14 +904,44 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) opts->discard = 0; } } +#else + struct exfat_sb_info *sbi; + /* + * GFP_KERNEL is ok here, because while we do hold the + * supeblock lock, memory pressure can't call back into + * the filesystem, since we're only just about to mount + * it and have no inodes etc active! + */ + sbi = kzalloc(sizeof(struct exfat_sb_info), GFP_KERNEL); + if (!sbi) + return -ENOMEM; + + mutex_init(&sbi->s_lock); + mutex_init(&sbi->bitmap_lock); + sb->s_fs_info = sbi; + ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, + DEFAULT_RATELIMIT_BURST); + err = parse_options(sb, data, silent, &sbi->options); + if (err) { + exfat_msg(sb, KERN_ERR, "failed to parse options"); + goto check_nls_io; + } +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) sb->s_flags |= SB_NODIRATIME; +#else + sb->s_flags |= MS_NODIRATIME; +#endif sb->s_magic = EXFAT_SUPER_MAGIC; sb->s_op = &exfat_sops; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) sb->s_time_gran = 10 * NSEC_PER_MSEC; sb->s_time_min = EXFAT_MIN_TIMESTAMP_SECS; sb->s_time_max = EXFAT_MAX_TIMESTAMP_SECS; +#endif err = __exfat_fill_super(sb); if (err) { @@ -619,7 +953,11 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) exfat_hash_init(sb); if (!strcmp(sbi->options.iocharset, "utf8")) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) opts->utf8 = 1; +#else + sbi->options.utf8 = 1; +#endif else { sbi->nls_io = load_nls(sbi->options.iocharset); if (!sbi->nls_io) { @@ -643,7 +981,12 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) } root_inode->i_ino = EXFAT_ROOT_INO; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) inode_set_iversion(root_inode, 1); +#else + root_inode->i_version = 1; +#endif err = exfat_read_root(root_inode); if (err) { exfat_err(sb, "failed to initialize root inode"); @@ -657,7 +1000,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) if (!sb->s_root) { exfat_err(sb, "failed to get the root dentry"); err = -ENOMEM; - goto put_inode; + goto free_table; } return 0; @@ -679,6 +1022,7 @@ check_nls_io: return err; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) static int exfat_get_tree(struct fs_context *fc) { return get_tree_bdev(fc, exfat_fill_super); @@ -719,6 +1063,7 @@ static int exfat_init_fs_context(struct fs_context *fc) return -ENOMEM; mutex_init(&sbi->s_lock); + mutex_init(&sbi->bitmap_lock); ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); @@ -734,12 +1079,23 @@ static int exfat_init_fs_context(struct fs_context *fc) fc->ops = &exfat_context_ops; return 0; } +#else +static struct dentry *exfat_fs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_bdev(fs_type, flags, dev_name, data, exfat_fill_super); +} +#endif static struct file_system_type exfat_fs_type = { .owner = THIS_MODULE, .name = "exfat", +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) .init_fs_context = exfat_init_fs_context, - .parameters = &exfat_parameters, + .parameters = exfat_parameters, +#else + .mount = exfat_fs_mount, +#endif .kill_sb = kill_block_super, .fs_flags = FS_REQUIRES_DEV, }; @@ -805,3 +1161,4 @@ MODULE_ALIAS_FS("exfat"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("exFAT filesystem support"); MODULE_AUTHOR("Samsung Electronics Co., Ltd."); +MODULE_VERSION(EXFAT_VERSION);