From 967fa6b9d1a96b37836e11908b7379867810953b Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 30 Aug 2023 22:20:14 +0900 Subject: [PATCH 01/18] exfat: vfs: get rid of old '->iterate' directory operation All users now just use '->iterate_shared()', which only takes the directory inode lock for reading. Filesystems that never got convered to shared mode now instead use a wrapper that drops the lock, re-takes it in write mode, calls the old function, and then downgrades the lock back to read mode. This way the VFS layer and other callers no longer need to care about filesystems that never got converted to the modern era. The filesystems that use the new wrapper are ceph, coda, exfat, jfs, ntfs, ocfs2, overlayfs, and vboxsf. Honestly, several of them look like they really could just iterate their directories in shared mode and skip the wrapper entirely, but the point of this change is to not change semantics or fix filesystems that haven't been fixed in the last 7+ years, but to finally get rid of the dual iterators. Signed-off-by: Linus Torvalds Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- dir.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dir.c b/dir.c index bf8eb5d1b1c2..a9dc79e0e6d4 100644 --- a/dir.c +++ b/dir.c @@ -307,10 +307,17 @@ out: return err; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) +WRAP_DIR_ITER(exfat_iterate) // FIXME! +#endif const struct file_operations exfat_dir_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) + .iterate_shared = shared_exfat_iterate, +#else .iterate = exfat_iterate, +#endif .unlocked_ioctl = exfat_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = exfat_compat_ioctl, From cc9879a6ce62c90737c19635bfc74c3c93e2b597 Mon Sep 17 00:00:00 2001 From: Jan Cincera Date: Mon, 4 Sep 2023 11:05:23 +0900 Subject: [PATCH 02/18] exfat: add ioctls for accessing attributes Add GET and SET attributes ioctls to enable attribute modification. We already do this in FAT and a few userspace utils made for it would benefit from this also working on exFAT, namely fatattr. Signed-off-by: Jan Cincera Signed-off-by: Namjae Jeon --- exfat_fs.h | 6 +++ file.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index b79f2fe2bea5..97ee6843c17e 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -162,6 +162,12 @@ enum { #define DIR_CACHE_SIZE \ (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) +/* + * attribute ioctls, same as their FAT equivalents. + */ +#define EXFAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) +#define EXFAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32) + struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ diff --git a/file.c b/file.c index e35998f5aa8c..216c38b981e1 100644 --- a/file.c +++ b/file.c @@ -9,8 +9,11 @@ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include #endif +#include #include #include +#include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -390,6 +393,130 @@ out: return error; } +/* + * modified ioctls from fat/file.c by Welmer Almesberger + */ +static int exfat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr) +{ + u32 attr; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + inode_lock_shared(inode); +#else + mutex_lock(&inode->i_mutex); +#endif + attr = exfat_make_attr(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + inode_unlock_shared(inode); +#else + mutex_unlock(&inode->i_mutex); +#endif + + return put_user(attr, user_attr); +} + +static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) +{ + struct inode *inode = file_inode(file); + struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); + int is_dir = S_ISDIR(inode->i_mode); + u32 attr, oldattr; + struct iattr ia; + int err; + + err = get_user(attr, user_attr); + if (err) + goto out; + + err = mnt_want_write_file(file); + if (err) + goto out; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) + inode_lock(inode); +#else + mutex_lock(&inode->i_mutex); +#endif + + oldattr = exfat_make_attr(inode); + + /* + * Mask attributes so we don't set reserved fields. + */ + attr &= (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE); + attr |= (is_dir ? ATTR_SUBDIR : 0); + + /* Equivalent to a chmod() */ + ia.ia_valid = ATTR_MODE | ATTR_CTIME; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + ia.ia_ctime = current_time(inode); +#else + ia.ia_ctime = current_fs_time(inode->i_sb); +#endif + if (is_dir) + ia.ia_mode = exfat_make_mode(sbi, attr, 0777); + else + ia.ia_mode = exfat_make_mode(sbi, attr, 0666 | (inode->i_mode & 0111)); + + /* The root directory has no attributes */ + if (inode->i_ino == EXFAT_ROOT_INO && attr != ATTR_SUBDIR) { + err = -EINVAL; + goto out_unlock_inode; + } + + if (((attr | oldattr) & ATTR_SYSTEM) && + !capable(CAP_LINUX_IMMUTABLE)) { + err = -EPERM; + goto out_unlock_inode; + } + + /* + * The security check is questionable... We single + * out the RO attribute for checking by the security + * module, just because it maps to a file mode. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + err = security_inode_setattr(file_mnt_idmap(file), + file->f_path.dentry, &ia); +#else + err = security_inode_setattr(file_mnt_user_ns(file), + file->f_path.dentry, &ia); +#endif +#else + err = security_inode_setattr(file->f_path.dentry, &ia); +#endif + if (err) + goto out_unlock_inode; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + /* This MUST be done before doing anything irreversible... */ + err = exfat_setattr(file_mnt_idmap(file), file->f_path.dentry, &ia); +#else + err = exfat_setattr(file_mnt_user_ns(file), file->f_path.dentry, &ia); +#endif +#else + err = exfat_setattr(file->f_path.dentry, &ia); +#endif + if (err) + goto out_unlock_inode; + + fsnotify_change(file->f_path.dentry, ia.ia_valid); + + exfat_save_attr(inode, attr); + mark_inode_dirty(inode); +out_unlock_inode: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) + inode_unlock(inode); +#else + mutex_unlock(&inode->i_mutex); +#endif + mnt_drop_write_file(file); +out: + return err; +} + static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) { #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) @@ -432,8 +559,13 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(filp); + u32 __user *user_attr = (u32 __user *)arg; switch (cmd) { + case EXFAT_IOCTL_GET_ATTRIBUTES: + return exfat_ioctl_get_attributes(inode, user_attr); + case EXFAT_IOCTL_SET_ATTRIBUTES: + return exfat_ioctl_set_attributes(filp, user_attr); case FITRIM: return exfat_ioctl_fitrim(inode, arg); default: From f6c705d1d91cc6624b70864a32ab55f09bdf5798 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 4 Sep 2023 11:08:43 +0900 Subject: [PATCH 03/18] exfat: support handle zero-size directory After repairing a corrupted file system with exfatprogs' fsck.exfat, zero-size directories may result. It is also possible to create zero-size directories in other exFAT implementation, such as Paragon ufsd dirver. As described in the specification, the lower directory size limits is 0 bytes. Without this commit, sub-directories and files cannot be created under a zero-size directory, and it cannot be removed. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- namei.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/namei.c b/namei.c index e6971c758892..3429a8e47495 100644 --- a/namei.c +++ b/namei.c @@ -378,14 +378,20 @@ static int exfat_find_empty_entry(struct inode *inode, if (exfat_check_max_dentries(inode)) return -ENOSPC; - /* we trust p_dir->size regardless of FAT type */ - if (exfat_find_last_cluster(sb, p_dir, &last_clu)) - return -EIO; - /* * Allocate new cluster to this directory */ - exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags); + if (ei->start_clu != EXFAT_EOF_CLUSTER) { + /* we trust p_dir->size regardless of FAT type */ + if (exfat_find_last_cluster(sb, p_dir, &last_clu)) + return -EIO; + + exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags); + } else { + /* This directory is empty */ + exfat_chain_set(&clu, EXFAT_EOF_CLUSTER, 0, + ALLOC_NO_FAT_CHAIN); + } /* allocate a cluster */ ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode)); @@ -395,6 +401,11 @@ static int exfat_find_empty_entry(struct inode *inode, if (exfat_zeroed_cluster(inode, clu.dir)) return -EIO; + if (ei->start_clu == EXFAT_EOF_CLUSTER) { + ei->start_clu = clu.dir; + p_dir->dir = clu.dir; + } + /* append to the FAT chain */ if (clu.flags != p_dir->flags) { /* no-fat-chain bit is disabled, @@ -709,7 +720,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); info->size = le64_to_cpu(ep2->dentry.stream.valid_size); - if ((info->type == TYPE_FILE) && (info->size == 0)) { + if (info->size == 0) { info->flags = ALLOC_NO_FAT_CHAIN; info->start_clu = EXFAT_EOF_CLUSTER; } else { @@ -1000,6 +1011,9 @@ static int exfat_check_dir_empty(struct super_block *sb, dentries_per_clu = sbi->dentries_per_clu; + if (p_dir->dir == EXFAT_EOF_CLUSTER) + return 0; + exfat_chain_dup(&clu, p_dir); while (clu.dir != EXFAT_EOF_CLUSTER) { @@ -1383,7 +1397,8 @@ static int __exfat_rename(struct inode *old_parent_inode, } /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */ - if (new_entry_type == TYPE_DIR) { + if (new_entry_type == TYPE_DIR && + new_ei->start_clu != EXFAT_EOF_CLUSTER) { /* new_ei, new_clu_to_free */ struct exfat_chain new_clu_to_free; From 5bb0e9f3ec0983e2dc77e8d4bb8b43b1bcfe280c Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Tue, 5 Sep 2023 11:26:20 +0900 Subject: [PATCH 04/18] exfat: support create zero-size directory This commit adds mount option 'zero_size_dir'. If this option enabled, don't allocate a cluster to directory when creating it, and set the directory size to 0. On Windows, a cluster is allocated for a directory when it is created, so the mount option is disabled by default. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 12 ++++++------ exfat_fs.h | 2 ++ namei.c | 7 +++++-- super.c | 12 ++++++++++++ 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/dir.c b/dir.c index a9dc79e0e6d4..f8a04e16dc88 100644 --- a/dir.c +++ b/dir.c @@ -425,11 +425,13 @@ static void exfat_set_entry_type(struct exfat_dentry *ep, unsigned int type) } static void exfat_init_stream_entry(struct exfat_dentry *ep, - unsigned char flags, unsigned int start_clu, - unsigned long long size) + unsigned int start_clu, unsigned long long size) { exfat_set_entry_type(ep, TYPE_STREAM); - ep->dentry.stream.flags = flags; + if (size == 0) + ep->dentry.stream.flags = ALLOC_FAT_CHAIN; + else + ep->dentry.stream.flags = ALLOC_NO_FAT_CHAIN; ep->dentry.stream.start_clu = cpu_to_le32(start_clu); ep->dentry.stream.valid_size = cpu_to_le64(size); ep->dentry.stream.size = cpu_to_le64(size); @@ -505,9 +507,7 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, if (!ep) return -EIO; - exfat_init_stream_entry(ep, - (type == TYPE_FILE) ? ALLOC_FAT_CHAIN : ALLOC_NO_FAT_CHAIN, - start_clu, size); + exfat_init_stream_entry(ep, start_clu, size); exfat_update_bh(bh, IS_DIRSYNC(inode)); brelse(bh); diff --git a/exfat_fs.h b/exfat_fs.h index 97ee6843c17e..311d9f28e25f 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -259,6 +259,8 @@ struct exfat_mount_options { discard:1, /* Issue discard requests on deletions */ keep_last_dots:1; /* Keep trailing periods in paths */ int time_offset; /* Offset of timestamps from UTC (in minutes) */ + /* Support creating zero-size directory, default: false */ + bool zero_size_dir; }; /* diff --git a/namei.c b/namei.c index 3429a8e47495..d43af646e229 100644 --- a/namei.c +++ b/namei.c @@ -545,7 +545,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, goto out; } - if (type == TYPE_DIR) { + if (type == TYPE_DIR && !sbi->options.zero_size_dir) { ret = exfat_alloc_new_dir(inode, &clu); if (ret) goto out; @@ -578,7 +578,10 @@ static int exfat_add_entry(struct inode *inode, const char *path, info->num_subdirs = 0; } else { info->attr = ATTR_SUBDIR; - info->start_clu = start_clu; + if (sbi->options.zero_size_dir) + info->start_clu = EXFAT_EOF_CLUSTER; + else + info->start_clu = start_clu; info->size = clu_size; info->num_subdirs = EXFAT_MIN_SUBDIR; } diff --git a/super.c b/super.c index 665c296e4ad4..89f60d722c99 100644 --- a/super.c +++ b/super.c @@ -195,6 +195,8 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",sys_tz"); else if (opts->time_offset) seq_printf(m, ",time_offset=%d", opts->time_offset); + if (opts->zero_size_dir) + seq_puts(m, ",zero_size_dir"); return 0; } @@ -278,6 +280,7 @@ enum { Opt_keep_last_dots, Opt_sys_tz, Opt_time_offset, + Opt_zero_size_dir, /* Deprecated options */ Opt_utf8, @@ -323,6 +326,7 @@ static const struct fs_parameter_spec exfat_param_specs[] = { fsparam_flag("keep_last_dots", Opt_keep_last_dots), fsparam_flag("sys_tz", Opt_sys_tz), fsparam_s32("time_offset", Opt_time_offset), + fsparam_flag("zero_size_dir", Opt_zero_size_dir), __fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated, NULL), __fsparam(NULL, "debug", Opt_debug, fs_param_deprecated, @@ -403,6 +407,9 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) return -EINVAL; opts->time_offset = result.int_32; break; + case Opt_zero_size_dir: + opts->zero_size_dir = true; + break; case Opt_utf8: case Opt_debug: case Opt_namecase: @@ -435,6 +442,7 @@ enum { Opt_debug, Opt_namecase, Opt_codepage, + Opt_zero_size_dir, Opt_fs, }; @@ -454,6 +462,7 @@ static const match_table_t exfat_tokens = { {Opt_namecase, "namecase=%u"}, {Opt_debug, "debug"}, {Opt_utf8, "utf8"}, + {Opt_zero_size_dir, "zero_size_dir"}, {Opt_err, NULL} }; @@ -545,6 +554,9 @@ static int parse_options(struct super_block *sb, char *options, int silent, case Opt_namecase: case Opt_codepage: break; + case Opt_zero_size_dir: + opts->zero_size_dir = true; + break; default: if (!silent) { exfat_err(sb, From 8279a1a0bc27b03565da6775454e09a520b13a09 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 11 Sep 2023 16:59:54 +0900 Subject: [PATCH 05/18] exfat: github action: remove liunx-4.1 source to get more disk space Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 375c800ac23c..6587fd98bd41 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -20,6 +20,7 @@ jobs: wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz tar xf linux-4.1.36.tar.gz mv linux-4.1.36 linux-stable + rm -rf linux-4.1.36.tar.gz - name: Prerequisite for xfstests testing run: | sudo apt-get install linux-headers-$(uname -r) @@ -47,7 +48,9 @@ jobs: make -j$((`nproc`+1)) fs/exfat/exfat.ko - name: Run xfstests testsuite run: | - cd ../linux-exfat-oot + cd .. + rm -rf linux-stable + cd linux-exfat-oot make > /dev/null sudo make install > /dev/null sudo insmod exfat.ko From 950e271397ef414c402e653e0117e1f884e06fe1 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 30 Oct 2023 21:08:39 +0900 Subject: [PATCH 06/18] exfat: use fat ioctls definitions from include/uapi/linux/msdos_fs.h Signed-off-by: Namjae Jeon --- dir.c | 8 ++++---- exfat_fs.h | 18 ++++++------------ exfat_raw.h | 17 +++++++++-------- file.c | 16 +++++++++------- inode.c | 6 +++--- namei.c | 16 ++++++++-------- super.c | 4 ++-- 7 files changed, 41 insertions(+), 44 deletions(-) diff --git a/dir.c b/dir.c index f8a04e16dc88..4d5b863b4fde 100644 --- a/dir.c +++ b/dir.c @@ -288,7 +288,7 @@ get_new: mutex_unlock(&EXFAT_SB(sb)->s_lock); if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, - (de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) + (de.attr & EXFAT_ATTR_SUBDIR) ? DT_DIR : DT_REG)) goto out; ctx->pos = cpos; goto get_new; @@ -366,7 +366,7 @@ unsigned int exfat_get_entry_type(struct exfat_dentry *ep) if (ep->type == EXFAT_VOLUME) return TYPE_VOLUME; if (ep->type == EXFAT_FILE) { - if (le16_to_cpu(ep->dentry.file.attr) & ATTR_SUBDIR) + if (le16_to_cpu(ep->dentry.file.attr) & EXFAT_ATTR_SUBDIR) return TYPE_DIR; return TYPE_FILE; } @@ -417,10 +417,10 @@ static void exfat_set_entry_type(struct exfat_dentry *ep, unsigned int type) ep->type = EXFAT_VOLUME; } else if (type == TYPE_DIR) { ep->type = EXFAT_FILE; - ep->dentry.file.attr = cpu_to_le16(ATTR_SUBDIR); + ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_SUBDIR); } else if (type == TYPE_FILE) { ep->type = EXFAT_FILE; - ep->dentry.file.attr = cpu_to_le16(ATTR_ARCHIVE); + ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_ARCHIVE); } } diff --git a/exfat_fs.h b/exfat_fs.h index 311d9f28e25f..9c2266afe3f6 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -162,12 +162,6 @@ enum { #define DIR_CACHE_SIZE \ (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) -/* - * attribute ioctls, same as their FAT equivalents. - */ -#define EXFAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) -#define EXFAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32) - struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ @@ -390,10 +384,10 @@ static inline int exfat_mode_can_hold_ro(struct inode *inode) static inline mode_t exfat_make_mode(struct exfat_sb_info *sbi, unsigned short attr, mode_t mode) { - if ((attr & ATTR_READONLY) && !(attr & ATTR_SUBDIR)) + if ((attr & EXFAT_ATTR_READONLY) && !(attr & EXFAT_ATTR_SUBDIR)) mode &= ~0222; - if (attr & ATTR_SUBDIR) + if (attr & EXFAT_ATTR_SUBDIR) return (mode & ~sbi->options.fs_dmask) | S_IFDIR; return (mode & ~sbi->options.fs_fmask) | S_IFREG; @@ -405,18 +399,18 @@ static inline unsigned short exfat_make_attr(struct inode *inode) unsigned short attr = EXFAT_I(inode)->attr; if (S_ISDIR(inode->i_mode)) - attr |= ATTR_SUBDIR; + attr |= EXFAT_ATTR_SUBDIR; if (exfat_mode_can_hold_ro(inode) && !(inode->i_mode & 0222)) - attr |= ATTR_READONLY; + attr |= EXFAT_ATTR_READONLY; return attr; } static inline void exfat_save_attr(struct inode *inode, unsigned short attr) { if (exfat_mode_can_hold_ro(inode)) - EXFAT_I(inode)->attr = attr & (ATTR_RWMASK | ATTR_READONLY); + EXFAT_I(inode)->attr = attr & (EXFAT_ATTR_RWMASK | EXFAT_ATTR_READONLY); else - EXFAT_I(inode)->attr = attr & ATTR_RWMASK; + EXFAT_I(inode)->attr = attr & EXFAT_ATTR_RWMASK; } static inline bool exfat_is_last_sector_in_cluster(struct exfat_sb_info *sbi, diff --git a/exfat_raw.h b/exfat_raw.h index 0ece2e43cf49..971a1ccd0e89 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -64,15 +64,16 @@ #define CS_DEFAULT 2 /* file attributes */ -#define ATTR_READONLY 0x0001 -#define ATTR_HIDDEN 0x0002 -#define ATTR_SYSTEM 0x0004 -#define ATTR_VOLUME 0x0008 -#define ATTR_SUBDIR 0x0010 -#define ATTR_ARCHIVE 0x0020 +#define EXFAT_ATTR_READONLY 0x0001 +#define EXFAT_ATTR_HIDDEN 0x0002 +#define EXFAT_ATTR_SYSTEM 0x0004 +#define EXFAT_ATTR_VOLUME 0x0008 +#define EXFAT_ATTR_SUBDIR 0x0010 +#define EXFAT_ATTR_ARCHIVE 0x0020 -#define ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ - ATTR_SUBDIR | ATTR_ARCHIVE) +#define EXFAT_ATTR_RWMASK (EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM | \ + EXFAT_ATTR_VOLUME | EXFAT_ATTR_SUBDIR | \ + EXFAT_ATTR_ARCHIVE) #define BOOTSEC_JUMP_BOOT_LEN 3 #define BOOTSEC_FS_NAME_LEN 8 diff --git a/file.c b/file.c index 216c38b981e1..59848fa2793d 100644 --- a/file.c +++ b/file.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -154,7 +155,7 @@ int __exfat_truncate(struct inode *inode) } if (ei->type == TYPE_FILE) - ei->attr |= ATTR_ARCHIVE; + ei->attr |= EXFAT_ATTR_ARCHIVE; /* * update the directory entry @@ -443,8 +444,9 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) /* * Mask attributes so we don't set reserved fields. */ - attr &= (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE); - attr |= (is_dir ? ATTR_SUBDIR : 0); + attr &= (EXFAT_ATTR_READONLY | EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM | + EXFAT_ATTR_ARCHIVE); + attr |= (is_dir ? EXFAT_ATTR_SUBDIR : 0); /* Equivalent to a chmod() */ ia.ia_valid = ATTR_MODE | ATTR_CTIME; @@ -459,12 +461,12 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) ia.ia_mode = exfat_make_mode(sbi, attr, 0666 | (inode->i_mode & 0111)); /* The root directory has no attributes */ - if (inode->i_ino == EXFAT_ROOT_INO && attr != ATTR_SUBDIR) { + if (inode->i_ino == EXFAT_ROOT_INO && attr != EXFAT_ATTR_SUBDIR) { err = -EINVAL; goto out_unlock_inode; } - if (((attr | oldattr) & ATTR_SYSTEM) && + if (((attr | oldattr) & EXFAT_ATTR_SYSTEM) && !capable(CAP_LINUX_IMMUTABLE)) { err = -EPERM; goto out_unlock_inode; @@ -562,9 +564,9 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) u32 __user *user_attr = (u32 __user *)arg; switch (cmd) { - case EXFAT_IOCTL_GET_ATTRIBUTES: + case FAT_IOCTL_GET_ATTRIBUTES: return exfat_ioctl_get_attributes(inode, user_attr); - case EXFAT_IOCTL_SET_ATTRIBUTES: + case FAT_IOCTL_SET_ATTRIBUTES: return exfat_ioctl_set_attributes(filp, user_attr); case FITRIM: return exfat_ioctl_fitrim(inode, arg); diff --git a/inode.c b/inode.c index 4d4449e1df3d..e4dffd734ba4 100644 --- a/inode.c +++ b/inode.c @@ -436,13 +436,13 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, if (err < len) exfat_write_failed(mapping, pos+len); - if (!(err < 0) && !(ei->attr & ATTR_ARCHIVE)) { + if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_ctime = current_time(inode); #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif - ei->attr |= ATTR_ARCHIVE; + ei->attr |= EXFAT_ATTR_ARCHIVE; mark_inode_dirty(inode); } @@ -629,7 +629,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) inode->i_generation = prandom_u32(); #endif - if (info->attr & ATTR_SUBDIR) { /* directory */ + if (info->attr & EXFAT_ATTR_SUBDIR) { /* directory */ inode->i_generation &= ~1; inode->i_mode = exfat_make_mode(sbi, info->attr, 0777); inode->i_op = &exfat_dir_inode_operations; diff --git a/namei.c b/namei.c index d43af646e229..5b2204cb2e4b 100644 --- a/namei.c +++ b/namei.c @@ -572,12 +572,12 @@ static int exfat_add_entry(struct inode *inode, const char *path, info->type = type; if (type == TYPE_FILE) { - info->attr = ATTR_ARCHIVE; + info->attr = EXFAT_ATTR_ARCHIVE; info->start_clu = EXFAT_EOF_CLUSTER; info->size = 0; info->num_subdirs = 0; } else { - info->attr = ATTR_SUBDIR; + info->attr = EXFAT_ATTR_SUBDIR; if (sbi->options.zero_size_dir) info->start_clu = EXFAT_EOF_CLUSTER; else @@ -1177,8 +1177,8 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, *epnew = *epold; if (exfat_get_entry_type(epnew) == TYPE_FILE) { - epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); - ei->attr |= ATTR_ARCHIVE; + epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); + ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(new_bh, sync); brelse(old_bh); @@ -1209,8 +1209,8 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, ei->entry = newentry; } else { if (exfat_get_entry_type(epold) == TYPE_FILE) { - epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); - ei->attr |= ATTR_ARCHIVE; + epold->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); + ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(old_bh, sync); brelse(old_bh); @@ -1258,8 +1258,8 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, *epnew = *epmov; if (exfat_get_entry_type(epnew) == TYPE_FILE) { - epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); - ei->attr |= ATTR_ARCHIVE; + epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE); + ei->attr |= EXFAT_ATTR_ARCHIVE; } exfat_update_bh(new_bh, IS_DIRSYNC(inode)); brelse(mov_bh); diff --git a/super.c b/super.c index 89f60d722c99..caf8fd4acf33 100644 --- a/super.c +++ b/super.c @@ -640,7 +640,7 @@ static int exfat_read_root(struct inode *inode) inode->i_version++; #endif inode->i_generation = 0; - inode->i_mode = exfat_make_mode(sbi, ATTR_SUBDIR, 0777); + inode->i_mode = exfat_make_mode(sbi, EXFAT_ATTR_SUBDIR, 0777); inode->i_op = &exfat_dir_inode_operations; inode->i_fop = &exfat_dir_operations; @@ -649,7 +649,7 @@ static int exfat_read_root(struct inode *inode) ei->i_size_aligned = i_size_read(inode); ei->i_size_ondisk = i_size_read(inode); - exfat_save_attr(inode, ATTR_SUBDIR); + exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = current_time(inode); From 98f36315064b54bf79e5db44839b800a6eccbb98 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 2 Nov 2023 22:52:14 +0900 Subject: [PATCH 07/18] exfat: fs: add CONFIG_BUFFER_HEAD Add a new config option that controls building the buffer_head code, and select it from all file systems and stacking drivers that need it. For the block device nodes and alternative iomap based buffered I/O path is provided when buffer_head support is not enabled, and iomap needs a a small tweak to define the IOMAP_F_BUFFER_HEAD flag to 0 to not call into the buffer_head code when it doesn't exist. Otherwise this is just Kconfig and ifdef changes. Signed-off-by: Christoph Hellwig Reviewed-by: Luis Chamberlain Reviewed-by: Johannes Thumshirn Link: https://lore.kernel.org/r/20230801172201.1923299-7-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/Kconfig b/Kconfig index b3e5e9ebabc8..e298907a829a 100644 --- a/Kconfig +++ b/Kconfig @@ -2,6 +2,7 @@ config EXFAT_FS tristate "exFAT filesystem support" + select BUFFER_HEAD select NLS select LEGACY_DIRECT_IO help From 6eb9af0626759d340afd146a7c0941975edd4eea Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:03:27 +0900 Subject: [PATCH 08/18] exfat: ensure that ctime is updated whenever the mtime is When removing entries from a directory, the ctime must also be updated alongside the mtime. Signed-off-by: Jeff Layton Message-Id: <20230705190309.579783-4-jlayton@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- namei.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/namei.c b/namei.c index 5b2204cb2e4b..01d1aefa1670 100644 --- a/namei.c +++ b/namei.c @@ -903,9 +903,9 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - dir->i_mtime = dir->i_atime = current_time(dir); + dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); #else - dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; + dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) @@ -915,9 +915,9 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_mtime = inode->i_atime = current_time(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); #else - inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; + inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); @@ -1111,9 +1111,9 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - dir->i_mtime = dir->i_atime = current_time(dir); + dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); #else - dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; + dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) @@ -1124,9 +1124,9 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_mtime = inode->i_atime = current_time(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); #else - inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); From 308d7f68ce8b4285ff2eb5795b86f0a7c23678c7 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 3 Nov 2023 14:15:57 +0900 Subject: [PATCH 09/18] exfat: convert to simple_rename_timestamp A rename potentially involves updating 4 different inode timestamps. Convert to the new simple_rename_timestamp helper function. Signed-off-by: Jeff Layton Message-Id: <20230705190309.579783-10-jlayton@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- namei.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/namei.c b/namei.c index 01d1aefa1670..ba6c604a48dd 100644 --- a/namei.c +++ b/namei.c @@ -1481,8 +1481,13 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, new_dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry); + EXFAT_I(new_dir)->i_crtime = current_time(new_dir); +#else new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = current_time(new_dir); +#endif #else new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC; @@ -1514,7 +1519,9 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, old_dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 6, 0) old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); +#endif #else old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC; #endif From c49f06ffabbbbc82d6693ae8f43ef2bfb1fa509b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:15:43 +0900 Subject: [PATCH 10/18] exfat: fs: pass the request_mask to generic_fillattr generic_fillattr just fills in the entire stat struct indiscriminately today, copying data from the inode. There is at least one attribute (STATX_CHANGE_COOKIE) that can have side effects when it is reported, and we're looking at adding more with the addition of multigrain timestamps. Add a request_mask argument to generic_fillattr and have most callers just pass in the value that is passed to getattr. Have other callers (e.g. ksmbd) just pass in STATX_BASIC_STATS. Also move the setting of STATX_CHANGE_COOKIE into generic_fillattr. Acked-by: Joseph Qi Reviewed-by: Xiubo Li Reviewed-by: "Paulo Alcantara (SUSE)" Reviewed-by: Jan Kara Signed-off-by: Jeff Layton Message-Id: <20230807-mgctime-v7-2-d1dec143a704@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/file.c b/file.c index 59848fa2793d..c82e5792e504 100644 --- a/file.c +++ b/file.c @@ -269,7 +269,11 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); +#else generic_fillattr(&nop_mnt_idmap, inode, stat); +#endif #else generic_fillattr(&init_user_ns, inode, stat); #endif From 0e040b023e2fb8e39719134533bbf683d262d35c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:32:26 +0900 Subject: [PATCH 11/18] exfat: convert to ctime accessor functions In later patches, we're going to change how the inode's ctime field is used. Switch to using accessor functions instead of raw accesses of inode->i_ctime. Signed-off-by: Jeff Layton Reviewed-by: Jan Kara Message-Id: <20230705190309.579783-38-jlayton@kernel.org> Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 8 ++++++++ inode.c | 12 ++++++++++++ namei.c | 36 ++++++++++++++++++++++++++++++++++++ super.c | 4 ++++ 4 files changed, 60 insertions(+) diff --git a/file.c b/file.c index c82e5792e504..2b74bd03e55e 100644 --- a/file.c +++ b/file.c @@ -30,7 +30,11 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) return err; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_ctime = inode->i_mtime = current_time(inode); +#endif #else inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; #endif @@ -363,7 +367,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) if (attr->ia_valid & ATTR_SIZE) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif diff --git a/inode.c b/inode.c index e4dffd734ba4..89e341e74640 100644 --- a/inode.c +++ b/inode.c @@ -380,7 +380,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) if (to > i_size_read(inode)) { truncate_pagecache(inode, i_size_read(inode)); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -438,7 +442,11 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -659,7 +667,11 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; inode->i_mtime = info->mtime; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode_set_ctime_to_ts(inode, info->mtime); +#else inode->i_ctime = info->mtime; +#endif ei->i_crtime = info->crtime; inode->i_atime = info->atime; diff --git a/namei.c b/namei.c index ba6c604a48dd..0dd33a0cfddc 100644 --- a/namei.c +++ b/namei.c @@ -625,7 +625,11 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = inode_set_ctime_current(dir); +#else dir->i_ctime = dir->i_mtime = current_time(dir); +#endif #else dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; #endif @@ -647,8 +651,12 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; @@ -903,7 +911,11 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); +#else dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); +#endif #else dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif @@ -915,7 +927,11 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif @@ -964,7 +980,11 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = inode_set_ctime_current(dir); +#else dir->i_ctime = dir->i_mtime = current_time(dir); +#endif #else dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; #endif @@ -986,8 +1006,12 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) inode->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; @@ -1111,7 +1135,11 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) dir->i_version++; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); +#else dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir); +#endif #else dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -1124,7 +1152,11 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) clear_nlink(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif @@ -1543,8 +1575,12 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, WARN_ON(new_inode->i_nlink == 0); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + EXFAT_I(new_inode)->i_crtime = current_time(new_inode); +#else new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = current_time(new_inode); +#endif #else new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = CURRENT_TIME_SEC; diff --git a/super.c b/super.c index caf8fd4acf33..0b09959c26e6 100644 --- a/super.c +++ b/super.c @@ -651,8 +651,12 @@ static int exfat_read_root(struct inode *inode) exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + inode->i_mtime = inode->i_atime = ei->i_crtime = inode_set_ctime_current(inode); +#else inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = current_time(inode); +#endif #else inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = CURRENT_TIME_SEC; From 59b349e724db3d04318735e2484f2f5aab1cdedd Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 2 Nov 2023 23:54:56 +0900 Subject: [PATCH 12/18] exfat: convert to new timestamp accessors Convert to using the new inode timestamp accessor functions. Signed-off-by: Jeff Layton Link: https://lore.kernel.org/r/20231004185347.80880-31-jlayton@kernel.org Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- exfat_fs.h | 3 +++ file.c | 12 ++++++++++++ inode.c | 34 ++++++++++++++++++++++++++++++++++ misc.c | 10 ++++++++++ namei.c | 43 +++++++++++++++++++++++++++++++++++++++++++ super.c | 5 +++++ 6 files changed, 107 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index 9c2266afe3f6..124dfd599c11 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -598,6 +598,9 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_cs); void exfat_truncate_atime(struct timespec64 *ts); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) +void exfat_truncate_inode_atime(struct inode *inode); +#endif void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); #else diff --git a/file.c b/file.c index 2b74bd03e55e..72879e405531 100644 --- a/file.c +++ b/file.c @@ -31,7 +31,11 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_ctime = inode->i_mtime = current_time(inode); #endif @@ -368,7 +372,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) if (attr->ia_valid & ATTR_SIZE) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_mtime = inode->i_ctime = current_time(inode); #endif @@ -376,6 +384,9 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) setattr_copy(&nop_mnt_idmap, inode, attr); @@ -386,6 +397,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) setattr_copy(inode, attr); #endif exfat_truncate_atime(&inode->i_atime); +#endif if (attr->ia_valid & ATTR_SIZE) { error = exfat_block_truncate_page(inode, attr->ia_size); diff --git a/inode.c b/inode.c index 89e341e74640..7d7f2cff8a05 100644 --- a/inode.c +++ b/inode.c @@ -28,6 +28,9 @@ int __exfat_write_inode(struct inode *inode, int sync) struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); bool is_dir = (ei->type == TYPE_DIR) ? true : false; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + struct timespec64 ts; +#endif if (inode->i_ino == EXFAT_ROOT_INO) return 0; @@ -57,6 +60,20 @@ int __exfat_write_inode(struct inode *inode, int sync) &ep->dentry.file.create_time, &ep->dentry.file.create_date, &ep->dentry.file.create_time_cs); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + exfat_set_entry_time(sbi, &ts, + &ep->dentry.file.modify_tz, + &ep->dentry.file.modify_time, + &ep->dentry.file.modify_date, + &ep->dentry.file.modify_time_cs); + inode_set_mtime_to_ts(inode, ts); + exfat_set_entry_time(sbi, &ts, + &ep->dentry.file.access_tz, + &ep->dentry.file.access_time, + &ep->dentry.file.access_date, + NULL); + inode_set_atime_to_ts(inode, ts); +#else exfat_set_entry_time(sbi, &inode->i_mtime, &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, @@ -67,6 +84,7 @@ int __exfat_write_inode(struct inode *inode, int sync) &ep->dentry.file.access_time, &ep->dentry.file.access_date, NULL); +#endif /* File size should be zero if there is no cluster allocated */ on_disk_size = i_size_read(inode); @@ -381,7 +399,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) truncate_pagecache(inode, i_size_read(inode)); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_mtime = inode->i_ctime = current_time(inode); #endif @@ -443,7 +465,11 @@ static int exfat_write_end(struct file *file, struct address_space *mapping, if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +#else inode->i_mtime = inode_set_ctime_current(inode); +#endif #else inode->i_mtime = inode->i_ctime = current_time(inode); #endif @@ -666,14 +692,22 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) exfat_save_attr(inode, info->attr); inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(inode, info->mtime); +#else inode->i_mtime = info->mtime; +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode_set_ctime_to_ts(inode, info->mtime); #else inode->i_ctime = info->mtime; #endif ei->i_crtime = info->crtime; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_atime_to_ts(inode, info->atime); +#else inode->i_atime = info->atime; +#endif return 0; } diff --git a/misc.c b/misc.c index e7ae6f629d6e..a744a1fb1e33 100644 --- a/misc.c +++ b/misc.c @@ -163,6 +163,16 @@ void exfat_truncate_atime(struct timespec *ts) ts->tv_nsec = 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) +void exfat_truncate_inode_atime(struct inode *inode) +{ + struct timespec64 atime = inode_get_atime(inode); + + exfat_truncate_atime(&atime); + inode_set_atime_to_ts(inode, atime); +} +#endif + u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type) { int i; diff --git a/namei.c b/namei.c index 0dd33a0cfddc..ef9a6df20998 100644 --- a/namei.c +++ b/namei.c @@ -626,7 +626,11 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); +#else dir->i_mtime = inode_set_ctime_current(dir); +#endif #else dir->i_ctime = dir->i_mtime = current_time(dir); #endif @@ -650,6 +654,10 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, inode->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); @@ -662,6 +670,7 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif /* timestamp is already written, so mark_inode_dirty() is unneeded. */ d_instantiate(dentry, inode); @@ -910,6 +919,11 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) #else dir->i_version++; #endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(dir); + exfat_truncate_inode_atime(dir); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); @@ -920,12 +934,17 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); +#endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else mark_inode_dirty(dir); clear_nlink(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); @@ -936,6 +955,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif exfat_unhash_inode(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); @@ -981,7 +1001,11 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); +#else dir->i_mtime = inode_set_ctime_current(dir); +#endif #else dir->i_ctime = dir->i_mtime = current_time(dir); #endif @@ -1005,6 +1029,10 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #else inode->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode); @@ -1017,6 +1045,7 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif /* timestamp is already written, so mark_inode_dirty() is unneeded. */ d_instantiate(dentry, inode); @@ -1134,6 +1163,10 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) #else dir->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(dir); + exfat_truncate_inode_atime(dir); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir); @@ -1144,6 +1177,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&dir->i_atime); +#endif if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else @@ -1151,6 +1185,10 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) drop_nlink(dir); clear_nlink(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode); @@ -1161,6 +1199,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif exfat_unhash_inode(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); @@ -1524,7 +1563,11 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + exfat_truncate_inode_atime(new_dir); +#else exfat_truncate_atime(&new_dir->i_atime); +#endif if (IS_DIRSYNC(new_dir)) exfat_sync_inode(new_dir); else diff --git a/super.c b/super.c index 0b09959c26e6..decf1c68323b 100644 --- a/super.c +++ b/super.c @@ -650,6 +650,10 @@ static int exfat_read_root(struct inode *inode) ei->i_size_ondisk = i_size_read(inode); exfat_save_attr(inode, EXFAT_ATTR_SUBDIR); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + ei->i_crtime = simple_inode_init_ts(inode); + exfat_truncate_inode_atime(inode); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) inode->i_mtime = inode->i_atime = ei->i_crtime = inode_set_ctime_current(inode); @@ -662,6 +666,7 @@ static int exfat_read_root(struct inode *inode) CURRENT_TIME_SEC; #endif exfat_truncate_atime(&inode->i_atime); +#endif return 0; } From 327699f41c880e71baf82b87d60bac73f78dda87 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 3 Nov 2023 08:51:38 +0900 Subject: [PATCH 13/18] exfat: fix setting uninitialized time to ctime/atime An uninitialized time is set to ctime/atime in __exfat_write_inode(). It causes xfstests generic/003 and generic/192 to fail. And since there will be a time gap between setting ctime/atime to the inode and writing back the inode, so ctime/atime should not be set again when writing back the inode. Fixes: 4c72a36edd54 ("exfat: convert to new timestamp accessors") Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inode.c b/inode.c index 7d7f2cff8a05..479db4299c83 100644 --- a/inode.c +++ b/inode.c @@ -61,18 +61,18 @@ int __exfat_write_inode(struct inode *inode, int sync) &ep->dentry.file.create_date, &ep->dentry.file.create_time_cs); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + ts = inode_get_mtime(inode); exfat_set_entry_time(sbi, &ts, &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, &ep->dentry.file.modify_date, &ep->dentry.file.modify_time_cs); - inode_set_mtime_to_ts(inode, ts); + ts = inode_get_atime(inode); exfat_set_entry_time(sbi, &ts, &ep->dentry.file.access_tz, &ep->dentry.file.access_time, &ep->dentry.file.access_date, NULL); - inode_set_atime_to_ts(inode, ts); #else exfat_set_entry_time(sbi, &inode->i_mtime, &ep->dentry.file.modify_tz, From 3ad149047d1acf158f327b3f0279d3d8b03bc894 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 3 Nov 2023 08:52:47 +0900 Subject: [PATCH 14/18] exfat: fix ctime is not updated Commit 4c72a36edd54 removed attr_copy() from exfat_set_attr(). It causes xfstests generic/221 to fail. In xfstests generic/221, it tests ctime should be updated even if futimens() update atime only. But in this case, ctime will not be updated if attr_copy() is removed. attr_copy() may also update other attributes, and removing it may cause other bugs, so this commit restores to call attr_copy() in exfat_set_attr(). Fixes: 4c72a36edd54 ("exfat: convert to new timestamp accessors") Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- file.c | 1 + 1 file changed, 1 insertion(+) diff --git a/file.c b/file.c index 72879e405531..749c349cbfba 100644 --- a/file.c +++ b/file.c @@ -385,6 +385,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0) + setattr_copy(&nop_mnt_idmap, inode, attr); exfat_truncate_inode_atime(inode); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) From 2a0d7a848f60be6a16202ac60328d9ae17757665 Mon Sep 17 00:00:00 2001 From: John Sanpe Date: Fri, 29 Dec 2023 23:04:09 +0900 Subject: [PATCH 15/18] exfat: using hweight instead of internal logic Replace the internal table lookup algorithm with the hweight library, which has instruction set acceleration capabilities. Use it to increase the length of a single calculation of the exfat_find_free_bitmap function to the long type. Signed-off-by: John Sanpe Signed-off-by: Namjae Jeon --- balloc.c | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/balloc.c b/balloc.c index c226d5d1c36b..e602bea72226 100644 --- a/balloc.c +++ b/balloc.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include @@ -17,6 +18,16 @@ #include "exfat_raw.h" #include "exfat_fs.h" +#if BITS_PER_LONG == 32 +#define __le_long __le32 +#define lel_to_cpu(A) le32_to_cpu(A) +#elif BITS_PER_LONG == 64 +#define __le_long __le64 +#define lel_to_cpu(A) le64_to_cpu(A) +#else +#error "BITS_PER_LONG not 32 or 64" +#endif + static const unsigned char free_bit[] = { 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/* 0 ~ 19*/ 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~ 39*/ @@ -33,22 +44,6 @@ static const unsigned char free_bit[] = { 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /*240 ~ 254*/ }; -static const unsigned char used_bit[] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,/* 0 ~ 19*/ - 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,/* 20 ~ 39*/ - 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,/* 40 ~ 59*/ - 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,/* 60 ~ 79*/ - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,/* 80 ~ 99*/ - 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,/*100 ~ 119*/ - 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4,/*120 ~ 139*/ - 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,/*140 ~ 159*/ - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,/*160 ~ 179*/ - 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,/*180 ~ 199*/ - 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,/*200 ~ 219*/ - 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,/*220 ~ 239*/ - 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 /*240 ~ 255*/ -}; - /* * Allocation Bitmap Management Functions */ @@ -255,25 +250,24 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count) unsigned int count = 0; unsigned int i, map_i = 0, map_b = 0; unsigned int total_clus = EXFAT_DATA_CLUSTER_COUNT(sbi); - unsigned int last_mask = total_clus & BITS_PER_BYTE_MASK; - unsigned char clu_bits; - const unsigned char last_bit_mask[] = {0, 0b00000001, 0b00000011, - 0b00000111, 0b00001111, 0b00011111, 0b00111111, 0b01111111}; + unsigned int last_mask = total_clus & (BITS_PER_LONG - 1); + unsigned long *bitmap, clu_bits; total_clus &= ~last_mask; - for (i = 0; i < total_clus; i += BITS_PER_BYTE) { - clu_bits = *(sbi->vol_amap[map_i]->b_data + map_b); - count += used_bit[clu_bits]; - if (++map_b >= (unsigned int)sb->s_blocksize) { + for (i = 0; i < total_clus; i += BITS_PER_LONG) { + bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b); + count += hweight_long(*bitmap); + map_b += sizeof(long); + if (map_b >= (unsigned int)sb->s_blocksize) { map_i++; map_b = 0; } } if (last_mask) { - clu_bits = *(sbi->vol_amap[map_i]->b_data + map_b); - clu_bits &= last_bit_mask[last_mask]; - count += used_bit[clu_bits]; + bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b); + clu_bits = lel_to_cpu(*(__le_long *)bitmap); + count += hweight_long(clu_bits & BITMAP_LAST_WORD_MASK(last_mask)); } *ret_count = count; From b1cc7d5aa34b0fdd67df01fc1696e0a25ab1d08c Mon Sep 17 00:00:00 2001 From: John Sanpe Date: Mon, 11 Dec 2023 09:26:13 +0900 Subject: [PATCH 16/18] exfat: using ffs instead of internal logic Replaced the internal table lookup algorithm with ffs of the bitops library with better performance. Use it to increase the single processing length of the exfat_find_free_bitmap function, from single-byte search to long type. Signed-off-by: John Sanpe Signed-off-by: Namjae Jeon --- balloc.c | 41 +++++++++++++++-------------------------- exfat_fs.h | 3 +-- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/balloc.c b/balloc.c index e602bea72226..63967f60ce84 100644 --- a/balloc.c +++ b/balloc.c @@ -21,29 +21,15 @@ #if BITS_PER_LONG == 32 #define __le_long __le32 #define lel_to_cpu(A) le32_to_cpu(A) +#define cpu_to_lel(A) cpu_to_le32(A) #elif BITS_PER_LONG == 64 #define __le_long __le64 #define lel_to_cpu(A) le64_to_cpu(A) +#define cpu_to_lel(A) cpu_to_le64(A) #else #error "BITS_PER_LONG not 32 or 64" #endif -static const unsigned char free_bit[] = { - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/* 0 ~ 19*/ - 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~ 39*/ - 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/* 40 ~ 59*/ - 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/* 60 ~ 79*/ - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2,/* 80 ~ 99*/ - 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3,/*100 ~ 119*/ - 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*120 ~ 139*/ - 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,/*140 ~ 159*/ - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/*160 ~ 179*/ - 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3,/*180 ~ 199*/ - 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*200 ~ 219*/ - 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/*220 ~ 239*/ - 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /*240 ~ 254*/ -}; - /* * Allocation Bitmap Management Functions */ @@ -206,32 +192,35 @@ unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu) { unsigned int i, map_i, map_b, ent_idx; unsigned int clu_base, clu_free; - unsigned char k, clu_mask; + unsigned long clu_bits, clu_mask; struct exfat_sb_info *sbi = EXFAT_SB(sb); + __le_long bitval; WARN_ON(clu < EXFAT_FIRST_CLUSTER); - ent_idx = CLUSTER_TO_BITMAP_ENT(clu); - clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx & ~(BITS_PER_BYTE_MASK)); + ent_idx = ALIGN_DOWN(CLUSTER_TO_BITMAP_ENT(clu), BITS_PER_LONG); + clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx); clu_mask = IGNORED_BITS_REMAINED(clu, clu_base); map_i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); map_b = BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent_idx); for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters; - i += BITS_PER_BYTE) { - k = *(sbi->vol_amap[map_i]->b_data + map_b); + i += BITS_PER_LONG) { + bitval = *(__le_long *)(sbi->vol_amap[map_i]->b_data + map_b); if (clu_mask > 0) { - k |= clu_mask; + bitval |= cpu_to_lel(clu_mask); clu_mask = 0; } - if (k < 0xFF) { - clu_free = clu_base + free_bit[k]; + if (lel_to_cpu(bitval) != ULONG_MAX) { + clu_bits = lel_to_cpu(bitval); + clu_free = clu_base + ffz(clu_bits); if (clu_free < sbi->num_clusters) return clu_free; } - clu_base += BITS_PER_BYTE; + clu_base += BITS_PER_LONG; + map_b += sizeof(long); - if (++map_b >= sb->s_blocksize || + if (map_b >= sb->s_blocksize || clu_base >= sbi->num_clusters) { if (++map_i >= sbi->map_sectors) { clu_base = EXFAT_FIRST_CLUSTER; diff --git a/exfat_fs.h b/exfat_fs.h index 124dfd599c11..6bf1c67daefb 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -148,8 +148,7 @@ enum { #define BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent) (ent & BITS_PER_SECTOR_MASK(sb)) #define BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent) \ ((ent / BITS_PER_BYTE) & ((sb)->s_blocksize - 1)) -#define BITS_PER_BYTE_MASK 0x7 -#define IGNORED_BITS_REMAINED(clu, clu_base) ((1 << ((clu) - (clu_base))) - 1) +#define IGNORED_BITS_REMAINED(clu, clu_base) ((1UL << ((clu) - (clu_base))) - 1) #define ES_ENTRY_NUM(name_len) (ES_IDX_LAST_FILENAME(name_len) + 1) /* 19 entries = 1 file entry + 1 stream entry + 17 filename entries */ From e9aa9b7d679302247de92e552dc250268298f18d Mon Sep 17 00:00:00 2001 From: HiGarfield Date: Sat, 30 Dec 2023 01:05:35 +0800 Subject: [PATCH 17/18] exfat: fix ALIGN_DOWN undefined error --- balloc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/balloc.c b/balloc.c index 63967f60ce84..5aa9b4ab80de 100644 --- a/balloc.c +++ b/balloc.c @@ -30,6 +30,10 @@ #error "BITS_PER_LONG not 32 or 64" #endif +#ifndef ALIGN_DOWN +#define ALIGN_DOWN(x, a) __ALIGN_KERNEL((x) - ((a) - 1), (a)) +#endif + /* * Allocation Bitmap Management Functions */ From ee8b3d9e325878d8163892114593e51256dcdc50 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 30 Dec 2023 10:13:29 +0900 Subject: [PATCH 18/18] exfat: github action: run buiuld and tests on for-kernel-version-from-4.1.0 branch Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 6587fd98bd41..277727301c8e 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -2,9 +2,9 @@ name: linux-exfat-oot CI on: push: - branches: [ "master" ] + branches: [ "for-kernel-version-from-4.1.0" ] pull_request: - branches: [ "master" ] + branches: [ "for-kernel-version-from-4.1.0" ] jobs: build: