From 50e2b33a90ddfa399bb92cbe1975f0059f189fd6 Mon Sep 17 00:00:00 2001 From: Kmods SIG Date: Jan 13 2022 23:40:34 +0000 Subject: Switch to EL kernel source and versioning --- diff --git a/.gitignore b/.gitignore index aa9bb68..0fb61c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/exfat-5.16.tar.xz +SOURCES/exfat-4.18.0-0.el8.tar.xz diff --git a/.kmod-exfat.metadata b/.kmod-exfat.metadata index 5e5e358..becc2f9 100644 --- a/.kmod-exfat.metadata +++ b/.kmod-exfat.metadata @@ -1 +1 @@ -1c5b3b2db1a0ed514355034fb687fa54982eef07 SOURCES/exfat-5.16.tar.xz +e61da1b076ce9fba5c0427ffb5b9cdd6091dc4be SOURCES/exfat-4.18.0-0.el8.tar.xz diff --git a/SOURCES/0001-exfat-add-in-memory-and-on-disk-structures-and-heade.patch b/SOURCES/0001-exfat-add-in-memory-and-on-disk-structures-and-heade.patch new file mode 100644 index 0000000..27d1ce7 --- /dev/null +++ b/SOURCES/0001-exfat-add-in-memory-and-on-disk-structures-and-heade.patch @@ -0,0 +1,741 @@ +From 1acf1a564b6034b5af1e7fb23cb98cb3bb4f6003 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:32 +0900 +Subject: [Backport 1acf1a564b60] exfat: add in-memory and on-disk structures + and headers +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds in-memory and on-disk structures and headers. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/exfat_fs.h | 519 +++++++++++++++++++++++++++++++++++++++++++ + src/exfat_raw.h | 184 +++++++++++++++ + 2 files changed, 703 insertions(+) + create mode 100644 src/exfat_fs.h + create mode 100644 src/exfat_raw.h + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +new file mode 100644 +index 0000000000000000000000000000000000000000..67d4e46fb81003d0c367862ee986b37c80e7fa4e +--- /dev/null ++++ b/src/exfat_fs.h +@@ -0,0 +1,519 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#ifndef _EXFAT_FS_H ++#define _EXFAT_FS_H ++ ++#include ++#include ++#include ++ ++#define EXFAT_SUPER_MAGIC 0x2011BAB0UL ++#define EXFAT_ROOT_INO 1 ++ ++#define EXFAT_SB_DIRTY 0 ++ ++#define EXFAT_CLUSTERS_UNTRACKED (~0u) ++ ++/* ++ * exfat error flags ++ */ ++enum exfat_error_mode { ++ EXFAT_ERRORS_CONT, /* ignore error and continue */ ++ EXFAT_ERRORS_PANIC, /* panic on error */ ++ EXFAT_ERRORS_RO, /* remount r/o on error */ ++}; ++ ++/* ++ * exfat nls lossy flag ++ */ ++enum { ++ NLS_NAME_NO_LOSSY, /* no lossy */ ++ NLS_NAME_LOSSY, /* just detected incorrect filename(s) */ ++ NLS_NAME_OVERLEN, /* the length is over than its limit */ ++}; ++ ++#define EXFAT_HASH_BITS 8 ++#define EXFAT_HASH_SIZE (1UL << EXFAT_HASH_BITS) ++ ++/* ++ * Type Definitions ++ */ ++#define ES_2_ENTRIES 2 ++#define ES_ALL_ENTRIES 0 ++ ++#define DIR_DELETED 0xFFFF0321 ++ ++/* type values */ ++#define TYPE_UNUSED 0x0000 ++#define TYPE_DELETED 0x0001 ++#define TYPE_INVALID 0x0002 ++#define TYPE_CRITICAL_PRI 0x0100 ++#define TYPE_BITMAP 0x0101 ++#define TYPE_UPCASE 0x0102 ++#define TYPE_VOLUME 0x0103 ++#define TYPE_DIR 0x0104 ++#define TYPE_FILE 0x011F ++#define TYPE_CRITICAL_SEC 0x0200 ++#define TYPE_STREAM 0x0201 ++#define TYPE_EXTEND 0x0202 ++#define TYPE_ACL 0x0203 ++#define TYPE_BENIGN_PRI 0x0400 ++#define TYPE_GUID 0x0401 ++#define TYPE_PADDING 0x0402 ++#define TYPE_ACLTAB 0x0403 ++#define TYPE_BENIGN_SEC 0x0800 ++#define TYPE_ALL 0x0FFF ++ ++#define MAX_CHARSET_SIZE 6 /* max size of multi-byte character */ ++#define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ ++#define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH + 1) * MAX_CHARSET_SIZE) ++ ++#define FAT_CACHE_SIZE 128 ++#define FAT_CACHE_HASH_SIZE 64 ++#define BUF_CACHE_SIZE 256 ++#define BUF_CACHE_HASH_SIZE 64 ++ ++#define EXFAT_HINT_NONE -1 ++#define EXFAT_MIN_SUBDIR 2 ++ ++/* ++ * helpers for cluster size to byte conversion. ++ */ ++#define EXFAT_CLU_TO_B(b, sbi) ((b) << (sbi)->cluster_size_bits) ++#define EXFAT_B_TO_CLU(b, sbi) ((b) >> (sbi)->cluster_size_bits) ++#define EXFAT_B_TO_CLU_ROUND_UP(b, sbi) \ ++ (((b - 1) >> (sbi)->cluster_size_bits) + 1) ++#define EXFAT_CLU_OFFSET(off, sbi) ((off) & ((sbi)->cluster_size - 1)) ++ ++/* ++ * helpers for block size to byte conversion. ++ */ ++#define EXFAT_BLK_TO_B(b, sb) ((b) << (sb)->s_blocksize_bits) ++#define EXFAT_B_TO_BLK(b, sb) ((b) >> (sb)->s_blocksize_bits) ++#define EXFAT_B_TO_BLK_ROUND_UP(b, sb) \ ++ (((b - 1) >> (sb)->s_blocksize_bits) + 1) ++#define EXFAT_BLK_OFFSET(off, sb) ((off) & ((sb)->s_blocksize - 1)) ++ ++/* ++ * helpers for block size to dentry size conversion. ++ */ ++#define EXFAT_B_TO_DEN_IDX(b, sbi) \ ++ ((b) << ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS)) ++#define EXFAT_B_TO_DEN(b) ((b) >> DENTRY_SIZE_BITS) ++#define EXFAT_DEN_TO_B(b) ((b) << DENTRY_SIZE_BITS) ++ ++/* ++ * helpers for fat entry. ++ */ ++#define FAT_ENT_SIZE (4) ++#define FAT_ENT_SIZE_BITS (2) ++#define FAT_ENT_OFFSET_SECTOR(sb, loc) (EXFAT_SB(sb)->FAT1_start_sector + \ ++ (((u64)loc << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits)) ++#define FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc) \ ++ ((loc << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1)) ++ ++/* ++ * helpers for bitmap. ++ */ ++#define CLUSTER_TO_BITMAP_ENT(clu) ((clu) - EXFAT_RESERVED_CLUSTERS) ++#define BITMAP_ENT_TO_CLUSTER(ent) ((ent) + EXFAT_RESERVED_CLUSTERS) ++#define BITS_PER_SECTOR(sb) ((sb)->s_blocksize * BITS_PER_BYTE) ++#define BITS_PER_SECTOR_MASK(sb) (BITS_PER_SECTOR(sb) - 1) ++#define BITMAP_OFFSET_SECTOR_INDEX(sb, ent) \ ++ ((ent / BITS_PER_BYTE) >> (sb)->s_blocksize_bits) ++#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) ++ ++struct exfat_dentry_namebuf { ++ char *lfn; ++ int lfnbuf_len; /* usally MAX_UNINAME_BUF_SIZE */ ++}; ++ ++/* unicode name structure */ ++struct exfat_uni_name { ++ /* +3 for null and for converting */ ++ unsigned short name[MAX_NAME_LENGTH + 3]; ++ unsigned short name_hash; ++ unsigned char name_len; ++}; ++ ++/* directory structure */ ++struct exfat_chain { ++ unsigned int dir; ++ unsigned int size; ++ unsigned char flags; ++}; ++ ++/* first empty entry hint information */ ++struct exfat_hint_femp { ++ /* entry index of a directory */ ++ int eidx; ++ /* count of continuous empty entry */ ++ int count; ++ /* the cluster that first empty slot exists in */ ++ struct exfat_chain cur; ++}; ++ ++/* hint structure */ ++struct exfat_hint { ++ unsigned int clu; ++ union { ++ unsigned int off; /* cluster offset */ ++ int eidx; /* entry index */ ++ }; ++}; ++ ++struct exfat_entry_set_cache { ++ /* sector number that contains file_entry */ ++ sector_t sector; ++ /* byte offset in the sector */ ++ unsigned int offset; ++ /* flag in stream entry. 01 for cluster chain, 03 for contig. */ ++ int alloc_flag; ++ unsigned int num_entries; ++ struct exfat_dentry entries[]; ++}; ++ ++struct exfat_dir_entry { ++ struct exfat_chain dir; ++ int entry; ++ unsigned int type; ++ unsigned int start_clu; ++ unsigned char flags; ++ unsigned short attr; ++ loff_t size; ++ unsigned int num_subdirs; ++ struct timespec64 atime; ++ struct timespec64 mtime; ++ struct timespec64 crtime; ++ struct exfat_dentry_namebuf namebuf; ++}; ++ ++/* ++ * exfat mount in-memory data ++ */ ++struct exfat_mount_options { ++ kuid_t fs_uid; ++ kgid_t fs_gid; ++ unsigned short fs_fmask; ++ unsigned short fs_dmask; ++ /* permission for setting the [am]time */ ++ unsigned short allow_utime; ++ /* charset for filename input/display */ ++ char *iocharset; ++ /* 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 */ ++ int time_offset; /* Offset of timestamps from UTC (in minutes) */ ++}; ++ ++/* ++ * EXFAT file system superblock in-memory data ++ */ ++struct exfat_sb_info { ++ unsigned long long num_sectors; /* num of sectors in volume */ ++ unsigned int num_clusters; /* num of clusters in volume */ ++ unsigned int cluster_size; /* cluster size in bytes */ ++ unsigned int cluster_size_bits; ++ unsigned int sect_per_clus; /* cluster size in sectors */ ++ unsigned int sect_per_clus_bits; ++ unsigned long long FAT1_start_sector; /* FAT1 start sector */ ++ unsigned long long FAT2_start_sector; /* FAT2 start sector */ ++ unsigned long long data_start_sector; /* data area start sector */ ++ unsigned int num_FAT_sectors; /* num of FAT sectors */ ++ unsigned int root_dir; /* root dir cluster */ ++ unsigned int dentries_per_clu; /* num of dentries per cluster */ ++ unsigned int vol_flag; /* volume dirty flag */ ++ struct buffer_head *pbr_bh; /* buffer_head of PBR sector */ ++ ++ unsigned int map_clu; /* allocation bitmap start cluster */ ++ unsigned int map_sectors; /* num of allocation bitmap sectors */ ++ struct buffer_head **vol_amap; /* allocation bitmap */ ++ ++ unsigned short *vol_utbl; /* upcase table */ ++ ++ unsigned int clu_srch_ptr; /* cluster search pointer */ ++ unsigned int used_clusters; /* number of used clusters */ ++ ++ unsigned long s_state; ++ struct mutex s_lock; /* superblock lock */ ++ struct exfat_mount_options options; ++ struct nls_table *nls_io; /* Charset used for input and display */ ++ struct ratelimit_state ratelimit; ++ ++ spinlock_t inode_hash_lock; ++ struct hlist_head inode_hashtable[EXFAT_HASH_SIZE]; ++ ++ struct rcu_head rcu; ++}; ++ ++/* ++ * EXFAT file system inode in-memory data ++ */ ++struct exfat_inode_info { ++ struct exfat_chain dir; ++ int entry; ++ unsigned int type; ++ unsigned short attr; ++ unsigned int start_clu; ++ unsigned char flags; ++ /* ++ * the copy of low 32bit of i_version to check ++ * the validation of hint_stat. ++ */ ++ unsigned int version; ++ /* file offset or dentry index for readdir */ ++ loff_t rwoffset; ++ ++ /* hint for cluster last accessed */ ++ struct exfat_hint hint_bmap; ++ /* hint for entry index we try to lookup next time */ ++ struct exfat_hint hint_stat; ++ /* hint for first empty entry */ ++ struct exfat_hint_femp hint_femp; ++ ++ spinlock_t cache_lru_lock; ++ struct list_head cache_lru; ++ int nr_caches; ++ /* for avoiding the race between alloc and free */ ++ unsigned int cache_valid_id; ++ ++ /* ++ * NOTE: i_size_ondisk is 64bits, so must hold ->inode_lock to access. ++ * physically allocated size. ++ */ ++ loff_t i_size_ondisk; ++ /* block-aligned i_size (used in cont_write_begin) */ ++ loff_t i_size_aligned; ++ /* on-disk position of directory entry or 0 */ ++ loff_t i_pos; ++ /* hash by i_location */ ++ struct hlist_node i_hash_fat; ++ /* protect bmap against truncate */ ++ struct rw_semaphore truncate_lock; ++ struct inode vfs_inode; ++ /* File creation time */ ++ struct timespec64 i_crtime; ++}; ++ ++static inline struct exfat_sb_info *EXFAT_SB(struct super_block *sb) ++{ ++ return sb->s_fs_info; ++} ++ ++static inline struct exfat_inode_info *EXFAT_I(struct inode *inode) ++{ ++ return container_of(inode, struct exfat_inode_info, vfs_inode); ++} ++ ++/* ++ * If ->i_mode can't hold 0222 (i.e. ATTR_RO), we use ->i_attrs to ++ * save ATTR_RO instead of ->i_mode. ++ * ++ * If it's directory and !sbi->options.rodir, ATTR_RO isn't read-only ++ * bit, it's just used as flag for app. ++ */ ++static inline int exfat_mode_can_hold_ro(struct inode *inode) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); ++ ++ if (S_ISDIR(inode->i_mode)) ++ return 0; ++ ++ if ((~sbi->options.fs_fmask) & 0222) ++ return 1; ++ return 0; ++} ++ ++/* Convert attribute bits and a mask to the UNIX mode. */ ++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)) ++ mode &= ~0222; ++ ++ if (attr & ATTR_SUBDIR) ++ return (mode & ~sbi->options.fs_dmask) | S_IFDIR; ++ ++ return (mode & ~sbi->options.fs_fmask) | S_IFREG; ++} ++ ++/* Return the FAT attribute byte for this inode */ ++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; ++ if (exfat_mode_can_hold_ro(inode) && !(inode->i_mode & 0222)) ++ attr |= 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); ++ else ++ EXFAT_I(inode)->attr = attr & ATTR_RWMASK; ++} ++ ++static inline bool exfat_is_last_sector_in_cluster(struct exfat_sb_info *sbi, ++ sector_t sec) ++{ ++ return ((sec - sbi->data_start_sector + 1) & ++ ((1 << sbi->sect_per_clus_bits) - 1)) == 0; ++} ++ ++static inline sector_t exfat_cluster_to_sector(struct exfat_sb_info *sbi, ++ unsigned int clus) ++{ ++ return ((clus - EXFAT_RESERVED_CLUSTERS) << sbi->sect_per_clus_bits) + ++ sbi->data_start_sector; ++} ++ ++static inline int exfat_sector_to_cluster(struct exfat_sb_info *sbi, ++ sector_t sec) ++{ ++ return ((sec - sbi->data_start_sector) >> sbi->sect_per_clus_bits) + ++ EXFAT_RESERVED_CLUSTERS; ++} ++ ++/* super.c */ ++int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag); ++ ++/* fatent.c */ ++#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); ++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); ++int exfat_ent_set(struct super_block *sb, unsigned int loc, ++ unsigned int content); ++int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir, ++ int entry, struct exfat_dentry *p_entry); ++int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, ++ unsigned int len); ++int exfat_zeroed_cluster(struct inode *dir, unsigned int clu); ++int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, ++ unsigned int *ret_clu); ++int exfat_count_num_clusters(struct super_block *sb, ++ struct exfat_chain *p_chain, unsigned int *ret_count); ++ ++/* 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); ++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); ++ ++/* 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); ++int exfat_setattr(struct dentry *dentry, struct iattr *attr); ++int exfat_getattr(const struct path *path, struct kstat *stat, ++ unsigned int request_mask, unsigned int query_flags); ++ ++/* namei.c */ ++extern const struct dentry_operations exfat_dentry_ops; ++extern const struct dentry_operations exfat_utf8_dentry_ops; ++ ++/* cache.c */ ++int exfat_cache_init(void); ++void exfat_cache_shutdown(void); ++void exfat_cache_init_inode(struct inode *inode); ++void exfat_cache_inval_inode(struct inode *inode); ++int exfat_get_cluster(struct inode *inode, unsigned int cluster, ++ unsigned int *fclus, unsigned int *dclus, ++ unsigned int *last_dclus, int allow_eof); ++ ++/* dir.c */ ++extern const struct inode_operations exfat_dir_inode_operations; ++extern const struct file_operations exfat_dir_operations; ++unsigned int exfat_get_entry_type(struct exfat_dentry *p_entry); ++int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, ++ int entry, unsigned int type, unsigned int start_clu, ++ unsigned long long size); ++int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, ++ int entry, int num_entries, struct exfat_uni_name *p_uniname); ++int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, ++ int entry, int order, int num_entries); ++int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, ++ int entry); ++int exfat_update_dir_chksum_with_entry_set(struct super_block *sb, ++ struct exfat_entry_set_cache *es, int sync); ++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 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_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, ++ struct exfat_chain *p_dir, int entry, unsigned int type, ++ struct exfat_dentry **file_ep); ++int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); ++ ++/* inode.c */ ++extern const struct inode_operations exfat_file_inode_operations; ++void exfat_sync_inode(struct inode *inode); ++struct inode *exfat_build_inode(struct super_block *sb, ++ struct exfat_dir_entry *info, loff_t i_pos); ++void exfat_hash_inode(struct inode *inode, loff_t i_pos); ++void exfat_unhash_inode(struct inode *inode); ++struct inode *exfat_iget(struct super_block *sb, loff_t i_pos); ++int exfat_write_inode(struct inode *inode, struct writeback_control *wbc); ++void exfat_evict_inode(struct inode *inode); ++int exfat_block_truncate_page(struct inode *inode, loff_t from); ++ ++/* exfat/nls.c */ ++unsigned short exfat_toupper(struct super_block *sb, unsigned short a); ++int exfat_uniname_ncmp(struct super_block *sb, unsigned short *a, ++ unsigned short *b, unsigned int len); ++int exfat_utf16_to_nls(struct super_block *sb, ++ struct exfat_uni_name *uniname, unsigned char *p_cstring, ++ int len); ++int exfat_nls_to_utf16(struct super_block *sb, ++ const unsigned char *p_cstring, const int len, ++ struct exfat_uni_name *uniname, int *p_lossy); ++int exfat_create_upcase_table(struct super_block *sb); ++void exfat_free_upcase_table(struct exfat_sb_info *sbi); ++unsigned short exfat_high_surrogate(unicode_t u); ++unsigned short exfat_low_surrogate(unicode_t u); ++ ++/* exfat/misc.c */ ++void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) ++ __printf(3, 4) __cold; ++#define exfat_fs_error(sb, fmt, args...) \ ++ __exfat_fs_error(sb, 1, fmt, ## args) ++#define exfat_fs_error_ratelimit(sb, fmt, args...) \ ++ __exfat_fs_error(sb, __ratelimit(&EXFAT_SB(sb)->ratelimit), \ ++ fmt, ## args) ++void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) ++ __printf(3, 4) __cold; ++void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, ++ u8 tz, __le16 time, __le16 date, u8 time_ms); ++void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, ++ u8 *tz, __le16 *time, __le16 *date, u8 *time_ms); ++unsigned short exfat_calc_chksum_2byte(void *data, int len, ++ unsigned short chksum, int type); ++void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync); ++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 */ +diff --git a/src/exfat_raw.h b/src/exfat_raw.h +new file mode 100644 +index 0000000000000000000000000000000000000000..2a841010e6490ccafd5d12844a48a93fc6ee4bbf +--- /dev/null ++++ b/src/exfat_raw.h +@@ -0,0 +1,184 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#ifndef _EXFAT_RAW_H ++#define _EXFAT_RAW_H ++ ++#include ++ ++#define PBR_SIGNATURE 0xAA55 ++ ++#define EXFAT_MAX_FILE_LEN 255 ++ ++#define VOL_CLEAN 0x0000 ++#define VOL_DIRTY 0x0002 ++ ++#define EXFAT_EOF_CLUSTER 0xFFFFFFFFu ++#define EXFAT_BAD_CLUSTER 0xFFFFFFF7u ++#define EXFAT_FREE_CLUSTER 0 ++/* Cluster 0, 1 are reserved, the first cluster is 2 in the cluster heap. */ ++#define EXFAT_RESERVED_CLUSTERS 2 ++#define EXFAT_FIRST_CLUSTER 2 ++#define EXFAT_DATA_CLUSTER_COUNT(sbi) \ ++ ((sbi)->num_clusters - EXFAT_RESERVED_CLUSTERS) ++ ++/* AllocationPossible and NoFatChain field in GeneralSecondaryFlags Field */ ++#define ALLOC_FAT_CHAIN 0x01 ++#define ALLOC_NO_FAT_CHAIN 0x03 ++ ++#define DENTRY_SIZE 32 /* directory entry size */ ++#define DENTRY_SIZE_BITS 5 ++/* exFAT allows 8388608(256MB) directory entries */ ++#define MAX_EXFAT_DENTRIES 8388608 ++ ++/* dentry types */ ++#define EXFAT_UNUSED 0x00 /* end of directory */ ++#define EXFAT_DELETE (~0x80) ++#define IS_EXFAT_DELETED(x) ((x) < 0x80) /* deleted file (0x01~0x7F) */ ++#define EXFAT_INVAL 0x80 /* invalid value */ ++#define EXFAT_BITMAP 0x81 /* allocation bitmap */ ++#define EXFAT_UPCASE 0x82 /* upcase table */ ++#define EXFAT_VOLUME 0x83 /* volume label */ ++#define EXFAT_FILE 0x85 /* file or dir */ ++#define EXFAT_GUID 0xA0 ++#define EXFAT_PADDING 0xA1 ++#define EXFAT_ACLTAB 0xA2 ++#define EXFAT_STREAM 0xC0 /* stream entry */ ++#define EXFAT_NAME 0xC1 /* file name entry */ ++#define EXFAT_ACL 0xC2 /* stream entry */ ++ ++#define IS_EXFAT_CRITICAL_PRI(x) (x < 0xA0) ++#define IS_EXFAT_BENIGN_PRI(x) (x < 0xC0) ++#define IS_EXFAT_CRITICAL_SEC(x) (x < 0xE0) ++ ++/* checksum types */ ++#define CS_DIR_ENTRY 0 ++#define CS_PBR_SECTOR 1 ++#define CS_DEFAULT 2 ++ ++/* file attributes */ ++#define ATTR_READONLY 0x0001 ++#define ATTR_HIDDEN 0x0002 ++#define ATTR_SYSTEM 0x0004 ++#define ATTR_VOLUME 0x0008 ++#define ATTR_SUBDIR 0x0010 ++#define ATTR_ARCHIVE 0x0020 ++ ++#define ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ ++ ATTR_SUBDIR | ATTR_ARCHIVE) ++ ++#define PBR64_JUMP_BOOT_LEN 3 ++#define PBR64_OEM_NAME_LEN 8 ++#define PBR64_RESERVED_LEN 53 ++ ++#define EXFAT_FILE_NAME_LEN 15 ++ ++/* EXFAT BIOS parameter block (64 bytes) */ ++struct bpb64 { ++ __u8 jmp_boot[PBR64_JUMP_BOOT_LEN]; ++ __u8 oem_name[PBR64_OEM_NAME_LEN]; ++ __u8 res_zero[PBR64_RESERVED_LEN]; ++} __packed; ++ ++/* EXFAT EXTEND BIOS parameter block (56 bytes) */ ++struct bsx64 { ++ __le64 vol_offset; ++ __le64 vol_length; ++ __le32 fat_offset; ++ __le32 fat_length; ++ __le32 clu_offset; ++ __le32 clu_count; ++ __le32 root_cluster; ++ __le32 vol_serial; ++ __u8 fs_version[2]; ++ __le16 vol_flags; ++ __u8 sect_size_bits; ++ __u8 sect_per_clus_bits; ++ __u8 num_fats; ++ __u8 phy_drv_no; ++ __u8 perc_in_use; ++ __u8 reserved2[7]; ++} __packed; ++ ++/* EXFAT PBR[BPB+BSX] (120 bytes) */ ++struct pbr64 { ++ struct bpb64 bpb; ++ struct bsx64 bsx; ++} __packed; ++ ++/* Common PBR[Partition Boot Record] (512 bytes) */ ++struct pbr { ++ union { ++ __u8 raw[64]; ++ struct bpb64 f64; ++ } bpb; ++ union { ++ __u8 raw[56]; ++ struct bsx64 f64; ++ } bsx; ++ __u8 boot_code[390]; ++ __le16 signature; ++} __packed; ++ ++struct exfat_dentry { ++ __u8 type; ++ union { ++ struct { ++ __u8 num_ext; ++ __le16 checksum; ++ __le16 attr; ++ __le16 reserved1; ++ __le16 create_time; ++ __le16 create_date; ++ __le16 modify_time; ++ __le16 modify_date; ++ __le16 access_time; ++ __le16 access_date; ++ __u8 create_time_ms; ++ __u8 modify_time_ms; ++ __u8 create_tz; ++ __u8 modify_tz; ++ __u8 access_tz; ++ __u8 reserved2[7]; ++ } __packed file; /* file directory entry */ ++ struct { ++ __u8 flags; ++ __u8 reserved1; ++ __u8 name_len; ++ __le16 name_hash; ++ __le16 reserved2; ++ __le64 valid_size; ++ __le32 reserved3; ++ __le32 start_clu; ++ __le64 size; ++ } __packed stream; /* stream extension directory entry */ ++ struct { ++ __u8 flags; ++ __le16 unicode_0_14[EXFAT_FILE_NAME_LEN]; ++ } __packed name; /* file name directory entry */ ++ struct { ++ __u8 flags; ++ __u8 reserved[18]; ++ __le32 start_clu; ++ __le64 size; ++ } __packed bitmap; /* allocation bitmap directory entry */ ++ struct { ++ __u8 reserved1[3]; ++ __le32 checksum; ++ __u8 reserved2[12]; ++ __le32 start_clu; ++ __le64 size; ++ } __packed upcase; /* up-case table directory entry */ ++ } __packed dentry; ++} __packed; ++ ++#define EXFAT_TZ_VALID (1 << 7) ++ ++/* Jan 1 GMT 00:00:00 1980 */ ++#define EXFAT_MIN_TIMESTAMP_SECS 315532800LL ++/* Dec 31 GMT 23:59:59 2107 */ ++#define EXFAT_MAX_TIMESTAMP_SECS 4354819199LL ++ ++#endif /* !_EXFAT_RAW_H */ +-- +2.31.1 + diff --git a/SOURCES/0002-exfat-add-super-block-operations.patch b/SOURCES/0002-exfat-add-super-block-operations.patch new file mode 100644 index 0000000..868a485 --- /dev/null +++ b/SOURCES/0002-exfat-add-super-block-operations.patch @@ -0,0 +1,758 @@ +From 719c1e1829166da399903b13da6dcc5344e73449 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:33 +0900 +Subject: [Backport 719c1e182916] exfat: add super block operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of superblock operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Reviewed-by: Arnd Bergmann +Signed-off-by: Al Viro +--- + src/super.c | 728 +++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 728 insertions(+) + create mode 100644 src/super.c + +diff --git a/src/super.c b/src/super.c +new file mode 100644 +index 0000000000000000000000000000000000000000..f06e0b53e39325937be7512f01c031c93917d9a8 +--- /dev/null ++++ b/src/super.c +@@ -0,0 +1,728 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++static char exfat_default_iocharset[] = CONFIG_EXFAT_DEFAULT_IOCHARSET; ++static struct kmem_cache *exfat_inode_cachep; ++ ++static void exfat_free_iocharset(struct exfat_sb_info *sbi) ++{ ++ if (sbi->options.iocharset != exfat_default_iocharset) ++ kfree(sbi->options.iocharset); ++} ++ ++static void exfat_delayed_free(struct rcu_head *p) ++{ ++ struct exfat_sb_info *sbi = container_of(p, struct exfat_sb_info, rcu); ++ ++ unload_nls(sbi->nls_io); ++ exfat_free_iocharset(sbi); ++ exfat_free_upcase_table(sbi); ++ kfree(sbi); ++} ++ ++static void exfat_put_super(struct super_block *sb) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ mutex_lock(&sbi->s_lock); ++ if (test_and_clear_bit(EXFAT_SB_DIRTY, &sbi->s_state)) ++ sync_blockdev(sb->s_bdev); ++ exfat_set_vol_flags(sb, VOL_CLEAN); ++ exfat_free_bitmap(sbi); ++ mutex_unlock(&sbi->s_lock); ++ ++ call_rcu(&sbi->rcu, exfat_delayed_free); ++} ++ ++static int exfat_sync_fs(struct super_block *sb, int wait) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ int err = 0; ++ ++ /* If there are some dirty buffers in the bdev inode */ ++ mutex_lock(&sbi->s_lock); ++ if (test_and_clear_bit(EXFAT_SB_DIRTY, &sbi->s_state)) { ++ sync_blockdev(sb->s_bdev); ++ if (exfat_set_vol_flags(sb, VOL_CLEAN)) ++ err = -EIO; ++ } ++ mutex_unlock(&sbi->s_lock); ++ return err; ++} ++ ++static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) ++{ ++ struct super_block *sb = dentry->d_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned long long id = huge_encode_dev(sb->s_bdev->bd_dev); ++ ++ if (sbi->used_clusters == EXFAT_CLUSTERS_UNTRACKED) { ++ mutex_lock(&sbi->s_lock); ++ if (exfat_count_used_clusters(sb, &sbi->used_clusters)) { ++ mutex_unlock(&sbi->s_lock); ++ return -EIO; ++ } ++ mutex_unlock(&sbi->s_lock); ++ } ++ ++ buf->f_type = sb->s_magic; ++ buf->f_bsize = sbi->cluster_size; ++ buf->f_blocks = sbi->num_clusters - 2; /* clu 0 & 1 */ ++ buf->f_bfree = buf->f_blocks - sbi->used_clusters; ++ buf->f_bavail = buf->f_bfree; ++ buf->f_fsid.val[0] = (unsigned int)id; ++ buf->f_fsid.val[1] = (unsigned int)(id >> 32); ++ /* Unicode utf16 255 characters */ ++ buf->f_namelen = EXFAT_MAX_FILE_LEN * NLS_MAX_CHARSET_SIZE; ++ return 0; ++} ++ ++int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct pbr64 *bpb; ++ bool sync = 0; ++ ++ /* flags are not changed */ ++ if (sbi->vol_flag == new_flag) ++ return 0; ++ ++ sbi->vol_flag = new_flag; ++ ++ /* skip updating volume dirty flag, ++ * if this volume has been mounted with read-only ++ */ ++ if (sb_rdonly(sb)) ++ return 0; ++ ++ if (!sbi->pbr_bh) { ++ sbi->pbr_bh = sb_bread(sb, 0); ++ if (!sbi->pbr_bh) { ++ exfat_msg(sb, KERN_ERR, "failed to read boot sector"); ++ return -ENOMEM; ++ } ++ } ++ ++ bpb = (struct pbr64 *)sbi->pbr_bh->b_data; ++ bpb->bsx.vol_flags = cpu_to_le16(new_flag); ++ ++ if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->pbr_bh)) ++ sync = true; ++ else ++ sync = false; ++ ++ set_buffer_uptodate(sbi->pbr_bh); ++ mark_buffer_dirty(sbi->pbr_bh); ++ ++ if (sync) ++ sync_dirty_buffer(sbi->pbr_bh); ++ return 0; ++} ++ ++static int exfat_show_options(struct seq_file *m, struct dentry *root) ++{ ++ struct super_block *sb = root->d_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_mount_options *opts = &sbi->options; ++ ++ /* Show partition info */ ++ if (!uid_eq(opts->fs_uid, GLOBAL_ROOT_UID)) ++ seq_printf(m, ",uid=%u", ++ from_kuid_munged(&init_user_ns, opts->fs_uid)); ++ if (!gid_eq(opts->fs_gid, GLOBAL_ROOT_GID)) ++ seq_printf(m, ",gid=%u", ++ from_kgid_munged(&init_user_ns, opts->fs_gid)); ++ seq_printf(m, ",fmask=%04o,dmask=%04o", opts->fs_fmask, opts->fs_dmask); ++ if (opts->allow_utime) ++ seq_printf(m, ",allow_utime=%04o", opts->allow_utime); ++ if (opts->utf8) ++ seq_puts(m, ",iocharset=utf8"); ++ else if (sbi->nls_io) ++ seq_printf(m, ",iocharset=%s", sbi->nls_io->charset); ++ seq_printf(m, ",bps=%ld", sb->s_blocksize); ++ if (opts->errors == EXFAT_ERRORS_CONT) ++ seq_puts(m, ",errors=continue"); ++ else if (opts->errors == EXFAT_ERRORS_PANIC) ++ seq_puts(m, ",errors=panic"); ++ else ++ seq_puts(m, ",errors=remount-ro"); ++ if (opts->discard) ++ seq_puts(m, ",discard"); ++ if (opts->time_offset) ++ seq_printf(m, ",time_offset=%d", opts->time_offset); ++ return 0; ++} ++ ++static struct inode *exfat_alloc_inode(struct super_block *sb) ++{ ++ struct exfat_inode_info *ei; ++ ++ ei = kmem_cache_alloc(exfat_inode_cachep, GFP_NOFS); ++ if (!ei) ++ return NULL; ++ ++ init_rwsem(&ei->truncate_lock); ++ return &ei->vfs_inode; ++} ++ ++static void exfat_free_inode(struct inode *inode) ++{ ++ kmem_cache_free(exfat_inode_cachep, EXFAT_I(inode)); ++} ++ ++static const struct super_operations exfat_sops = { ++ .alloc_inode = exfat_alloc_inode, ++ .free_inode = exfat_free_inode, ++ .write_inode = exfat_write_inode, ++ .evict_inode = exfat_evict_inode, ++ .put_super = exfat_put_super, ++ .sync_fs = exfat_sync_fs, ++ .statfs = exfat_statfs, ++ .show_options = exfat_show_options, ++}; ++ ++enum { ++ Opt_uid, ++ Opt_gid, ++ Opt_umask, ++ Opt_dmask, ++ Opt_fmask, ++ Opt_allow_utime, ++ Opt_charset, ++ Opt_errors, ++ Opt_discard, ++ Opt_time_offset, ++}; ++ ++static const struct fs_parameter_spec exfat_param_specs[] = { ++ fsparam_u32("uid", Opt_uid), ++ fsparam_u32("gid", Opt_gid), ++ fsparam_u32oct("umask", Opt_umask), ++ fsparam_u32oct("dmask", Opt_dmask), ++ fsparam_u32oct("fmask", Opt_fmask), ++ fsparam_u32oct("allow_utime", Opt_allow_utime), ++ fsparam_string("iocharset", Opt_charset), ++ fsparam_enum("errors", Opt_errors), ++ fsparam_flag("discard", Opt_discard), ++ fsparam_s32("time_offset", Opt_time_offset), ++ {} ++}; ++ ++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 }, ++ {} ++}; ++ ++static const struct fs_parameter_description exfat_parameters = { ++ .name = "exfat", ++ .specs = exfat_param_specs, ++ .enums = exfat_param_enums, ++}; ++ ++static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) ++{ ++ struct exfat_sb_info *sbi = fc->s_fs_info; ++ struct exfat_mount_options *opts = &sbi->options; ++ struct fs_parse_result result; ++ int opt; ++ ++ opt = fs_parse(fc, &exfat_parameters, param, &result); ++ if (opt < 0) ++ return opt; ++ ++ switch (opt) { ++ case Opt_uid: ++ opts->fs_uid = make_kuid(current_user_ns(), result.uint_32); ++ break; ++ case Opt_gid: ++ opts->fs_gid = make_kgid(current_user_ns(), result.uint_32); ++ break; ++ case Opt_umask: ++ opts->fs_fmask = result.uint_32; ++ opts->fs_dmask = result.uint_32; ++ break; ++ case Opt_dmask: ++ opts->fs_dmask = result.uint_32; ++ break; ++ case Opt_fmask: ++ opts->fs_fmask = result.uint_32; ++ break; ++ case Opt_allow_utime: ++ opts->allow_utime = result.uint_32 & 0022; ++ break; ++ case Opt_charset: ++ exfat_free_iocharset(sbi); ++ opts->iocharset = kstrdup(param->string, GFP_KERNEL); ++ if (!opts->iocharset) ++ return -ENOMEM; ++ break; ++ case Opt_errors: ++ opts->errors = result.uint_32; ++ break; ++ case Opt_discard: ++ opts->discard = 1; ++ break; ++ case Opt_time_offset: ++ /* ++ * Make the limit 24 just in case someone invents something ++ * unusual. ++ */ ++ if (result.int_32 < -24 * 60 || result.int_32 > 24 * 60) ++ return -EINVAL; ++ opts->time_offset = result.int_32; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void exfat_hash_init(struct super_block *sb) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ int i; ++ ++ spin_lock_init(&sbi->inode_hash_lock); ++ for (i = 0; i < EXFAT_HASH_SIZE; i++) ++ INIT_HLIST_HEAD(&sbi->inode_hashtable[i]); ++} ++ ++static int exfat_read_root(struct inode *inode) ++{ ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct exfat_chain cdir; ++ int num_subdirs, num_clu = 0; ++ ++ exfat_chain_set(&ei->dir, sbi->root_dir, 0, ALLOC_FAT_CHAIN); ++ ei->entry = -1; ++ ei->start_clu = sbi->root_dir; ++ ei->flags = ALLOC_FAT_CHAIN; ++ ei->type = TYPE_DIR; ++ ei->version = 0; ++ ei->rwoffset = 0; ++ ei->hint_bmap.off = EXFAT_EOF_CLUSTER; ++ ei->hint_stat.eidx = 0; ++ ei->hint_stat.clu = sbi->root_dir; ++ ei->hint_femp.eidx = EXFAT_HINT_NONE; ++ ++ exfat_chain_set(&cdir, sbi->root_dir, 0, ALLOC_FAT_CHAIN); ++ if (exfat_count_num_clusters(sb, &cdir, &num_clu)) ++ return -EIO; ++ i_size_write(inode, num_clu << sbi->cluster_size_bits); ++ ++ num_subdirs = exfat_count_dir_entries(sb, &cdir); ++ if (num_subdirs < 0) ++ return -EIO; ++ set_nlink(inode, num_subdirs + EXFAT_MIN_SUBDIR); ++ ++ inode->i_uid = sbi->options.fs_uid; ++ inode->i_gid = sbi->options.fs_gid; ++ inode_inc_iversion(inode); ++ 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); ++ ++ exfat_save_attr(inode, ATTR_SUBDIR); ++ inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = ++ current_time(inode); ++ exfat_cache_init_inode(inode); ++ return 0; ++} ++ ++static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb, ++ struct buffer_head **prev_bh) ++{ ++ struct pbr *p_pbr = (struct pbr *) (*prev_bh)->b_data; ++ unsigned short logical_sect = 0; ++ ++ logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; ++ ++ if (!is_power_of_2(logical_sect) || ++ logical_sect < 512 || logical_sect > 4096) { ++ exfat_msg(sb, KERN_ERR, "bogus logical sector size %u", ++ logical_sect); ++ return NULL; ++ } ++ ++ if (logical_sect < sb->s_blocksize) { ++ exfat_msg(sb, KERN_ERR, ++ "logical sector size too small for device (logical sector size = %u)", ++ logical_sect); ++ return NULL; ++ } ++ ++ if (logical_sect > sb->s_blocksize) { ++ struct buffer_head *bh = NULL; ++ ++ __brelse(*prev_bh); ++ *prev_bh = NULL; ++ ++ if (!sb_set_blocksize(sb, logical_sect)) { ++ exfat_msg(sb, KERN_ERR, ++ "unable to set blocksize %u", logical_sect); ++ return NULL; ++ } ++ bh = sb_bread(sb, 0); ++ if (!bh) { ++ exfat_msg(sb, KERN_ERR, ++ "unable to read boot sector (logical sector size = %lu)", ++ sb->s_blocksize); ++ return NULL; ++ } ++ ++ *prev_bh = bh; ++ p_pbr = (struct pbr *) bh->b_data; ++ } ++ return p_pbr; ++} ++ ++/* mount the file system volume */ ++static int __exfat_fill_super(struct super_block *sb) ++{ ++ int ret; ++ struct pbr *p_pbr; ++ struct pbr64 *p_bpb; ++ struct buffer_head *bh; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ /* set block size to read super block */ ++ sb_min_blocksize(sb, 512); ++ ++ /* read boot sector */ ++ bh = sb_bread(sb, 0); ++ if (!bh) { ++ exfat_msg(sb, KERN_ERR, "unable to read boot sector"); ++ return -EIO; ++ } ++ ++ /* PRB is read */ ++ p_pbr = (struct pbr *)bh->b_data; ++ ++ /* check the validity of PBR */ ++ if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { ++ exfat_msg(sb, KERN_ERR, "invalid boot record signature"); ++ ret = -EINVAL; ++ goto free_bh; ++ } ++ ++ ++ /* check logical sector size */ ++ p_pbr = exfat_read_pbr_with_logical_sector(sb, &bh); ++ if (!p_pbr) { ++ ret = -EIO; ++ goto free_bh; ++ } ++ ++ /* ++ * res_zero field must be filled with zero to prevent mounting ++ * from FAT volume. ++ */ ++ if (memchr_inv(p_pbr->bpb.f64.res_zero, 0, ++ sizeof(p_pbr->bpb.f64.res_zero))) { ++ ret = -EINVAL; ++ goto free_bh; ++ } ++ ++ p_bpb = (struct pbr64 *)p_pbr; ++ if (!p_bpb->bsx.num_fats) { ++ exfat_msg(sb, KERN_ERR, "bogus number of FAT structure"); ++ ret = -EINVAL; ++ goto free_bh; ++ } ++ ++ sbi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits; ++ sbi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits; ++ sbi->cluster_size_bits = sbi->sect_per_clus_bits + sb->s_blocksize_bits; ++ sbi->cluster_size = 1 << sbi->cluster_size_bits; ++ sbi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length); ++ sbi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset); ++ sbi->FAT2_start_sector = p_bpb->bsx.num_fats == 1 ? ++ sbi->FAT1_start_sector : ++ sbi->FAT1_start_sector + sbi->num_FAT_sectors; ++ sbi->data_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset); ++ sbi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length); ++ /* because the cluster index starts with 2 */ ++ sbi->num_clusters = le32_to_cpu(p_bpb->bsx.clu_count) + ++ EXFAT_RESERVED_CLUSTERS; ++ ++ sbi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster); ++ sbi->dentries_per_clu = 1 << ++ (sbi->cluster_size_bits - DENTRY_SIZE_BITS); ++ ++ sbi->vol_flag = le16_to_cpu(p_bpb->bsx.vol_flags); ++ sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; ++ sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; ++ ++ if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) { ++ sbi->vol_flag |= VOL_DIRTY; ++ exfat_msg(sb, KERN_WARNING, ++ "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); ++ } ++ ++ /* exFAT file size is limited by a disk volume size */ ++ sb->s_maxbytes = (u64)(sbi->num_clusters - EXFAT_RESERVED_CLUSTERS) << ++ sbi->cluster_size_bits; ++ ++ ret = exfat_create_upcase_table(sb); ++ if (ret) { ++ exfat_msg(sb, KERN_ERR, "failed to load upcase table"); ++ goto free_bh; ++ } ++ ++ ret = exfat_load_bitmap(sb); ++ if (ret) { ++ exfat_msg(sb, KERN_ERR, "failed to load alloc-bitmap"); ++ goto free_upcase_table; ++ } ++ ++ ret = exfat_count_used_clusters(sb, &sbi->used_clusters); ++ if (ret) { ++ exfat_msg(sb, KERN_ERR, "failed to scan clusters"); ++ goto free_alloc_bitmap; ++ } ++ ++ return 0; ++ ++free_alloc_bitmap: ++ exfat_free_bitmap(sbi); ++free_upcase_table: ++ exfat_free_upcase_table(sbi); ++free_bh: ++ brelse(bh); ++ return ret; ++} ++ ++static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) ++{ ++ struct exfat_sb_info *sbi = sb->s_fs_info; ++ struct exfat_mount_options *opts = &sbi->options; ++ struct inode *root_inode; ++ int err; ++ ++ if (opts->allow_utime == (unsigned short)-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; ++ } ++ ++ sb->s_flags |= SB_NODIRATIME; ++ sb->s_magic = EXFAT_SUPER_MAGIC; ++ sb->s_op = &exfat_sops; ++ ++ sb->s_time_gran = 1; ++ sb->s_time_min = EXFAT_MIN_TIMESTAMP_SECS; ++ sb->s_time_max = EXFAT_MAX_TIMESTAMP_SECS; ++ ++ err = __exfat_fill_super(sb); ++ if (err) { ++ exfat_msg(sb, KERN_ERR, "failed to recognize exfat type"); ++ goto check_nls_io; ++ } ++ ++ /* set up enough so that it can read an inode */ ++ exfat_hash_init(sb); ++ ++ if (!strcmp(sbi->options.iocharset, "utf8")) ++ opts->utf8 = 1; ++ else { ++ sbi->nls_io = load_nls(sbi->options.iocharset); ++ if (!sbi->nls_io) { ++ exfat_msg(sb, KERN_ERR, "IO charset %s not found", ++ sbi->options.iocharset); ++ err = -EINVAL; ++ goto free_table; ++ } ++ } ++ ++ if (sbi->options.utf8) ++ sb->s_d_op = &exfat_utf8_dentry_ops; ++ else ++ sb->s_d_op = &exfat_dentry_ops; ++ ++ root_inode = new_inode(sb); ++ if (!root_inode) { ++ exfat_msg(sb, KERN_ERR, "failed to allocate root inode."); ++ err = -ENOMEM; ++ goto free_table; ++ } ++ ++ root_inode->i_ino = EXFAT_ROOT_INO; ++ inode_set_iversion(root_inode, 1); ++ err = exfat_read_root(root_inode); ++ if (err) { ++ exfat_msg(sb, KERN_ERR, "failed to initialize root inode."); ++ goto put_inode; ++ } ++ ++ exfat_hash_inode(root_inode, EXFAT_I(root_inode)->i_pos); ++ insert_inode_hash(root_inode); ++ ++ sb->s_root = d_make_root(root_inode); ++ if (!sb->s_root) { ++ exfat_msg(sb, KERN_ERR, "failed to get the root dentry"); ++ err = -ENOMEM; ++ goto put_inode; ++ } ++ ++ return 0; ++ ++put_inode: ++ iput(root_inode); ++ sb->s_root = NULL; ++ ++free_table: ++ exfat_free_upcase_table(sbi); ++ exfat_free_bitmap(sbi); ++ ++check_nls_io: ++ unload_nls(sbi->nls_io); ++ exfat_free_iocharset(sbi); ++ sb->s_fs_info = NULL; ++ kfree(sbi); ++ return err; ++} ++ ++static int exfat_get_tree(struct fs_context *fc) ++{ ++ return get_tree_bdev(fc, exfat_fill_super); ++} ++ ++static void exfat_free(struct fs_context *fc) ++{ ++ kfree(fc->s_fs_info); ++} ++ ++static const struct fs_context_operations exfat_context_ops = { ++ .parse_param = exfat_parse_param, ++ .get_tree = exfat_get_tree, ++ .free = exfat_free, ++}; ++ ++static int exfat_init_fs_context(struct fs_context *fc) ++{ ++ struct exfat_sb_info *sbi; ++ ++ sbi = kzalloc(sizeof(struct exfat_sb_info), GFP_KERNEL); ++ if (!sbi) ++ return -ENOMEM; ++ ++ mutex_init(&sbi->s_lock); ++ ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, ++ DEFAULT_RATELIMIT_BURST); ++ ++ sbi->options.fs_uid = current_uid(); ++ sbi->options.fs_gid = current_gid(); ++ sbi->options.fs_fmask = current->fs->umask; ++ sbi->options.fs_dmask = current->fs->umask; ++ sbi->options.allow_utime = -1; ++ sbi->options.iocharset = exfat_default_iocharset; ++ sbi->options.errors = EXFAT_ERRORS_RO; ++ ++ fc->s_fs_info = sbi; ++ fc->ops = &exfat_context_ops; ++ return 0; ++} ++ ++static struct file_system_type exfat_fs_type = { ++ .owner = THIS_MODULE, ++ .name = "exfat", ++ .init_fs_context = exfat_init_fs_context, ++ .parameters = &exfat_parameters, ++ .kill_sb = kill_block_super, ++ .fs_flags = FS_REQUIRES_DEV, ++}; ++ ++static void exfat_inode_init_once(void *foo) ++{ ++ struct exfat_inode_info *ei = (struct exfat_inode_info *)foo; ++ ++ INIT_HLIST_NODE(&ei->i_hash_fat); ++ inode_init_once(&ei->vfs_inode); ++} ++ ++static int __init init_exfat_fs(void) ++{ ++ int err; ++ ++ err = exfat_cache_init(); ++ if (err) ++ return err; ++ ++ exfat_inode_cachep = kmem_cache_create("exfat_inode_cache", ++ sizeof(struct exfat_inode_info), ++ 0, SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD, ++ exfat_inode_init_once); ++ if (!exfat_inode_cachep) { ++ err = -ENOMEM; ++ goto shutdown_cache; ++ } ++ ++ err = register_filesystem(&exfat_fs_type); ++ if (err) ++ goto destroy_cache; ++ ++ return 0; ++ ++destroy_cache: ++ kmem_cache_destroy(exfat_inode_cachep); ++shutdown_cache: ++ exfat_cache_shutdown(); ++ return err; ++} ++ ++static void __exit exit_exfat_fs(void) ++{ ++ /* ++ * Make sure all delayed rcu free inodes are flushed before we ++ * destroy cache. ++ */ ++ rcu_barrier(); ++ kmem_cache_destroy(exfat_inode_cachep); ++ unregister_filesystem(&exfat_fs_type); ++ exfat_cache_shutdown(); ++} ++ ++module_init(init_exfat_fs); ++module_exit(exit_exfat_fs); ++ ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("exFAT filesystem support"); ++MODULE_AUTHOR("Samsung Electronics Co., Ltd."); +-- +2.31.1 + diff --git a/SOURCES/0003-exfat-add-inode-operations.patch b/SOURCES/0003-exfat-add-inode-operations.patch new file mode 100644 index 0000000..4d2f627 --- /dev/null +++ b/SOURCES/0003-exfat-add-inode-operations.patch @@ -0,0 +1,2156 @@ +From 5f2aa075070cf5b2e6aae011d5a3390408d7d913 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:34 +0900 +Subject: [Backport 5f2aa075070c] exfat: add inode operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of inode operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/inode.c | 671 +++++++++++++++++++++ + src/namei.c | 1448 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 2119 insertions(+) + create mode 100644 src/inode.c + create mode 100644 src/namei.c + +diff --git a/src/inode.c b/src/inode.c +new file mode 100644 +index 0000000000000000000000000000000000000000..06887492f54b791506d2f7c76eb3ef073b72026d +--- /dev/null ++++ b/src/inode.c +@@ -0,0 +1,671 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++static int __exfat_write_inode(struct inode *inode, int sync) ++{ ++ int ret = -EIO; ++ unsigned long long on_disk_size; ++ struct exfat_dentry *ep, *ep2; ++ struct exfat_entry_set_cache *es = NULL; ++ struct super_block *sb = inode->i_sb; ++ 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 (inode->i_ino == EXFAT_ROOT_INO) ++ return 0; ++ ++ /* ++ * If the indode is already unlinked, there is no need for updating it. ++ */ ++ if (ei->dir.dir == DIR_DELETED) ++ return 0; ++ ++ if (is_dir && ei->dir.dir == sbi->root_dir && ei->entry == -1) ++ return 0; ++ ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ ++ /* get the directory entry of given file or directory */ ++ es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES, ++ &ep); ++ if (!es) ++ return -EIO; ++ ep2 = ep + 1; ++ ++ ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); ++ ++ /* set FILE_INFO structure using the acquired struct exfat_dentry */ ++ exfat_set_entry_time(sbi, &ei->i_crtime, ++ &ep->dentry.file.create_tz, ++ &ep->dentry.file.create_time, ++ &ep->dentry.file.create_date, ++ &ep->dentry.file.create_time_ms); ++ exfat_set_entry_time(sbi, &inode->i_mtime, ++ &ep->dentry.file.modify_tz, ++ &ep->dentry.file.modify_time, ++ &ep->dentry.file.modify_date, ++ &ep->dentry.file.modify_time_ms); ++ exfat_set_entry_time(sbi, &inode->i_atime, ++ &ep->dentry.file.access_tz, ++ &ep->dentry.file.access_time, ++ &ep->dentry.file.access_date, ++ NULL); ++ ++ /* File size should be zero if there is no cluster allocated */ ++ on_disk_size = i_size_read(inode); ++ ++ if (ei->start_clu == EXFAT_EOF_CLUSTER) ++ on_disk_size = 0; ++ ++ ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size); ++ ep2->dentry.stream.size = ep2->dentry.stream.valid_size; ++ ++ ret = exfat_update_dir_chksum_with_entry_set(sb, es, sync); ++ kfree(es); ++ return ret; ++} ++ ++int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) ++{ ++ int ret; ++ ++ mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); ++ ret = __exfat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL); ++ mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); ++ ++ return ret; ++} ++ ++void exfat_sync_inode(struct inode *inode) ++{ ++ lockdep_assert_held(&EXFAT_SB(inode->i_sb)->s_lock); ++ __exfat_write_inode(inode, 1); ++} ++ ++/* ++ * Input: inode, (logical) clu_offset, target allocation area ++ * Output: errcode, cluster number ++ * *clu = (~0), if it's unable to allocate a new cluster ++ */ ++static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, ++ unsigned int *clu, int create) ++{ ++ int ret, modified = false; ++ unsigned int last_clu; ++ struct exfat_chain new_clu; ++ struct exfat_dentry *ep; ++ struct exfat_entry_set_cache *es = NULL; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ unsigned int local_clu_offset = clu_offset; ++ unsigned int num_to_be_allocated = 0, num_clusters = 0; ++ ++ ei->rwoffset = EXFAT_CLU_TO_B(clu_offset, sbi); ++ ++ if (EXFAT_I(inode)->i_size_ondisk > 0) ++ num_clusters = ++ EXFAT_B_TO_CLU_ROUND_UP(EXFAT_I(inode)->i_size_ondisk, ++ sbi); ++ ++ if (clu_offset >= num_clusters) ++ num_to_be_allocated = clu_offset - num_clusters + 1; ++ ++ if (!create && (num_to_be_allocated > 0)) { ++ *clu = EXFAT_EOF_CLUSTER; ++ return 0; ++ } ++ ++ *clu = last_clu = ei->start_clu; ++ ++ if (ei->flags == ALLOC_NO_FAT_CHAIN) { ++ if (clu_offset > 0 && *clu != EXFAT_EOF_CLUSTER) { ++ last_clu += clu_offset - 1; ++ ++ if (clu_offset == num_clusters) ++ *clu = EXFAT_EOF_CLUSTER; ++ else ++ *clu += clu_offset; ++ } ++ } else if (ei->type == TYPE_FILE) { ++ unsigned int fclus = 0; ++ int err = exfat_get_cluster(inode, clu_offset, ++ &fclus, clu, &last_clu, 1); ++ if (err) ++ return -EIO; ++ ++ clu_offset -= fclus; ++ } else { ++ /* hint information */ ++ if (clu_offset > 0 && ei->hint_bmap.off != EXFAT_EOF_CLUSTER && ++ ei->hint_bmap.off > 0 && clu_offset >= ei->hint_bmap.off) { ++ clu_offset -= ei->hint_bmap.off; ++ /* hint_bmap.clu should be valid */ ++ WARN_ON(ei->hint_bmap.clu < 2); ++ *clu = ei->hint_bmap.clu; ++ } ++ ++ while (clu_offset > 0 && *clu != EXFAT_EOF_CLUSTER) { ++ last_clu = *clu; ++ if (exfat_get_next_cluster(sb, clu)) ++ return -EIO; ++ clu_offset--; ++ } ++ } ++ ++ if (*clu == EXFAT_EOF_CLUSTER) { ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ ++ new_clu.dir = (last_clu == EXFAT_EOF_CLUSTER) ? ++ EXFAT_EOF_CLUSTER : last_clu + 1; ++ new_clu.size = 0; ++ new_clu.flags = ei->flags; ++ ++ /* allocate a cluster */ ++ if (num_to_be_allocated < 1) { ++ /* Broken FAT (i_sze > allocated FAT) */ ++ exfat_fs_error(sb, "broken FAT chain."); ++ return -EIO; ++ } ++ ++ ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu); ++ if (ret) ++ return ret; ++ ++ if (new_clu.dir == EXFAT_EOF_CLUSTER || ++ new_clu.dir == EXFAT_FREE_CLUSTER) { ++ exfat_fs_error(sb, ++ "bogus cluster new allocated (last_clu : %u, new_clu : %u)", ++ last_clu, new_clu.dir); ++ return -EIO; ++ } ++ ++ /* append to the FAT chain */ ++ if (last_clu == EXFAT_EOF_CLUSTER) { ++ if (new_clu.flags == ALLOC_FAT_CHAIN) ++ ei->flags = ALLOC_FAT_CHAIN; ++ ei->start_clu = new_clu.dir; ++ modified = true; ++ } else { ++ if (new_clu.flags != ei->flags) { ++ /* no-fat-chain bit is disabled, ++ * so fat-chain should be synced with ++ * alloc-bitmap ++ */ ++ exfat_chain_cont_cluster(sb, ei->start_clu, ++ num_clusters); ++ ei->flags = ALLOC_FAT_CHAIN; ++ modified = true; ++ } ++ if (new_clu.flags == ALLOC_FAT_CHAIN) ++ if (exfat_ent_set(sb, last_clu, new_clu.dir)) ++ return -EIO; ++ } ++ ++ num_clusters += num_to_be_allocated; ++ *clu = new_clu.dir; ++ ++ if (ei->dir.dir != DIR_DELETED) { ++ es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ++ ES_ALL_ENTRIES, &ep); ++ if (!es) ++ return -EIO; ++ /* get stream entry */ ++ ep++; ++ ++ /* update directory entry */ ++ if (modified) { ++ if (ep->dentry.stream.flags != ei->flags) ++ ep->dentry.stream.flags = ei->flags; ++ ++ if (le32_to_cpu(ep->dentry.stream.start_clu) != ++ ei->start_clu) ++ ep->dentry.stream.start_clu = ++ cpu_to_le32(ei->start_clu); ++ ++ ep->dentry.stream.valid_size = ++ cpu_to_le64(i_size_read(inode)); ++ ep->dentry.stream.size = ++ ep->dentry.stream.valid_size; ++ } ++ ++ if (exfat_update_dir_chksum_with_entry_set(sb, es, ++ inode_needs_sync(inode))) ++ return -EIO; ++ kfree(es); ++ ++ } /* end of if != DIR_DELETED */ ++ ++ inode->i_blocks += ++ num_to_be_allocated << sbi->sect_per_clus_bits; ++ ++ /* ++ * Move *clu pointer along FAT chains (hole care) because the ++ * caller of this function expect *clu to be the last cluster. ++ * This only works when num_to_be_allocated >= 2, ++ * *clu = (the first cluster of the allocated chain) => ++ * (the last cluster of ...) ++ */ ++ if (ei->flags == ALLOC_NO_FAT_CHAIN) { ++ *clu += num_to_be_allocated - 1; ++ } else { ++ while (num_to_be_allocated > 1) { ++ if (exfat_get_next_cluster(sb, clu)) ++ return -EIO; ++ num_to_be_allocated--; ++ } ++ } ++ ++ } ++ ++ /* hint information */ ++ ei->hint_bmap.off = local_clu_offset; ++ ei->hint_bmap.clu = *clu; ++ ++ return 0; ++} ++ ++static int exfat_map_new_buffer(struct exfat_inode_info *ei, ++ struct buffer_head *bh, loff_t pos) ++{ ++ if (buffer_delay(bh) && pos > ei->i_size_aligned) ++ return -EIO; ++ set_buffer_new(bh); ++ ++ /* ++ * Adjust i_size_aligned if i_size_ondisk is bigger than it. ++ */ ++ if (ei->i_size_ondisk > ei->i_size_aligned) ++ ei->i_size_aligned = ei->i_size_ondisk; ++ return 0; ++} ++ ++static int exfat_get_block(struct inode *inode, sector_t iblock, ++ struct buffer_head *bh_result, int create) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits; ++ int err = 0; ++ unsigned long mapped_blocks = 0; ++ unsigned int cluster, sec_offset; ++ sector_t last_block; ++ sector_t phys = 0; ++ loff_t pos; ++ ++ mutex_lock(&sbi->s_lock); ++ last_block = EXFAT_B_TO_BLK_ROUND_UP(i_size_read(inode), sb); ++ if (iblock >= last_block && !create) ++ goto done; ++ ++ /* Is this block already allocated? */ ++ err = exfat_map_cluster(inode, iblock >> sbi->sect_per_clus_bits, ++ &cluster, create); ++ if (err) { ++ if (err != -ENOSPC) ++ exfat_fs_error_ratelimit(sb, ++ "failed to bmap (inode : %p iblock : %llu, err : %d)", ++ inode, (unsigned long long)iblock, err); ++ goto unlock_ret; ++ } ++ ++ if (cluster == EXFAT_EOF_CLUSTER) ++ goto done; ++ ++ /* sector offset in cluster */ ++ sec_offset = iblock & (sbi->sect_per_clus - 1); ++ ++ phys = exfat_cluster_to_sector(sbi, cluster) + sec_offset; ++ mapped_blocks = sbi->sect_per_clus - sec_offset; ++ max_blocks = min(mapped_blocks, max_blocks); ++ ++ /* Treat newly added block / cluster */ ++ if (iblock < last_block) ++ create = 0; ++ ++ if (create || buffer_delay(bh_result)) { ++ pos = EXFAT_BLK_TO_B((iblock + 1), sb); ++ if (ei->i_size_ondisk < pos) ++ ei->i_size_ondisk = pos; ++ } ++ ++ if (create) { ++ err = exfat_map_new_buffer(ei, bh_result, pos); ++ if (err) { ++ exfat_fs_error(sb, ++ "requested for bmap out of range(pos : (%llu) > i_size_aligned(%llu)\n", ++ pos, ei->i_size_aligned); ++ goto unlock_ret; ++ } ++ } ++ ++ if (buffer_delay(bh_result)) ++ clear_buffer_delay(bh_result); ++ map_bh(bh_result, sb, phys); ++done: ++ bh_result->b_size = EXFAT_BLK_TO_B(max_blocks, sb); ++unlock_ret: ++ mutex_unlock(&sbi->s_lock); ++ return err; ++} ++ ++static int exfat_readpage(struct file *file, struct page *page) ++{ ++ return mpage_readpage(page, exfat_get_block); ++} ++ ++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); ++} ++ ++static int exfat_writepage(struct page *page, struct writeback_control *wbc) ++{ ++ return block_write_full_page(page, exfat_get_block, wbc); ++} ++ ++static int exfat_writepages(struct address_space *mapping, ++ struct writeback_control *wbc) ++{ ++ return mpage_writepages(mapping, wbc, exfat_get_block); ++} ++ ++static void exfat_write_failed(struct address_space *mapping, loff_t to) ++{ ++ struct inode *inode = mapping->host; ++ ++ if (to > i_size_read(inode)) { ++ truncate_pagecache(inode, i_size_read(inode)); ++ exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); ++ } ++} ++ ++static int exfat_write_begin(struct file *file, struct address_space *mapping, ++ loff_t pos, unsigned int len, unsigned int flags, ++ struct page **pagep, void **fsdata) ++{ ++ int ret; ++ ++ *pagep = NULL; ++ ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata, ++ exfat_get_block, ++ &EXFAT_I(mapping->host)->i_size_ondisk); ++ ++ if (ret < 0) ++ exfat_write_failed(mapping, pos+len); ++ ++ return ret; ++} ++ ++static int exfat_write_end(struct file *file, struct address_space *mapping, ++ loff_t pos, unsigned int len, unsigned int copied, ++ struct page *pagep, void *fsdata) ++{ ++ struct inode *inode = mapping->host; ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ int err; ++ ++ err = generic_write_end(file, mapping, pos, len, copied, pagep, fsdata); ++ ++ if (EXFAT_I(inode)->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); ++ return -EIO; ++ } ++ ++ if (err < len) ++ exfat_write_failed(mapping, pos+len); ++ ++ if (!(err < 0) && !(ei->attr & ATTR_ARCHIVE)) { ++ inode->i_mtime = inode->i_ctime = current_time(inode); ++ ei->attr |= ATTR_ARCHIVE; ++ mark_inode_dirty(inode); ++ } ++ ++ return err; ++} ++ ++static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) ++{ ++ struct address_space *mapping = iocb->ki_filp->f_mapping; ++ struct inode *inode = mapping->host; ++ loff_t size = iocb->ki_pos + iov_iter_count(iter); ++ int rw = iov_iter_rw(iter); ++ ssize_t ret; ++ ++ if (rw == WRITE) { ++ /* ++ * FIXME: blockdev_direct_IO() doesn't use ->write_begin(), ++ * so we need to update the ->i_size_aligned to block boundary. ++ * ++ * But we must fill the remaining area or hole by nul for ++ * updating ->i_size_aligned ++ * ++ * Return 0, and fallback to normal buffered write. ++ */ ++ if (EXFAT_I(inode)->i_size_aligned < size) ++ return 0; ++ } ++ ++ /* ++ * Need to use the DIO_LOCKING for avoiding the race ++ * condition of exfat_get_block() and ->truncate(). ++ */ ++ ret = blockdev_direct_IO(iocb, inode, iter, exfat_get_block); ++ if (ret < 0 && (rw & WRITE)) ++ exfat_write_failed(mapping, size); ++ return ret; ++} ++ ++static sector_t exfat_aop_bmap(struct address_space *mapping, sector_t block) ++{ ++ sector_t blocknr; ++ ++ /* exfat_get_cluster() assumes the requested blocknr isn't truncated. */ ++ down_read(&EXFAT_I(mapping->host)->truncate_lock); ++ blocknr = generic_block_bmap(mapping, block, exfat_get_block); ++ up_read(&EXFAT_I(mapping->host)->truncate_lock); ++ return blocknr; ++} ++ ++/* ++ * exfat_block_truncate_page() zeroes out a mapping from file offset `from' ++ * up to the end of the block which corresponds to `from'. ++ * This is required during truncate to physically zeroout the tail end ++ * of that block so it doesn't yield old data if the file is later grown. ++ * Also, avoid causing failure from fsx for cases of "data past EOF" ++ */ ++int exfat_block_truncate_page(struct inode *inode, loff_t from) ++{ ++ return block_truncate_page(inode->i_mapping, from, exfat_get_block); ++} ++ ++static const struct address_space_operations exfat_aops = { ++ .readpage = exfat_readpage, ++ .readpages = exfat_readpages, ++ .writepage = exfat_writepage, ++ .writepages = exfat_writepages, ++ .write_begin = exfat_write_begin, ++ .write_end = exfat_write_end, ++ .direct_IO = exfat_direct_IO, ++ .bmap = exfat_aop_bmap ++}; ++ ++static inline unsigned long exfat_hash(loff_t i_pos) ++{ ++ return hash_32(i_pos, EXFAT_HASH_BITS); ++} ++ ++void exfat_hash_inode(struct inode *inode, loff_t i_pos) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); ++ struct hlist_head *head = sbi->inode_hashtable + exfat_hash(i_pos); ++ ++ spin_lock(&sbi->inode_hash_lock); ++ EXFAT_I(inode)->i_pos = i_pos; ++ hlist_add_head(&EXFAT_I(inode)->i_hash_fat, head); ++ spin_unlock(&sbi->inode_hash_lock); ++} ++ ++void exfat_unhash_inode(struct inode *inode) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); ++ ++ spin_lock(&sbi->inode_hash_lock); ++ hlist_del_init(&EXFAT_I(inode)->i_hash_fat); ++ EXFAT_I(inode)->i_pos = 0; ++ spin_unlock(&sbi->inode_hash_lock); ++} ++ ++struct inode *exfat_iget(struct super_block *sb, loff_t i_pos) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *info; ++ struct hlist_head *head = sbi->inode_hashtable + exfat_hash(i_pos); ++ struct inode *inode = NULL; ++ ++ spin_lock(&sbi->inode_hash_lock); ++ hlist_for_each_entry(info, head, i_hash_fat) { ++ WARN_ON(info->vfs_inode.i_sb != sb); ++ ++ if (i_pos != info->i_pos) ++ continue; ++ inode = igrab(&info->vfs_inode); ++ if (inode) ++ break; ++ } ++ spin_unlock(&sbi->inode_hash_lock); ++ return inode; ++} ++ ++/* doesn't deal with root inode */ ++static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ loff_t size = info->size; ++ ++ memcpy(&ei->dir, &info->dir, sizeof(struct exfat_chain)); ++ ei->entry = info->entry; ++ ei->attr = info->attr; ++ ei->start_clu = info->start_clu; ++ ei->flags = info->flags; ++ ei->type = info->type; ++ ++ ei->version = 0; ++ ei->hint_stat.eidx = 0; ++ ei->hint_stat.clu = info->start_clu; ++ ei->hint_femp.eidx = EXFAT_HINT_NONE; ++ ei->rwoffset = 0; ++ ei->hint_bmap.off = EXFAT_EOF_CLUSTER; ++ ei->i_pos = 0; ++ ++ inode->i_uid = sbi->options.fs_uid; ++ inode->i_gid = sbi->options.fs_gid; ++ inode_inc_iversion(inode); ++ inode->i_generation = prandom_u32(); ++ ++ if (info->attr & ATTR_SUBDIR) { /* directory */ ++ inode->i_generation &= ~1; ++ inode->i_mode = exfat_make_mode(sbi, info->attr, 0777); ++ inode->i_op = &exfat_dir_inode_operations; ++ inode->i_fop = &exfat_dir_operations; ++ set_nlink(inode, info->num_subdirs); ++ } else { /* regular file */ ++ inode->i_generation |= 1; ++ inode->i_mode = exfat_make_mode(sbi, info->attr, 0777); ++ inode->i_op = &exfat_file_inode_operations; ++ inode->i_fop = &exfat_file_operations; ++ inode->i_mapping->a_ops = &exfat_aops; ++ inode->i_mapping->nrpages = 0; ++ } ++ ++ i_size_write(inode, size); ++ ++ /* ondisk and aligned size should be aligned with block size */ ++ if (size & (inode->i_sb->s_blocksize - 1)) { ++ size |= (inode->i_sb->s_blocksize - 1); ++ size++; ++ } ++ ++ ei->i_size_aligned = size; ++ ei->i_size_ondisk = size; ++ ++ exfat_save_attr(inode, info->attr); ++ ++ inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & ++ ~(sbi->cluster_size - 1)) >> inode->i_blkbits; ++ inode->i_mtime = info->mtime; ++ inode->i_ctime = info->mtime; ++ ei->i_crtime = info->crtime; ++ inode->i_atime = info->atime; ++ ++ exfat_cache_init_inode(inode); ++ ++ return 0; ++} ++ ++struct inode *exfat_build_inode(struct super_block *sb, ++ struct exfat_dir_entry *info, loff_t i_pos) ++{ ++ struct inode *inode; ++ int err; ++ ++ inode = exfat_iget(sb, i_pos); ++ if (inode) ++ goto out; ++ inode = new_inode(sb); ++ if (!inode) { ++ inode = ERR_PTR(-ENOMEM); ++ goto out; ++ } ++ inode->i_ino = iunique(sb, EXFAT_ROOT_INO); ++ inode_set_iversion(inode, 1); ++ err = exfat_fill_inode(inode, info); ++ if (err) { ++ iput(inode); ++ inode = ERR_PTR(err); ++ goto out; ++ } ++ exfat_hash_inode(inode, i_pos); ++ insert_inode_hash(inode); ++out: ++ return inode; ++} ++ ++void exfat_evict_inode(struct inode *inode) ++{ ++ truncate_inode_pages(&inode->i_data, 0); ++ ++ if (!inode->i_nlink) { ++ i_size_write(inode, 0); ++ mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); ++ __exfat_truncate(inode, 0); ++ mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); ++ } ++ ++ invalidate_inode_buffers(inode); ++ clear_inode(inode); ++ exfat_cache_inval_inode(inode); ++ exfat_unhash_inode(inode); ++} +diff --git a/src/namei.c b/src/namei.c +new file mode 100644 +index 0000000000000000000000000000000000000000..a8681d91f56900107ea3df90e2b6dd986045a7bc +--- /dev/null ++++ b/src/namei.c +@@ -0,0 +1,1448 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++static inline unsigned long exfat_d_version(struct dentry *dentry) ++{ ++ return (unsigned long) dentry->d_fsdata; ++} ++ ++static inline void exfat_d_version_set(struct dentry *dentry, ++ unsigned long version) ++{ ++ dentry->d_fsdata = (void *) version; ++} ++ ++/* ++ * If new entry was created in the parent, it could create the 8.3 alias (the ++ * shortname of logname). So, the parent may have the negative-dentry which ++ * matches the created 8.3 alias. ++ * ++ * If it happened, the negative dentry isn't actually negative anymore. So, ++ * drop it. ++ */ ++static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags) ++{ ++ int ret; ++ ++ if (flags & LOOKUP_RCU) ++ return -ECHILD; ++ ++ /* ++ * This is not negative dentry. Always valid. ++ * ++ * Note, rename() to existing directory entry will have ->d_inode, and ++ * will use existing name which isn't specified name by user. ++ * ++ * We may be able to drop this positive dentry here. But dropping ++ * positive dentry isn't good idea. So it's unsupported like ++ * rename("filename", "FILENAME") for now. ++ */ ++ if (d_really_is_positive(dentry)) ++ return 1; ++ ++ /* ++ * Drop the negative dentry, in order to make sure to use the case ++ * sensitive name which is specified by user if this is for creation. ++ */ ++ if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET)) ++ return 0; ++ ++ spin_lock(&dentry->d_lock); ++ ret = inode_eq_iversion(d_inode(dentry->d_parent), ++ exfat_d_version(dentry)); ++ 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) ++{ ++ while (len && name[len - 1] == '.') ++ len--; ++ return len; ++} ++ ++/* ++ * Compute the hash for the exfat name corresponding to the dentry. If the name ++ * is invalid, we leave the hash code unchanged so that the existing dentry can ++ * be used. The exfat fs routines will return ENOENT or EINVAL as appropriate. ++ */ ++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 long hash = init_name_hash(dentry); ++ int i, charlen; ++ wchar_t c; ++ ++ for (i = 0; i < len; i += charlen) { ++ charlen = t->char2uni(&name[i], len - i, &c); ++ if (charlen < 0) ++ return charlen; ++ hash = partial_name_hash(exfat_toupper(sb, c), hash); ++ } ++ ++ qstr->hash = end_name_hash(hash); ++ return 0; ++} ++ ++static int exfat_d_cmp(const struct dentry *dentry, unsigned int len, ++ const char *str, const struct qstr *name) ++{ ++ 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); ++ wchar_t c1, c2; ++ int charlen, i; ++ ++ if (alen != blen) ++ return 1; ++ ++ for (i = 0; i < len; i += charlen) { ++ charlen = t->char2uni(&name->name[i], alen - i, &c1); ++ if (charlen < 0) ++ return 1; ++ if (charlen != t->char2uni(&str[i], blen - i, &c2)) ++ return 1; ++ ++ if (exfat_toupper(sb, c1) != exfat_toupper(sb, c2)) ++ return 1; ++ } ++ ++ return 0; ++} ++ ++const struct dentry_operations exfat_dentry_ops = { ++ .d_revalidate = exfat_d_revalidate, ++ .d_hash = exfat_d_hash, ++ .d_compare = exfat_d_cmp, ++}; ++ ++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 long hash = init_name_hash(dentry); ++ int i, charlen; ++ unicode_t u; ++ ++ for (i = 0; i < len; i += charlen) { ++ charlen = utf8_to_utf32(&name[i], len - i, &u); ++ if (charlen < 0) ++ return charlen; ++ ++ /* ++ * Convert to UTF-16: code points above U+FFFF are encoded as ++ * surrogate pairs. ++ * exfat_toupper() works only for code points up to the U+FFFF. ++ */ ++ if (u > 0xFFFF) { ++ hash = partial_name_hash(exfat_high_surrogate(u), hash); ++ hash = partial_name_hash(exfat_low_surrogate(u), hash); ++ } else { ++ hash = partial_name_hash(exfat_toupper(sb, u), hash); ++ } ++ } ++ ++ qstr->hash = end_name_hash(hash); ++ return 0; ++} ++ ++static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len, ++ const char *str, const struct qstr *name) ++{ ++ 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); ++ unicode_t u_a, u_b; ++ int charlen, i; ++ ++ if (alen != blen) ++ return 1; ++ ++ for (i = 0; i < alen; i += charlen) { ++ charlen = utf8_to_utf32(&name->name[i], alen - i, &u_a); ++ if (charlen < 0) ++ return 1; ++ if (charlen != utf8_to_utf32(&str[i], blen - i, &u_b)) ++ return 1; ++ ++ if (u_a <= 0xFFFF && u_b <= 0xFFFF) { ++ if (exfat_toupper(sb, u_a) != exfat_toupper(sb, u_b)) ++ return 1; ++ } else if (u_a > 0xFFFF && u_b > 0xFFFF) { ++ if (exfat_low_surrogate(u_a) != ++ exfat_low_surrogate(u_b) || ++ exfat_high_surrogate(u_a) != ++ exfat_high_surrogate(u_b)) ++ return 1; ++ } else { ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++const struct dentry_operations exfat_utf8_dentry_ops = { ++ .d_revalidate = exfat_d_revalidate, ++ .d_hash = exfat_utf8_d_hash, ++ .d_compare = exfat_utf8_d_cmp, ++}; ++ ++/* used only in search empty_slot() */ ++#define CNT_UNUSED_NOHIT (-1) ++#define CNT_UNUSED_HIT (-2) ++/* search EMPTY CONTINUOUS "num_entries" entries */ ++static int exfat_search_empty_slot(struct super_block *sb, ++ struct exfat_hint_femp *hint_femp, struct exfat_chain *p_dir, ++ int num_entries) ++{ ++ int i, dentry, num_empty = 0; ++ int dentries_per_clu; ++ unsigned int type; ++ struct exfat_chain clu; ++ struct exfat_dentry *ep; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct buffer_head *bh; ++ ++ dentries_per_clu = sbi->dentries_per_clu; ++ ++ if (hint_femp->eidx != EXFAT_HINT_NONE) { ++ dentry = hint_femp->eidx; ++ if (num_entries <= hint_femp->count) { ++ hint_femp->eidx = EXFAT_HINT_NONE; ++ return dentry; ++ } ++ ++ exfat_chain_dup(&clu, &hint_femp->cur); ++ } else { ++ exfat_chain_dup(&clu, p_dir); ++ dentry = 0; ++ } ++ ++ while (clu.dir != EXFAT_EOF_CLUSTER) { ++ i = dentry & (dentries_per_clu - 1); ++ ++ for (; i < dentries_per_clu; i++, dentry++) { ++ ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); ++ if (!ep) ++ return -EIO; ++ type = exfat_get_entry_type(ep); ++ brelse(bh); ++ ++ if (type == TYPE_UNUSED || type == TYPE_DELETED) { ++ num_empty++; ++ if (hint_femp->eidx == EXFAT_HINT_NONE) { ++ hint_femp->eidx = dentry; ++ hint_femp->count = CNT_UNUSED_NOHIT; ++ exfat_chain_set(&hint_femp->cur, ++ clu.dir, clu.size, clu.flags); ++ } ++ ++ if (type == TYPE_UNUSED && ++ hint_femp->count != CNT_UNUSED_HIT) ++ hint_femp->count = CNT_UNUSED_HIT; ++ } else { ++ if (hint_femp->eidx != EXFAT_HINT_NONE && ++ hint_femp->count == CNT_UNUSED_HIT) { ++ /* unused empty group means ++ * an empty group which includes ++ * unused dentry ++ */ ++ exfat_fs_error(sb, ++ "found bogus dentry(%d) beyond unused empty group(%d) (start_clu : %u, cur_clu : %u)", ++ dentry, hint_femp->eidx, ++ p_dir->dir, clu.dir); ++ return -EIO; ++ } ++ ++ num_empty = 0; ++ hint_femp->eidx = EXFAT_HINT_NONE; ++ } ++ ++ if (num_empty >= num_entries) { ++ /* found and invalidate hint_femp */ ++ hint_femp->eidx = EXFAT_HINT_NONE; ++ return (dentry - (num_entries - 1)); ++ } ++ } ++ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ if (--clu.size > 0) ++ clu.dir++; ++ else ++ clu.dir = EXFAT_EOF_CLUSTER; ++ } else { ++ if (exfat_get_next_cluster(sb, &clu.dir)) ++ return -EIO; ++ } ++ } ++ ++ return -ENOSPC; ++} ++ ++static int exfat_check_max_dentries(struct inode *inode) ++{ ++ if (EXFAT_B_TO_DEN(i_size_read(inode)) >= MAX_EXFAT_DENTRIES) { ++ /* ++ * exFAT spec allows a dir to grow upto 8388608(256MB) ++ * dentries ++ */ ++ return -ENOSPC; ++ } ++ return 0; ++} ++ ++/* find empty directory entry. ++ * if there isn't any empty slot, expand cluster chain. ++ */ ++static int exfat_find_empty_entry(struct inode *inode, ++ struct exfat_chain *p_dir, int num_entries) ++{ ++ int dentry; ++ unsigned int ret, last_clu; ++ sector_t sector; ++ loff_t size = 0; ++ struct exfat_chain clu; ++ struct exfat_dentry *ep = NULL; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct exfat_hint_femp hint_femp; ++ ++ hint_femp.eidx = EXFAT_HINT_NONE; ++ ++ if (ei->hint_femp.eidx != EXFAT_HINT_NONE) { ++ memcpy(&hint_femp, &ei->hint_femp, ++ sizeof(struct exfat_hint_femp)); ++ ei->hint_femp.eidx = EXFAT_HINT_NONE; ++ } ++ ++ while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir, ++ num_entries)) < 0) { ++ if (dentry == -EIO) ++ break; ++ ++ 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); ++ ++ /* allocate a cluster */ ++ ret = exfat_alloc_cluster(inode, 1, &clu); ++ if (ret) ++ return ret; ++ ++ if (exfat_zeroed_cluster(inode, clu.dir)) ++ return -EIO; ++ ++ /* append to the FAT chain */ ++ if (clu.flags != p_dir->flags) { ++ /* no-fat-chain bit is disabled, ++ * so fat-chain should be synced with alloc-bitmap ++ */ ++ exfat_chain_cont_cluster(sb, p_dir->dir, p_dir->size); ++ p_dir->flags = ALLOC_FAT_CHAIN; ++ hint_femp.cur.flags = ALLOC_FAT_CHAIN; ++ } ++ ++ if (clu.flags == ALLOC_FAT_CHAIN) ++ if (exfat_ent_set(sb, last_clu, clu.dir)) ++ return -EIO; ++ ++ if (hint_femp.eidx == EXFAT_HINT_NONE) { ++ /* the special case that new dentry ++ * should be allocated from the start of new cluster ++ */ ++ hint_femp.eidx = EXFAT_B_TO_DEN_IDX(p_dir->size, sbi); ++ hint_femp.count = sbi->dentries_per_clu; ++ ++ exfat_chain_set(&hint_femp.cur, clu.dir, 0, clu.flags); ++ } ++ hint_femp.cur.size++; ++ p_dir->size++; ++ size = EXFAT_CLU_TO_B(p_dir->size, sbi); ++ ++ /* update the directory entry */ ++ if (p_dir->dir != sbi->root_dir) { ++ struct buffer_head *bh; ++ ++ ep = exfat_get_dentry(sb, ++ &(ei->dir), ei->entry + 1, &bh, §or); ++ if (!ep) ++ return -EIO; ++ ++ ep->dentry.stream.valid_size = cpu_to_le64(size); ++ ep->dentry.stream.size = ep->dentry.stream.valid_size; ++ ep->dentry.stream.flags = p_dir->flags; ++ exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ brelse(bh); ++ if (exfat_update_dir_chksum(inode, &(ei->dir), ++ ei->entry)) ++ return -EIO; ++ } ++ ++ /* directory inode should be updated in here */ ++ i_size_write(inode, size); ++ 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; ++ inode->i_blocks += 1 << sbi->sect_per_clus_bits; ++ } ++ ++ return dentry; ++} ++ ++/* ++ * Name Resolution Functions : ++ * Zero if it was successful; otherwise nonzero. ++ */ ++static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, ++ struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, ++ int lookup) ++{ ++ int namelen; ++ int lossy = NLS_NAME_NO_LOSSY; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ ++ /* strip all trailing periods */ ++ namelen = exfat_striptail_len(strlen(path), path); ++ if (!namelen) ++ return -ENOENT; ++ ++ if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE)) ++ return -ENAMETOOLONG; ++ ++ /* ++ * strip all leading spaces : ++ * "MS windows 7" supports leading spaces. ++ * So we should skip this preprocessing for compatibility. ++ */ ++ ++ /* file name conversion : ++ * If lookup case, we allow bad-name for compatibility. ++ */ ++ namelen = exfat_nls_to_utf16(sb, path, namelen, p_uniname, ++ &lossy); ++ if (namelen < 0) ++ return namelen; /* return error value */ ++ ++ if ((lossy && !lookup) || !namelen) ++ return -EINVAL; ++ ++ exfat_chain_set(p_dir, ei->start_clu, ++ EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); ++ ++ return 0; ++} ++ ++static inline int exfat_resolve_path(struct inode *inode, ++ const unsigned char *path, struct exfat_chain *dir, ++ struct exfat_uni_name *uni) ++{ ++ return __exfat_resolve_path(inode, path, dir, uni, 0); ++} ++ ++static inline int exfat_resolve_path_for_lookup(struct inode *inode, ++ const unsigned char *path, struct exfat_chain *dir, ++ struct exfat_uni_name *uni) ++{ ++ return __exfat_resolve_path(inode, path, dir, uni, 1); ++} ++ ++static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info) ++{ ++ return ((loff_t) info->dir.dir << 32) | (info->entry & 0xffffffff); ++} ++ ++static int exfat_add_entry(struct inode *inode, const char *path, ++ struct exfat_chain *p_dir, unsigned int type, ++ struct exfat_dir_entry *info) ++{ ++ int ret, dentry, num_entries; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_uni_name uniname; ++ struct exfat_chain clu; ++ int clu_size = 0; ++ unsigned int start_clu = EXFAT_FREE_CLUSTER; ++ ++ ret = exfat_resolve_path(inode, path, p_dir, &uniname); ++ if (ret) ++ goto out; ++ ++ num_entries = exfat_calc_num_entries(&uniname); ++ if (num_entries < 0) { ++ ret = num_entries; ++ goto out; ++ } ++ ++ /* exfat_find_empty_entry must be called before alloc_cluster() */ ++ dentry = exfat_find_empty_entry(inode, p_dir, num_entries); ++ if (dentry < 0) { ++ ret = dentry; /* -EIO or -ENOSPC */ ++ goto out; ++ } ++ ++ if (type == TYPE_DIR) { ++ ret = exfat_alloc_new_dir(inode, &clu); ++ if (ret) ++ goto out; ++ start_clu = clu.dir; ++ clu_size = sbi->cluster_size; ++ } ++ ++ /* update the directory entry */ ++ /* fill the dos name directory entry information of the created file. ++ * the first cluster is not determined yet. (0) ++ */ ++ ret = exfat_init_dir_entry(inode, p_dir, dentry, type, ++ start_clu, clu_size); ++ if (ret) ++ goto out; ++ ++ ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname); ++ if (ret) ++ goto out; ++ ++ memcpy(&info->dir, p_dir, sizeof(struct exfat_chain)); ++ info->entry = dentry; ++ info->flags = ALLOC_NO_FAT_CHAIN; ++ info->type = type; ++ ++ if (type == TYPE_FILE) { ++ info->attr = ATTR_ARCHIVE; ++ info->start_clu = EXFAT_EOF_CLUSTER; ++ info->size = 0; ++ info->num_subdirs = 0; ++ } else { ++ int count; ++ struct exfat_chain cdir; ++ ++ info->attr = ATTR_SUBDIR; ++ info->start_clu = start_clu; ++ info->size = clu_size; ++ ++ exfat_chain_set(&cdir, info->start_clu, ++ EXFAT_B_TO_CLU(info->size, sbi), info->flags); ++ count = exfat_count_dir_entries(sb, &cdir); ++ if (count < 0) ++ return -EIO; ++ info->num_subdirs = count + EXFAT_MIN_SUBDIR; ++ } ++ memset(&info->crtime, 0, sizeof(info->crtime)); ++ memset(&info->mtime, 0, sizeof(info->mtime)); ++ memset(&info->atime, 0, sizeof(info->atime)); ++out: ++ return ret; ++} ++ ++static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, ++ bool excl) ++{ ++ struct super_block *sb = dir->i_sb; ++ struct inode *inode; ++ struct exfat_chain cdir; ++ struct exfat_dir_entry info; ++ loff_t i_pos; ++ int err; ++ ++ mutex_lock(&EXFAT_SB(sb)->s_lock); ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE, ++ &info); ++ exfat_set_vol_flags(sb, VOL_CLEAN); ++ if (err) ++ goto unlock; ++ ++ inode_inc_iversion(dir); ++ dir->i_ctime = dir->i_mtime = current_time(dir); ++ if (IS_DIRSYNC(dir)) ++ exfat_sync_inode(dir); ++ else ++ mark_inode_dirty(dir); ++ ++ i_pos = exfat_make_i_pos(&info); ++ inode = exfat_build_inode(sb, &info, i_pos); ++ if (IS_ERR(inode)) ++ goto unlock; ++ ++ inode_inc_iversion(inode); ++ inode->i_mtime = inode->i_atime = inode->i_ctime = ++ EXFAT_I(inode)->i_crtime = current_time(inode); ++ /* timestamp is already written, so mark_inode_dirty() is unneeded. */ ++ ++ d_instantiate(dentry, inode); ++unlock: ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ return err; ++} ++ ++/* lookup a file */ ++static int exfat_find(struct inode *dir, struct qstr *qname, ++ struct exfat_dir_entry *info) ++{ ++ int ret, dentry, num_entries, count; ++ struct exfat_chain cdir; ++ struct exfat_uni_name uni_name; ++ struct exfat_dentry *ep, *ep2; ++ struct exfat_entry_set_cache *es = NULL; ++ struct super_block *sb = dir->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(dir); ++ ++ if (qname->len == 0) ++ return -ENOENT; ++ ++ /* check the validity of directory name in the given pathname */ ++ ret = exfat_resolve_path_for_lookup(dir, qname->name, &cdir, &uni_name); ++ if (ret) ++ return ret; ++ ++ num_entries = exfat_calc_num_entries(&uni_name); ++ if (num_entries < 0) ++ return num_entries; ++ ++ /* check the validation of hint_stat and initialize it if required */ ++ if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { ++ ei->hint_stat.clu = cdir.dir; ++ ei->hint_stat.eidx = 0; ++ ei->version = (inode_peek_iversion_raw(dir) & 0xffffffff); ++ 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); ++ ++ if ((dentry < 0) && (dentry != -EEXIST)) ++ return dentry; /* -error value */ ++ ++ memcpy(&info->dir, &cdir.dir, sizeof(struct exfat_chain)); ++ info->entry = dentry; ++ info->num_subdirs = 0; ++ ++ /* root directory itself */ ++ if (unlikely(dentry == -EEXIST)) { ++ int num_clu = 0; ++ ++ info->type = TYPE_DIR; ++ info->attr = ATTR_SUBDIR; ++ info->flags = ALLOC_FAT_CHAIN; ++ info->start_clu = sbi->root_dir; ++ memset(&info->crtime, 0, sizeof(info->crtime)); ++ memset(&info->mtime, 0, sizeof(info->mtime)); ++ memset(&info->atime, 0, sizeof(info->atime)); ++ ++ exfat_chain_set(&cdir, sbi->root_dir, 0, ALLOC_FAT_CHAIN); ++ if (exfat_count_num_clusters(sb, &cdir, &num_clu)) ++ return -EIO; ++ info->size = num_clu << sbi->cluster_size_bits; ++ ++ count = exfat_count_dir_entries(sb, &cdir); ++ if (count < 0) ++ return -EIO; ++ ++ info->num_subdirs = count; ++ } else { ++ es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES, &ep); ++ if (!es) ++ return -EIO; ++ ep2 = ep + 1; ++ ++ 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)) { ++ info->flags = ALLOC_NO_FAT_CHAIN; ++ info->start_clu = EXFAT_EOF_CLUSTER; ++ } else { ++ info->flags = ep2->dentry.stream.flags; ++ info->start_clu = ++ le32_to_cpu(ep2->dentry.stream.start_clu); ++ } ++ ++ if (ei->start_clu == EXFAT_FREE_CLUSTER) { ++ exfat_fs_error(sb, ++ "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", ++ i_size_read(dir), ei->dir.dir, ei->entry); ++ return -EIO; ++ } ++ ++ exfat_get_entry_time(sbi, &info->crtime, ++ ep->dentry.file.create_tz, ++ ep->dentry.file.create_time, ++ ep->dentry.file.create_date, ++ ep->dentry.file.create_time_ms); ++ exfat_get_entry_time(sbi, &info->mtime, ++ ep->dentry.file.modify_tz, ++ ep->dentry.file.modify_time, ++ ep->dentry.file.modify_date, ++ ep->dentry.file.modify_time_ms); ++ exfat_get_entry_time(sbi, &info->atime, ++ ep->dentry.file.access_tz, ++ ep->dentry.file.access_time, ++ ep->dentry.file.access_date, ++ 0); ++ kfree(es); ++ ++ if (info->type == TYPE_DIR) { ++ exfat_chain_set(&cdir, info->start_clu, ++ EXFAT_B_TO_CLU(info->size, sbi), info->flags); ++ count = exfat_count_dir_entries(sb, &cdir); ++ if (count < 0) ++ return -EIO; ++ ++ info->num_subdirs = count + EXFAT_MIN_SUBDIR; ++ } ++ } ++ return 0; ++} ++ ++static int exfat_d_anon_disconn(struct dentry *dentry) ++{ ++ return IS_ROOT(dentry) && (dentry->d_flags & DCACHE_DISCONNECTED); ++} ++ ++static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, ++ unsigned int flags) ++{ ++ struct super_block *sb = dir->i_sb; ++ struct inode *inode; ++ struct dentry *alias; ++ struct exfat_dir_entry info; ++ int err; ++ loff_t i_pos; ++ mode_t i_mode; ++ ++ mutex_lock(&EXFAT_SB(sb)->s_lock); ++ err = exfat_find(dir, &dentry->d_name, &info); ++ if (err) { ++ if (err == -ENOENT) { ++ inode = NULL; ++ goto out; ++ } ++ goto unlock; ++ } ++ ++ i_pos = exfat_make_i_pos(&info); ++ inode = exfat_build_inode(sb, &info, i_pos); ++ if (IS_ERR(inode)) { ++ err = PTR_ERR(inode); ++ goto unlock; ++ } ++ ++ i_mode = inode->i_mode; ++ alias = d_find_alias(inode); ++ ++ /* ++ * Checking "alias->d_parent == dentry->d_parent" to make sure ++ * FS is not corrupted (especially double linked dir). ++ */ ++ if (alias && alias->d_parent == dentry->d_parent && ++ !exfat_d_anon_disconn(alias)) { ++ ++ /* ++ * Unhashed alias is able to exist because of revalidate() ++ * called by lookup_fast. You can easily make this status ++ * by calling create and lookup concurrently ++ * In such case, we reuse an alias instead of new dentry ++ */ ++ if (d_unhashed(alias)) { ++ WARN_ON(alias->d_name.hash_len != ++ dentry->d_name.hash_len); ++ exfat_msg(sb, KERN_INFO, ++ "rehashed a dentry(%p) in read lookup", alias); ++ d_drop(dentry); ++ d_rehash(alias); ++ } else if (!S_ISDIR(i_mode)) { ++ /* ++ * This inode has non anonymous-DCACHE_DISCONNECTED ++ * dentry. This means, the user did ->lookup() by an ++ * another name (longname vs 8.3 alias of it) in past. ++ * ++ * Switch to new one for reason of locality if possible. ++ */ ++ d_move(alias, dentry); ++ } ++ iput(inode); ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ return alias; ++ } ++ dput(alias); ++out: ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ if (!inode) ++ exfat_d_version_set(dentry, inode_query_iversion(dir)); ++ ++ return d_splice_alias(inode, dentry); ++unlock: ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ return ERR_PTR(err); ++} ++ ++/* remove an entry, BUT don't truncate */ ++static int exfat_unlink(struct inode *dir, struct dentry *dentry) ++{ ++ struct exfat_chain cdir; ++ struct exfat_dentry *ep; ++ struct super_block *sb = dir->i_sb; ++ struct inode *inode = dentry->d_inode; ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct buffer_head *bh; ++ sector_t sector; ++ int num_entries, entry, err = 0; ++ ++ mutex_lock(&EXFAT_SB(sb)->s_lock); ++ exfat_chain_dup(&cdir, &ei->dir); ++ entry = ei->entry; ++ if (ei->dir.dir == DIR_DELETED) { ++ exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry"); ++ err = -ENOENT; ++ goto unlock; ++ } ++ ++ ep = exfat_get_dentry(sb, &cdir, entry, &bh, §or); ++ if (!ep) { ++ err = -EIO; ++ goto unlock; ++ } ++ num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep); ++ if (num_entries < 0) { ++ err = -EIO; ++ brelse(bh); ++ goto unlock; ++ } ++ num_entries++; ++ brelse(bh); ++ ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ /* update the directory entry */ ++ if (exfat_remove_entries(dir, &cdir, entry, 0, num_entries)) { ++ err = -EIO; ++ goto unlock; ++ } ++ ++ /* This doesn't modify ei */ ++ ei->dir.dir = DIR_DELETED; ++ exfat_set_vol_flags(sb, VOL_CLEAN); ++ ++ inode_inc_iversion(dir); ++ dir->i_mtime = dir->i_atime = current_time(dir); ++ if (IS_DIRSYNC(dir)) ++ exfat_sync_inode(dir); ++ else ++ mark_inode_dirty(dir); ++ ++ clear_nlink(inode); ++ inode->i_mtime = inode->i_atime = current_time(inode); ++ exfat_unhash_inode(inode); ++ exfat_d_version_set(dentry, inode_query_iversion(dir)); ++unlock: ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ return err; ++} ++ ++static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) ++{ ++ struct super_block *sb = dir->i_sb; ++ struct inode *inode; ++ struct exfat_dir_entry info; ++ struct exfat_chain cdir; ++ loff_t i_pos; ++ int err; ++ ++ mutex_lock(&EXFAT_SB(sb)->s_lock); ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR, ++ &info); ++ exfat_set_vol_flags(sb, VOL_CLEAN); ++ if (err) ++ goto unlock; ++ ++ inode_inc_iversion(dir); ++ dir->i_ctime = dir->i_mtime = current_time(dir); ++ if (IS_DIRSYNC(dir)) ++ exfat_sync_inode(dir); ++ else ++ mark_inode_dirty(dir); ++ inc_nlink(dir); ++ ++ i_pos = exfat_make_i_pos(&info); ++ inode = exfat_build_inode(sb, &info, i_pos); ++ if (IS_ERR(inode)) { ++ err = PTR_ERR(inode); ++ goto unlock; ++ } ++ ++ inode_inc_iversion(inode); ++ inode->i_mtime = inode->i_atime = inode->i_ctime = ++ EXFAT_I(inode)->i_crtime = current_time(inode); ++ /* timestamp is already written, so mark_inode_dirty() is unneeded. */ ++ ++ d_instantiate(dentry, inode); ++ ++unlock: ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ return err; ++} ++ ++static int exfat_check_dir_empty(struct super_block *sb, ++ struct exfat_chain *p_dir) ++{ ++ int i, dentries_per_clu; ++ unsigned int type; ++ struct exfat_chain clu; ++ struct exfat_dentry *ep; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct buffer_head *bh; ++ ++ dentries_per_clu = sbi->dentries_per_clu; ++ ++ exfat_chain_dup(&clu, 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); ++ if (!ep) ++ return -EIO; ++ type = exfat_get_entry_type(ep); ++ brelse(bh); ++ if (type == TYPE_UNUSED) ++ return 0; ++ ++ if (type != TYPE_FILE && type != TYPE_DIR) ++ continue; ++ ++ return -ENOTEMPTY; ++ } ++ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ if (--clu.size > 0) ++ clu.dir++; ++ else ++ clu.dir = EXFAT_EOF_CLUSTER; ++ } else { ++ if (exfat_get_next_cluster(sb, &(clu.dir))) ++ return -EIO; ++ } ++ } ++ ++ return 0; ++} ++ ++static int exfat_rmdir(struct inode *dir, struct dentry *dentry) ++{ ++ struct inode *inode = dentry->d_inode; ++ struct exfat_dentry *ep; ++ struct exfat_chain cdir, clu_to_free; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct buffer_head *bh; ++ sector_t sector; ++ int num_entries, entry, err; ++ ++ mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); ++ ++ exfat_chain_dup(&cdir, &ei->dir); ++ entry = ei->entry; ++ ++ if (ei->dir.dir == DIR_DELETED) { ++ exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry"); ++ err = -ENOENT; ++ goto unlock; ++ } ++ ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_chain_set(&clu_to_free, ei->start_clu, ++ EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi), ei->flags); ++ ++ err = exfat_check_dir_empty(sb, &clu_to_free); ++ if (err) { ++ if (err == -EIO) ++ exfat_msg(sb, KERN_ERR, ++ "failed to exfat_check_dir_empty : err(%d)", ++ err); ++ goto unlock; ++ } ++ ++ ep = exfat_get_dentry(sb, &cdir, entry, &bh, §or); ++ if (!ep) { ++ err = -EIO; ++ goto unlock; ++ } ++ ++ num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep); ++ if (num_entries < 0) { ++ err = -EIO; ++ brelse(bh); ++ goto unlock; ++ } ++ num_entries++; ++ brelse(bh); ++ ++ err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries); ++ if (err) { ++ exfat_msg(sb, KERN_ERR, ++ "failed to exfat_remove_entries : err(%d)", ++ err); ++ goto unlock; ++ } ++ ei->dir.dir = DIR_DELETED; ++ exfat_set_vol_flags(sb, VOL_CLEAN); ++ ++ inode_inc_iversion(dir); ++ dir->i_mtime = dir->i_atime = current_time(dir); ++ if (IS_DIRSYNC(dir)) ++ exfat_sync_inode(dir); ++ else ++ mark_inode_dirty(dir); ++ drop_nlink(dir); ++ ++ clear_nlink(inode); ++ inode->i_mtime = inode->i_atime = current_time(inode); ++ exfat_unhash_inode(inode); ++ exfat_d_version_set(dentry, inode_query_iversion(dir)); ++unlock: ++ mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); ++ return err; ++} ++ ++static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, ++ int oldentry, struct exfat_uni_name *p_uniname, ++ struct exfat_inode_info *ei) ++{ ++ int ret, num_old_entries, num_new_entries; ++ 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); ++ if (!epold) ++ return -EIO; ++ ++ num_old_entries = exfat_count_ext_entries(sb, p_dir, oldentry, epold); ++ if (num_old_entries < 0) ++ return -EIO; ++ num_old_entries++; ++ ++ num_new_entries = exfat_calc_num_entries(p_uniname); ++ if (num_new_entries < 0) ++ return num_new_entries; ++ ++ if (num_old_entries < num_new_entries) { ++ int newentry; ++ ++ newentry = ++ exfat_find_empty_entry(inode, p_dir, num_new_entries); ++ if (newentry < 0) ++ return newentry; /* -EIO or -ENOSPC */ ++ ++ epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh, ++ §or_new); ++ if (!epnew) ++ return -EIO; ++ ++ memcpy(epnew, epold, DENTRY_SIZE); ++ if (exfat_get_entry_type(epnew) == TYPE_FILE) { ++ epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); ++ ei->attr |= ATTR_ARCHIVE; ++ } ++ exfat_update_bh(sb, new_bh, sync); ++ brelse(old_bh); ++ brelse(new_bh); ++ ++ epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh, ++ §or_old); ++ epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh, ++ §or_new); ++ if (!epold || !epnew) ++ return -EIO; ++ ++ memcpy(epnew, epold, DENTRY_SIZE); ++ exfat_update_bh(sb, new_bh, sync); ++ brelse(old_bh); ++ brelse(new_bh); ++ ++ ret = exfat_init_ext_entry(inode, p_dir, newentry, ++ num_new_entries, p_uniname); ++ if (ret) ++ return ret; ++ ++ exfat_remove_entries(inode, p_dir, oldentry, 0, ++ num_old_entries); ++ 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; ++ } ++ exfat_update_bh(sb, old_bh, sync); ++ brelse(old_bh); ++ ret = exfat_init_ext_entry(inode, p_dir, oldentry, ++ num_new_entries, p_uniname); ++ if (ret) ++ return ret; ++ ++ exfat_remove_entries(inode, p_dir, oldentry, num_new_entries, ++ num_old_entries); ++ } ++ return 0; ++} ++ ++static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, ++ int oldentry, struct exfat_chain *p_newdir, ++ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) ++{ ++ int ret, newentry, num_new_entries, num_old_entries; ++ 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); ++ if (!epmov) ++ return -EIO; ++ ++ /* check if the source and target directory is the same */ ++ if (exfat_get_entry_type(epmov) == TYPE_DIR && ++ le32_to_cpu(epmov->dentry.stream.start_clu) == p_newdir->dir) ++ return -EINVAL; ++ ++ num_old_entries = exfat_count_ext_entries(sb, p_olddir, oldentry, ++ epmov); ++ if (num_old_entries < 0) ++ return -EIO; ++ num_old_entries++; ++ ++ num_new_entries = exfat_calc_num_entries(p_uniname); ++ if (num_new_entries < 0) ++ return num_new_entries; ++ ++ newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries); ++ if (newentry < 0) ++ return newentry; /* -EIO or -ENOSPC */ ++ ++ epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh, §or_new); ++ if (!epnew) ++ return -EIO; ++ ++ memcpy(epnew, epmov, DENTRY_SIZE); ++ if (exfat_get_entry_type(epnew) == TYPE_FILE) { ++ epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); ++ ei->attr |= ATTR_ARCHIVE; ++ } ++ exfat_update_bh(sb, new_bh, IS_DIRSYNC(inode)); ++ brelse(mov_bh); ++ brelse(new_bh); ++ ++ epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh, ++ §or_mov); ++ epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh, ++ §or_new); ++ if (!epmov || !epnew) ++ return -EIO; ++ ++ memcpy(epnew, epmov, DENTRY_SIZE); ++ exfat_update_bh(sb, new_bh, IS_DIRSYNC(inode)); ++ brelse(mov_bh); ++ brelse(new_bh); ++ ++ ret = exfat_init_ext_entry(inode, p_newdir, newentry, num_new_entries, ++ p_uniname); ++ if (ret) ++ return ret; ++ ++ exfat_remove_entries(inode, p_olddir, oldentry, 0, num_old_entries); ++ ++ exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, ++ p_newdir->flags); ++ ++ ei->entry = newentry; ++ 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, ++ struct dentry *new_dentry) ++{ ++ int ret; ++ int dentry; ++ struct exfat_chain olddir, newdir; ++ struct exfat_chain *p_dir = NULL; ++ struct exfat_uni_name uni_name; ++ struct exfat_dentry *ep; ++ struct super_block *sb = old_parent_inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ const unsigned char *new_path = new_dentry->d_name.name; ++ struct inode *new_inode = new_dentry->d_inode; ++ int num_entries; ++ struct exfat_inode_info *new_ei = NULL; ++ unsigned int new_entry_type = TYPE_UNUSED; ++ int new_entry = 0; ++ struct buffer_head *old_bh, *new_bh = NULL; ++ ++ /* check the validity of pointer parameters */ ++ if (new_path == NULL || strlen(new_path) == 0) ++ return -EINVAL; ++ ++ if (ei->dir.dir == DIR_DELETED) { ++ exfat_msg(sb, KERN_ERR, ++ "abnormal access to deleted source dentry"); ++ 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); ++ if (!ep) { ++ ret = -EIO; ++ goto out; ++ } ++ brelse(old_bh); ++ ++ /* check whether new dir is existing directory and empty */ ++ if (new_inode) { ++ ret = -EIO; ++ new_ei = EXFAT_I(new_inode); ++ ++ if (new_ei->dir.dir == DIR_DELETED) { ++ exfat_msg(sb, KERN_ERR, ++ "abnormal access to deleted target dentry"); ++ 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); ++ if (!ep) ++ goto out; ++ ++ new_entry_type = exfat_get_entry_type(ep); ++ brelse(new_bh); ++ ++ /* if new_inode exists, update ei */ ++ if (new_entry_type == TYPE_DIR) { ++ struct exfat_chain new_clu; ++ ++ new_clu.dir = new_ei->start_clu; ++ new_clu.size = ++ EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode), ++ sbi); ++ new_clu.flags = new_ei->flags; ++ ++ ret = exfat_check_dir_empty(sb, &new_clu); ++ if (ret) ++ goto out; ++ } ++ } ++ ++ /* check the validity of directory name in the given new pathname */ ++ ret = exfat_resolve_path(new_parent_inode, new_path, &newdir, ++ &uni_name); ++ if (ret) ++ goto out; ++ ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ ++ if (olddir.dir == newdir.dir) ++ ret = exfat_rename_file(new_parent_inode, &olddir, dentry, ++ &uni_name, ei); ++ else ++ ret = exfat_move_file(new_parent_inode, &olddir, dentry, ++ &newdir, &uni_name, ei); ++ ++ if (!ret && new_inode) { ++ /* delete entries of new_dir */ ++ ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh, NULL); ++ if (!ep) { ++ ret = -EIO; ++ goto del_out; ++ } ++ ++ num_entries = exfat_count_ext_entries(sb, p_dir, new_entry, ep); ++ if (num_entries < 0) { ++ ret = -EIO; ++ goto del_out; ++ } ++ brelse(new_bh); ++ ++ if (exfat_remove_entries(new_inode, p_dir, new_entry, 0, ++ num_entries + 1)) { ++ ret = -EIO; ++ goto del_out; ++ } ++ ++ /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */ ++ if (new_entry_type == TYPE_DIR) { ++ /* new_ei, new_clu_to_free */ ++ struct exfat_chain new_clu_to_free; ++ ++ exfat_chain_set(&new_clu_to_free, new_ei->start_clu, ++ EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode), ++ sbi), new_ei->flags); ++ ++ if (exfat_free_cluster(new_inode, &new_clu_to_free)) { ++ /* just set I/O error only */ ++ ret = -EIO; ++ } ++ ++ i_size_write(new_inode, 0); ++ new_ei->start_clu = EXFAT_EOF_CLUSTER; ++ new_ei->flags = ALLOC_NO_FAT_CHAIN; ++ } ++del_out: ++ /* Update new_inode ei ++ * Prevent syncing removed new_inode ++ * (new_ei is already initialized above code ("if (new_inode)") ++ */ ++ new_ei->dir.dir = DIR_DELETED; ++ } ++ exfat_set_vol_flags(sb, VOL_CLEAN); ++out: ++ return ret; ++} ++ ++static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, ++ struct inode *new_dir, struct dentry *new_dentry, ++ unsigned int flags) ++{ ++ struct inode *old_inode, *new_inode; ++ struct super_block *sb = old_dir->i_sb; ++ loff_t i_pos; ++ int err; ++ ++ /* ++ * The VFS already checks for existence, so for local filesystems ++ * the RENAME_NOREPLACE implementation is equivalent to plain rename. ++ * Don't support any other flags ++ */ ++ if (flags & ~RENAME_NOREPLACE) ++ return -EINVAL; ++ ++ mutex_lock(&EXFAT_SB(sb)->s_lock); ++ old_inode = old_dentry->d_inode; ++ new_inode = new_dentry->d_inode; ++ ++ err = __exfat_rename(old_dir, EXFAT_I(old_inode), new_dir, new_dentry); ++ if (err) ++ goto unlock; ++ ++ inode_inc_iversion(new_dir); ++ new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = ++ EXFAT_I(new_dir)->i_crtime = current_time(new_dir); ++ if (IS_DIRSYNC(new_dir)) ++ exfat_sync_inode(new_dir); ++ else ++ mark_inode_dirty(new_dir); ++ ++ i_pos = ((loff_t)EXFAT_I(old_inode)->dir.dir << 32) | ++ (EXFAT_I(old_inode)->entry & 0xffffffff); ++ exfat_unhash_inode(old_inode); ++ exfat_hash_inode(old_inode, i_pos); ++ if (IS_DIRSYNC(new_dir)) ++ exfat_sync_inode(old_inode); ++ else ++ mark_inode_dirty(old_inode); ++ ++ if (S_ISDIR(old_inode->i_mode) && old_dir != new_dir) { ++ drop_nlink(old_dir); ++ if (!new_inode) ++ inc_nlink(new_dir); ++ } ++ ++ inode_inc_iversion(old_dir); ++ old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); ++ if (IS_DIRSYNC(old_dir)) ++ exfat_sync_inode(old_dir); ++ else ++ mark_inode_dirty(old_dir); ++ ++ if (new_inode) { ++ exfat_unhash_inode(new_inode); ++ ++ /* skip drop_nlink if new_inode already has been dropped */ ++ if (new_inode->i_nlink) { ++ drop_nlink(new_inode); ++ if (S_ISDIR(new_inode->i_mode)) ++ drop_nlink(new_inode); ++ } else { ++ exfat_msg(sb, KERN_WARNING, ++ "abnormal access to an inode dropped"); ++ WARN_ON(new_inode->i_nlink == 0); ++ } ++ new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = ++ current_time(new_inode); ++ } ++ ++unlock: ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ return err; ++} ++ ++const struct inode_operations exfat_dir_inode_operations = { ++ .create = exfat_create, ++ .lookup = exfat_lookup, ++ .unlink = exfat_unlink, ++ .mkdir = exfat_mkdir, ++ .rmdir = exfat_rmdir, ++ .rename = exfat_rename, ++ .setattr = exfat_setattr, ++ .getattr = exfat_getattr, ++}; +-- +2.31.1 + diff --git a/SOURCES/0004-exfat-add-directory-operations.patch b/SOURCES/0004-exfat-add-directory-operations.patch new file mode 100644 index 0000000..49bc2f7 --- /dev/null +++ b/SOURCES/0004-exfat-add-directory-operations.patch @@ -0,0 +1,1267 @@ +From ca06197382bde0a3bc20215595d1c9ce20c6e341 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:35 +0900 +Subject: [Backport ca06197382bd] exfat: add directory operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of directory operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/dir.c | 1238 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 1238 insertions(+) + create mode 100644 src/dir.c + +diff --git a/src/dir.c b/src/dir.c +new file mode 100644 +index 0000000000000000000000000000000000000000..4b91afb0f0515c06036821468b667ea14b43654d +--- /dev/null ++++ b/src/dir.c +@@ -0,0 +1,1238 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++static int exfat_extract_uni_name(struct exfat_dentry *ep, ++ unsigned short *uniname) ++{ ++ int i, len = 0; ++ ++ for (i = 0; i < EXFAT_FILE_NAME_LEN; i++) { ++ *uniname = le16_to_cpu(ep->dentry.name.unicode_0_14[i]); ++ if (*uniname == 0x0) ++ return len; ++ uniname++; ++ len++; ++ } ++ ++ *uniname = 0x0; ++ return len; ++ ++} ++ ++static void exfat_get_uniname_from_ext_entry(struct super_block *sb, ++ struct exfat_chain *p_dir, int entry, unsigned short *uniname) ++{ ++ int i; ++ struct exfat_dentry *ep; ++ struct exfat_entry_set_cache *es; ++ ++ es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES, &ep); ++ if (!es) ++ return; ++ ++ if (es->num_entries < 3) ++ goto free_es; ++ ++ ep += 2; ++ ++ /* ++ * First entry : file entry ++ * Second entry : stream-extension entry ++ * Third entry : first file-name entry ++ * So, the index of first file-name dentry should start from 2. ++ */ ++ for (i = 2; i < es->num_entries; i++, ep++) { ++ /* end of name entry */ ++ if (exfat_get_entry_type(ep) != TYPE_EXTEND) ++ goto free_es; ++ ++ exfat_extract_uni_name(ep, uniname); ++ uniname += EXFAT_FILE_NAME_LEN; ++ } ++ ++free_es: ++ kfree(es); ++} ++ ++/* read a directory entry from the opened directory */ ++static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) ++{ ++ int i, dentries_per_clu, dentries_per_clu_bits = 0; ++ unsigned int type, clu_offset; ++ sector_t sector; ++ struct exfat_chain dir, clu; ++ struct exfat_uni_name uni_name; ++ struct exfat_dentry *ep; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ unsigned int dentry = ei->rwoffset & 0xFFFFFFFF; ++ struct buffer_head *bh; ++ ++ /* check if the given file ID is opened */ ++ if (ei->type != TYPE_DIR) ++ return -EPERM; ++ ++ if (ei->entry == -1) ++ exfat_chain_set(&dir, sbi->root_dir, 0, ALLOC_FAT_CHAIN); ++ else ++ exfat_chain_set(&dir, ei->start_clu, ++ EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); ++ ++ dentries_per_clu = sbi->dentries_per_clu; ++ dentries_per_clu_bits = ilog2(dentries_per_clu); ++ ++ clu_offset = dentry >> dentries_per_clu_bits; ++ exfat_chain_dup(&clu, &dir); ++ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ clu.dir += clu_offset; ++ clu.size -= clu_offset; ++ } else { ++ /* hint_information */ ++ if (clu_offset > 0 && ei->hint_bmap.off != EXFAT_EOF_CLUSTER && ++ ei->hint_bmap.off > 0 && clu_offset >= ei->hint_bmap.off) { ++ clu_offset -= ei->hint_bmap.off; ++ clu.dir = ei->hint_bmap.clu; ++ } ++ ++ while (clu_offset > 0) { ++ if (exfat_get_next_cluster(sb, &(clu.dir))) ++ return -EIO; ++ ++ clu_offset--; ++ } ++ } ++ ++ while (clu.dir != EXFAT_EOF_CLUSTER) { ++ i = dentry & (dentries_per_clu - 1); ++ ++ for ( ; i < dentries_per_clu; i++, dentry++) { ++ ep = exfat_get_dentry(sb, &clu, i, &bh, §or); ++ if (!ep) ++ return -EIO; ++ ++ type = exfat_get_entry_type(ep); ++ if (type == TYPE_UNUSED) { ++ brelse(bh); ++ break; ++ } ++ ++ if (type != TYPE_FILE && type != TYPE_DIR) { ++ brelse(bh); ++ continue; ++ } ++ ++ dir_entry->attr = le16_to_cpu(ep->dentry.file.attr); ++ exfat_get_entry_time(sbi, &dir_entry->crtime, ++ ep->dentry.file.create_tz, ++ ep->dentry.file.create_time, ++ ep->dentry.file.create_date, ++ ep->dentry.file.create_time_ms); ++ exfat_get_entry_time(sbi, &dir_entry->mtime, ++ ep->dentry.file.modify_tz, ++ ep->dentry.file.modify_time, ++ ep->dentry.file.modify_date, ++ ep->dentry.file.modify_time_ms); ++ exfat_get_entry_time(sbi, &dir_entry->atime, ++ ep->dentry.file.access_tz, ++ ep->dentry.file.access_time, ++ ep->dentry.file.access_date, ++ 0); ++ ++ *uni_name.name = 0x0; ++ exfat_get_uniname_from_ext_entry(sb, &dir, dentry, ++ 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); ++ if (!ep) ++ return -EIO; ++ dir_entry->size = ++ le64_to_cpu(ep->dentry.stream.valid_size); ++ brelse(bh); ++ ++ ei->hint_bmap.off = dentry >> dentries_per_clu_bits; ++ ei->hint_bmap.clu = clu.dir; ++ ++ ei->rwoffset = ++dentry; ++ return 0; ++ } ++ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ if (--clu.size > 0) ++ clu.dir++; ++ else ++ clu.dir = EXFAT_EOF_CLUSTER; ++ } else { ++ if (exfat_get_next_cluster(sb, &(clu.dir))) ++ return -EIO; ++ } ++ } ++ ++ dir_entry->namebuf.lfn[0] = '\0'; ++ ei->rwoffset = dentry; ++ return 0; ++} ++ ++static void exfat_init_namebuf(struct exfat_dentry_namebuf *nb) ++{ ++ nb->lfn = NULL; ++ nb->lfnbuf_len = 0; ++} ++ ++static int exfat_alloc_namebuf(struct exfat_dentry_namebuf *nb) ++{ ++ nb->lfn = __getname(); ++ if (!nb->lfn) ++ return -ENOMEM; ++ nb->lfnbuf_len = MAX_VFSNAME_BUF_SIZE; ++ return 0; ++} ++ ++static void exfat_free_namebuf(struct exfat_dentry_namebuf *nb) ++{ ++ if (!nb->lfn) ++ return; ++ ++ __putname(nb->lfn); ++ exfat_init_namebuf(nb); ++} ++ ++/* skip iterating emit_dots when dir is empty */ ++#define ITER_POS_FILLED_DOTS (2) ++static int exfat_iterate(struct file *filp, struct dir_context *ctx) ++{ ++ struct inode *inode = filp->f_path.dentry->d_inode; ++ struct super_block *sb = inode->i_sb; ++ struct inode *tmp; ++ struct exfat_dir_entry de; ++ struct exfat_dentry_namebuf *nb = &(de.namebuf); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ unsigned long inum; ++ loff_t cpos, i_pos; ++ int err = 0, fake_offset = 0; ++ ++ exfat_init_namebuf(nb); ++ mutex_lock(&EXFAT_SB(sb)->s_lock); ++ ++ cpos = ctx->pos; ++ if (!dir_emit_dots(filp, ctx)) ++ goto unlock; ++ ++ if (ctx->pos == ITER_POS_FILLED_DOTS) { ++ cpos = 0; ++ fake_offset = 1; ++ } ++ ++ if (cpos & (DENTRY_SIZE - 1)) { ++ err = -ENOENT; ++ goto unlock; ++ } ++ ++ /* name buffer should be allocated before use */ ++ err = exfat_alloc_namebuf(nb); ++ if (err) ++ goto unlock; ++get_new: ++ ei->rwoffset = EXFAT_B_TO_DEN(cpos); ++ ++ if (cpos >= i_size_read(inode)) ++ goto end_of_dir; ++ ++ err = exfat_readdir(inode, &de); ++ if (err) { ++ /* ++ * At least we tried to read a sector. Move cpos to next sector ++ * position (should be aligned). ++ */ ++ if (err == -EIO) { ++ cpos += 1 << (sb->s_blocksize_bits); ++ cpos &= ~(sb->s_blocksize - 1); ++ } ++ ++ err = -EIO; ++ goto end_of_dir; ++ } ++ ++ cpos = EXFAT_DEN_TO_B(ei->rwoffset); ++ ++ if (!nb->lfn[0]) ++ goto end_of_dir; ++ ++ i_pos = ((loff_t)ei->start_clu << 32) | ++ ((ei->rwoffset - 1) & 0xffffffff); ++ tmp = exfat_iget(sb, i_pos); ++ if (tmp) { ++ inum = tmp->i_ino; ++ iput(tmp); ++ } else { ++ inum = iunique(sb, EXFAT_ROOT_INO); ++ } ++ ++ /* ++ * Before calling dir_emit(), sb_lock should be released. ++ * Because page fault can occur in dir_emit() when the size ++ * of buffer given from user is larger than one page size. ++ */ ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, ++ (de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) ++ goto out_unlocked; ++ mutex_lock(&EXFAT_SB(sb)->s_lock); ++ ctx->pos = cpos; ++ goto get_new; ++ ++end_of_dir: ++ if (!cpos && fake_offset) ++ cpos = ITER_POS_FILLED_DOTS; ++ ctx->pos = cpos; ++unlock: ++ mutex_unlock(&EXFAT_SB(sb)->s_lock); ++out_unlocked: ++ /* ++ * To improve performance, free namebuf after unlock sb_lock. ++ * If namebuf is not allocated, this function do nothing ++ */ ++ exfat_free_namebuf(nb); ++ return err; ++} ++ ++const struct file_operations exfat_dir_operations = { ++ .llseek = generic_file_llseek, ++ .read = generic_read_dir, ++ .iterate = exfat_iterate, ++ .fsync = generic_file_fsync, ++}; ++ ++int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu) ++{ ++ int ret; ++ ++ exfat_chain_set(clu, EXFAT_EOF_CLUSTER, 0, ALLOC_NO_FAT_CHAIN); ++ ++ ret = exfat_alloc_cluster(inode, 1, clu); ++ if (ret) ++ return ret; ++ ++ return exfat_zeroed_cluster(inode, clu->dir); ++} ++ ++int exfat_calc_num_entries(struct exfat_uni_name *p_uniname) ++{ ++ int len; ++ ++ len = p_uniname->name_len; ++ if (len == 0) ++ return -EINVAL; ++ ++ /* 1 file entry + 1 stream entry + name entries */ ++ return ((len - 1) / EXFAT_FILE_NAME_LEN + 3); ++} ++ ++unsigned int exfat_get_entry_type(struct exfat_dentry *ep) ++{ ++ if (ep->type == EXFAT_UNUSED) ++ return TYPE_UNUSED; ++ if (IS_EXFAT_DELETED(ep->type)) ++ return TYPE_DELETED; ++ if (ep->type == EXFAT_INVAL) ++ return TYPE_INVALID; ++ if (IS_EXFAT_CRITICAL_PRI(ep->type)) { ++ if (ep->type == EXFAT_BITMAP) ++ return TYPE_BITMAP; ++ if (ep->type == EXFAT_UPCASE) ++ return TYPE_UPCASE; ++ if (ep->type == EXFAT_VOLUME) ++ return TYPE_VOLUME; ++ if (ep->type == EXFAT_FILE) { ++ if (le16_to_cpu(ep->dentry.file.attr) & ATTR_SUBDIR) ++ return TYPE_DIR; ++ return TYPE_FILE; ++ } ++ return TYPE_CRITICAL_PRI; ++ } ++ if (IS_EXFAT_BENIGN_PRI(ep->type)) { ++ if (ep->type == EXFAT_GUID) ++ return TYPE_GUID; ++ if (ep->type == EXFAT_PADDING) ++ return TYPE_PADDING; ++ if (ep->type == EXFAT_ACLTAB) ++ return TYPE_ACLTAB; ++ return TYPE_BENIGN_PRI; ++ } ++ if (IS_EXFAT_CRITICAL_SEC(ep->type)) { ++ if (ep->type == EXFAT_STREAM) ++ return TYPE_STREAM; ++ if (ep->type == EXFAT_NAME) ++ return TYPE_EXTEND; ++ if (ep->type == EXFAT_ACL) ++ return TYPE_ACL; ++ return TYPE_CRITICAL_SEC; ++ } ++ return TYPE_BENIGN_SEC; ++} ++ ++static void exfat_set_entry_type(struct exfat_dentry *ep, unsigned int type) ++{ ++ if (type == TYPE_UNUSED) { ++ ep->type = EXFAT_UNUSED; ++ } else if (type == TYPE_DELETED) { ++ ep->type &= EXFAT_DELETE; ++ } else if (type == TYPE_STREAM) { ++ ep->type = EXFAT_STREAM; ++ } else if (type == TYPE_EXTEND) { ++ ep->type = EXFAT_NAME; ++ } else if (type == TYPE_BITMAP) { ++ ep->type = EXFAT_BITMAP; ++ } else if (type == TYPE_UPCASE) { ++ ep->type = EXFAT_UPCASE; ++ } else if (type == TYPE_VOLUME) { ++ ep->type = EXFAT_VOLUME; ++ } else if (type == TYPE_DIR) { ++ ep->type = EXFAT_FILE; ++ ep->dentry.file.attr = cpu_to_le16(ATTR_SUBDIR); ++ } else if (type == TYPE_FILE) { ++ ep->type = EXFAT_FILE; ++ ep->dentry.file.attr = cpu_to_le16(ATTR_ARCHIVE); ++ } ++} ++ ++static void exfat_init_stream_entry(struct exfat_dentry *ep, ++ unsigned char flags, unsigned int start_clu, ++ unsigned long long size) ++{ ++ exfat_set_entry_type(ep, TYPE_STREAM); ++ ep->dentry.stream.flags = flags; ++ 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); ++} ++ ++static void exfat_init_name_entry(struct exfat_dentry *ep, ++ unsigned short *uniname) ++{ ++ int i; ++ ++ exfat_set_entry_type(ep, TYPE_EXTEND); ++ ep->dentry.name.flags = 0x0; ++ ++ for (i = 0; i < EXFAT_FILE_NAME_LEN; i++) { ++ ep->dentry.name.unicode_0_14[i] = cpu_to_le16(*uniname); ++ if (*uniname == 0x0) ++ break; ++ uniname++; ++ } ++} ++ ++int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, ++ int entry, unsigned int type, unsigned int start_clu, ++ unsigned long long size) ++{ ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct timespec64 ts = current_time(inode); ++ sector_t sector; ++ struct exfat_dentry *ep; ++ struct buffer_head *bh; ++ ++ /* ++ * 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); ++ if (!ep) ++ return -EIO; ++ ++ exfat_set_entry_type(ep, type); ++ exfat_set_entry_time(sbi, &ts, ++ &ep->dentry.file.create_tz, ++ &ep->dentry.file.create_time, ++ &ep->dentry.file.create_date, ++ &ep->dentry.file.create_time_ms); ++ 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_ms); ++ exfat_set_entry_time(sbi, &ts, ++ &ep->dentry.file.access_tz, ++ &ep->dentry.file.access_time, ++ &ep->dentry.file.access_date, ++ NULL); ++ ++ exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ brelse(bh); ++ ++ ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh, §or); ++ if (!ep) ++ return -EIO; ++ ++ exfat_init_stream_entry(ep, ++ (type == TYPE_FILE) ? ALLOC_FAT_CHAIN : ALLOC_NO_FAT_CHAIN, ++ start_clu, size); ++ exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ brelse(bh); ++ ++ return 0; ++} ++ ++int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, ++ int entry) ++{ ++ struct super_block *sb = inode->i_sb; ++ int ret = 0; ++ int i, num_entries; ++ sector_t sector; ++ unsigned short chksum; ++ struct exfat_dentry *ep, *fep; ++ struct buffer_head *fbh, *bh; ++ ++ fep = exfat_get_dentry(sb, p_dir, entry, &fbh, §or); ++ if (!fep) ++ return -EIO; ++ ++ num_entries = fep->dentry.file.num_ext + 1; ++ chksum = exfat_calc_chksum_2byte(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); ++ if (!ep) { ++ ret = -EIO; ++ goto release_fbh; ++ } ++ chksum = exfat_calc_chksum_2byte(ep, DENTRY_SIZE, chksum, ++ CS_DEFAULT); ++ brelse(bh); ++ } ++ ++ fep->dentry.file.checksum = cpu_to_le16(chksum); ++ exfat_update_bh(sb, fbh, IS_DIRSYNC(inode)); ++release_fbh: ++ brelse(fbh); ++ return ret; ++} ++ ++int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, ++ int entry, int num_entries, struct exfat_uni_name *p_uniname) ++{ ++ 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); ++ if (!ep) ++ return -EIO; ++ ++ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); ++ exfat_update_bh(sb, bh, sync); ++ brelse(bh); ++ ++ ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh, §or); ++ if (!ep) ++ return -EIO; ++ ++ ep->dentry.stream.name_len = p_uniname->name_len; ++ ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash); ++ exfat_update_bh(sb, bh, sync); ++ brelse(bh); ++ ++ for (i = EXFAT_FIRST_CLUSTER; i < num_entries; i++) { ++ ep = exfat_get_dentry(sb, p_dir, entry + i, &bh, §or); ++ if (!ep) ++ return -EIO; ++ ++ exfat_init_name_entry(ep, uniname); ++ exfat_update_bh(sb, bh, sync); ++ brelse(bh); ++ uniname += EXFAT_FILE_NAME_LEN; ++ } ++ ++ exfat_update_dir_chksum(inode, p_dir, entry); ++ return 0; ++} ++ ++int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, ++ int entry, int order, int num_entries) ++{ ++ 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); ++ if (!ep) ++ return -EIO; ++ ++ exfat_set_entry_type(ep, TYPE_DELETED); ++ exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ brelse(bh); ++ } ++ ++ return 0; ++} ++ ++int exfat_update_dir_chksum_with_entry_set(struct super_block *sb, ++ struct exfat_entry_set_cache *es, int sync) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct buffer_head *bh; ++ sector_t sec = es->sector; ++ unsigned int off = es->offset; ++ int chksum_type = CS_DIR_ENTRY, i, num_entries = es->num_entries; ++ unsigned int buf_off = (off - es->offset); ++ unsigned int remaining_byte_in_sector, copy_entries, clu; ++ unsigned short chksum = 0; ++ ++ for (i = 0; i < num_entries; i++) { ++ chksum = exfat_calc_chksum_2byte(&es->entries[i], DENTRY_SIZE, ++ chksum, chksum_type); ++ chksum_type = CS_DEFAULT; ++ } ++ ++ es->entries[0].dentry.file.checksum = cpu_to_le16(chksum); ++ ++ while (num_entries) { ++ /* write per sector base */ ++ remaining_byte_in_sector = (1 << sb->s_blocksize_bits) - off; ++ copy_entries = min_t(int, ++ EXFAT_B_TO_DEN(remaining_byte_in_sector), ++ num_entries); ++ bh = sb_bread(sb, sec); ++ if (!bh) ++ goto err_out; ++ memcpy(bh->b_data + off, ++ (unsigned char *)&es->entries[0] + buf_off, ++ EXFAT_DEN_TO_B(copy_entries)); ++ exfat_update_bh(sb, bh, sync); ++ brelse(bh); ++ num_entries -= copy_entries; ++ ++ if (num_entries) { ++ /* get next sector */ ++ if (exfat_is_last_sector_in_cluster(sbi, sec)) { ++ clu = exfat_sector_to_cluster(sbi, sec); ++ if (es->alloc_flag == ALLOC_NO_FAT_CHAIN) ++ clu++; ++ else if (exfat_get_next_cluster(sb, &clu)) ++ goto err_out; ++ sec = exfat_cluster_to_sector(sbi, clu); ++ } else { ++ sec++; ++ } ++ off = 0; ++ buf_off += EXFAT_DEN_TO_B(copy_entries); ++ } ++ } ++ ++ return 0; ++err_out: ++ return -EIO; ++} ++ ++static int exfat_walk_fat_chain(struct super_block *sb, ++ struct exfat_chain *p_dir, unsigned int byte_offset, ++ unsigned int *clu) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned int clu_offset; ++ unsigned int cur_clu; ++ ++ clu_offset = EXFAT_B_TO_CLU(byte_offset, sbi); ++ cur_clu = p_dir->dir; ++ ++ if (p_dir->flags == ALLOC_NO_FAT_CHAIN) { ++ cur_clu += clu_offset; ++ } else { ++ while (clu_offset > 0) { ++ if (exfat_get_next_cluster(sb, &cur_clu)) ++ return -EIO; ++ if (cur_clu == EXFAT_EOF_CLUSTER) { ++ exfat_fs_error(sb, ++ "invalid dentry access beyond EOF (clu : %u, eidx : %d)", ++ p_dir->dir, ++ EXFAT_B_TO_DEN(byte_offset)); ++ return -EIO; ++ } ++ clu_offset--; ++ } ++ } ++ ++ *clu = cur_clu; ++ return 0; ++} ++ ++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; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ off = EXFAT_DEN_TO_B(entry); ++ ++ ret = exfat_walk_fat_chain(sb, p_dir, off, &clu); ++ if (ret) ++ return ret; ++ ++ /* byte offset in cluster */ ++ off = EXFAT_CLU_OFFSET(off, sbi); ++ ++ /* byte offset in sector */ ++ *offset = EXFAT_BLK_OFFSET(off, sb); ++ ++ /* sector offset in cluster */ ++ *sector = EXFAT_B_TO_BLK(off, sb); ++ *sector += exfat_cluster_to_sector(sbi, clu); ++ return 0; ++} ++ ++#define EXFAT_MAX_RA_SIZE (128*1024) ++static int exfat_dir_readahead(struct super_block *sb, sector_t sec) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct buffer_head *bh; ++ unsigned int max_ra_count = EXFAT_MAX_RA_SIZE >> sb->s_blocksize_bits; ++ unsigned int page_ra_count = PAGE_SIZE >> sb->s_blocksize_bits; ++ unsigned int adj_ra_count = max(sbi->sect_per_clus, page_ra_count); ++ unsigned int ra_count = min(adj_ra_count, max_ra_count); ++ ++ /* Read-ahead is not required */ ++ if (sbi->sect_per_clus == 1) ++ return 0; ++ ++ if (sec < sbi->data_start_sector) { ++ exfat_msg(sb, KERN_ERR, ++ "requested sector is invalid(sect:%llu, root:%llu)", ++ (unsigned long long)sec, sbi->data_start_sector); ++ return -EIO; ++ } ++ ++ /* Not sector aligned with ra_count, resize ra_count to page size */ ++ if ((sec - sbi->data_start_sector) & (ra_count - 1)) ++ ra_count = page_ra_count; ++ ++ bh = sb_find_get_block(sb, sec); ++ if (!bh || !buffer_uptodate(bh)) { ++ unsigned int i; ++ ++ for (i = 0; i < ra_count; i++) ++ sb_breadahead(sb, (sector_t)(sec + i)); ++ } ++ brelse(bh); ++ return 0; ++} ++ ++struct exfat_dentry *exfat_get_dentry(struct super_block *sb, ++ struct exfat_chain *p_dir, int entry, struct buffer_head **bh, ++ sector_t *sector) ++{ ++ unsigned int dentries_per_page = EXFAT_B_TO_DEN(PAGE_SIZE); ++ int off; ++ sector_t sec; ++ ++ if (p_dir->dir == DIR_DELETED) { ++ exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry\n"); ++ return NULL; ++ } ++ ++ if (exfat_find_location(sb, p_dir, entry, &sec, &off)) ++ return NULL; ++ ++ if (p_dir->dir != EXFAT_FREE_CLUSTER && ++ !(entry & (dentries_per_page - 1))) ++ exfat_dir_readahead(sb, sec); ++ ++ *bh = sb_bread(sb, sec); ++ if (!*bh) ++ return NULL; ++ ++ if (sector) ++ *sector = sec; ++ return (struct exfat_dentry *)((*bh)->b_data + off); ++} ++ ++enum exfat_validate_dentry_mode { ++ ES_MODE_STARTED, ++ ES_MODE_GET_FILE_ENTRY, ++ ES_MODE_GET_STRM_ENTRY, ++ ES_MODE_GET_NAME_ENTRY, ++ ES_MODE_GET_CRITICAL_SEC_ENTRY, ++}; ++ ++static bool exfat_validate_entry(unsigned int type, ++ enum exfat_validate_dentry_mode *mode) ++{ ++ if (type == TYPE_UNUSED || type == TYPE_DELETED) ++ return false; ++ ++ switch (*mode) { ++ case ES_MODE_STARTED: ++ if (type != TYPE_FILE && type != TYPE_DIR) ++ return false; ++ *mode = ES_MODE_GET_FILE_ENTRY; ++ return true; ++ case ES_MODE_GET_FILE_ENTRY: ++ if (type != TYPE_STREAM) ++ return false; ++ *mode = ES_MODE_GET_STRM_ENTRY; ++ return true; ++ case ES_MODE_GET_STRM_ENTRY: ++ if (type != TYPE_EXTEND) ++ return false; ++ *mode = ES_MODE_GET_NAME_ENTRY; ++ return true; ++ case ES_MODE_GET_NAME_ENTRY: ++ if (type == TYPE_STREAM) ++ return false; ++ if (type != TYPE_EXTEND) { ++ if (!(type & TYPE_CRITICAL_SEC)) ++ return false; ++ *mode = ES_MODE_GET_CRITICAL_SEC_ENTRY; ++ } ++ return true; ++ case ES_MODE_GET_CRITICAL_SEC_ENTRY: ++ if (type == TYPE_EXTEND || type == TYPE_STREAM) ++ return false; ++ if ((type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC) ++ return false; ++ return true; ++ default: ++ WARN_ON_ONCE(1); ++ return false; ++ } ++} ++ ++/* ++ * Returns a set of dentries for a file or dir. ++ * ++ * Note that this is a copy (dump) of dentries so that user should ++ * call write_entry_set() to apply changes made in this entry set ++ * to the real device. ++ * ++ * in: ++ * sb+p_dir+entry: indicates a file/dir ++ * type: specifies how many dentries should be included. ++ * out: ++ * file_ep: will point the first dentry(= file dentry) on success ++ * return: ++ * pointer of entry set on success, ++ * NULL on failure. ++ */ ++struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, ++ struct exfat_chain *p_dir, int entry, unsigned int type, ++ struct exfat_dentry **file_ep) ++{ ++ int ret; ++ unsigned int off, byte_offset, clu = 0; ++ unsigned int entry_type; ++ sector_t sec; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_entry_set_cache *es; ++ struct exfat_dentry *ep, *pos; ++ unsigned char num_entries; ++ enum exfat_validate_dentry_mode mode = ES_MODE_STARTED; ++ struct buffer_head *bh; ++ ++ if (p_dir->dir == DIR_DELETED) { ++ exfat_msg(sb, KERN_ERR, "access to deleted dentry\n"); ++ return NULL; ++ } ++ ++ byte_offset = EXFAT_DEN_TO_B(entry); ++ ret = exfat_walk_fat_chain(sb, p_dir, byte_offset, &clu); ++ if (ret) ++ return NULL; ++ ++ /* byte offset in cluster */ ++ byte_offset = EXFAT_CLU_OFFSET(byte_offset, sbi); ++ ++ /* byte offset in sector */ ++ off = EXFAT_BLK_OFFSET(byte_offset, sb); ++ ++ /* sector offset in cluster */ ++ sec = EXFAT_B_TO_BLK(byte_offset, sb); ++ sec += exfat_cluster_to_sector(sbi, clu); ++ ++ bh = sb_bread(sb, sec); ++ if (!bh) ++ return NULL; ++ ++ ep = (struct exfat_dentry *)(bh->b_data + off); ++ entry_type = exfat_get_entry_type(ep); ++ ++ if (entry_type != TYPE_FILE && entry_type != TYPE_DIR) ++ goto release_bh; ++ ++ num_entries = type == ES_ALL_ENTRIES ? ++ ep->dentry.file.num_ext + 1 : type; ++ es = kmalloc(struct_size(es, entries, num_entries), GFP_KERNEL); ++ if (!es) ++ goto release_bh; ++ ++ es->num_entries = num_entries; ++ es->sector = sec; ++ es->offset = off; ++ es->alloc_flag = p_dir->flags; ++ ++ pos = &es->entries[0]; ++ ++ while (num_entries) { ++ if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) ++ goto free_es; ++ ++ /* copy dentry */ ++ memcpy(pos, ep, sizeof(struct exfat_dentry)); ++ ++ if (--num_entries == 0) ++ break; ++ ++ if (((off + DENTRY_SIZE) & (sb->s_blocksize - 1)) < ++ (off & (sb->s_blocksize - 1))) { ++ /* get the next sector */ ++ if (exfat_is_last_sector_in_cluster(sbi, sec)) { ++ if (es->alloc_flag == ALLOC_NO_FAT_CHAIN) ++ clu++; ++ else if (exfat_get_next_cluster(sb, &clu)) ++ goto free_es; ++ sec = exfat_cluster_to_sector(sbi, clu); ++ } else { ++ sec++; ++ } ++ ++ brelse(bh); ++ bh = sb_bread(sb, sec); ++ if (!bh) ++ goto free_es; ++ off = 0; ++ ep = (struct exfat_dentry *)bh->b_data; ++ } else { ++ ep++; ++ off += DENTRY_SIZE; ++ } ++ pos++; ++ } ++ ++ if (file_ep) ++ *file_ep = &es->entries[0]; ++ brelse(bh); ++ return es; ++ ++free_es: ++ kfree(es); ++release_bh: ++ brelse(bh); ++ return NULL; ++} ++ ++enum { ++ DIRENT_STEP_FILE, ++ DIRENT_STEP_STRM, ++ DIRENT_STEP_NAME, ++ DIRENT_STEP_SECD, ++}; ++ ++/* ++ * return values: ++ * >= 0 : return dir entiry position with the name in dir ++ * -EEXIST : (root dir, ".") it is the root dir itself ++ * -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 i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; ++ int order, step, name_len = 0; ++ int dentries_per_clu, num_empty = 0; ++ unsigned int entry_type; ++ unsigned short *uniname = NULL; ++ struct exfat_chain clu; ++ struct exfat_hint *hint_stat = &ei->hint_stat; ++ struct exfat_hint_femp candi_empty; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ dentries_per_clu = sbi->dentries_per_clu; ++ ++ exfat_chain_dup(&clu, p_dir); ++ ++ if (hint_stat->eidx) { ++ clu.dir = hint_stat->clu; ++ dentry = hint_stat->eidx; ++ end_eidx = dentry; ++ } ++ ++ candi_empty.eidx = EXFAT_HINT_NONE; ++rewind: ++ order = 0; ++ step = DIRENT_STEP_FILE; ++ while (clu.dir != EXFAT_EOF_CLUSTER) { ++ i = dentry & (dentries_per_clu - 1); ++ for (; i < dentries_per_clu; i++, dentry++) { ++ struct exfat_dentry *ep; ++ struct buffer_head *bh; ++ ++ if (rewind && dentry == end_eidx) ++ goto not_found; ++ ++ ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); ++ if (!ep) ++ return -EIO; ++ ++ entry_type = exfat_get_entry_type(ep); ++ ++ if (entry_type == TYPE_UNUSED || ++ entry_type == TYPE_DELETED) { ++ step = DIRENT_STEP_FILE; ++ ++ num_empty++; ++ if (candi_empty.eidx == EXFAT_HINT_NONE && ++ num_empty == 1) { ++ exfat_chain_set(&candi_empty.cur, ++ clu.dir, clu.size, clu.flags); ++ } ++ ++ if (candi_empty.eidx == EXFAT_HINT_NONE && ++ num_empty >= num_entries) { ++ candi_empty.eidx = ++ dentry - (num_empty - 1); ++ WARN_ON(candi_empty.eidx < 0); ++ candi_empty.count = num_empty; ++ ++ if (ei->hint_femp.eidx == ++ EXFAT_HINT_NONE || ++ candi_empty.eidx <= ++ ei->hint_femp.eidx) { ++ memcpy(&ei->hint_femp, ++ &candi_empty, ++ sizeof(candi_empty)); ++ } ++ } ++ ++ brelse(bh); ++ if (entry_type == TYPE_UNUSED) ++ goto not_found; ++ continue; ++ } ++ ++ num_empty = 0; ++ candi_empty.eidx = EXFAT_HINT_NONE; ++ ++ if (entry_type == TYPE_FILE || entry_type == TYPE_DIR) { ++ step = DIRENT_STEP_FILE; ++ if (type == TYPE_ALL || type == entry_type) { ++ num_ext = ep->dentry.file.num_ext; ++ step = DIRENT_STEP_STRM; ++ } ++ brelse(bh); ++ continue; ++ } ++ ++ if (entry_type == TYPE_STREAM) { ++ unsigned short name_hash; ++ ++ if (step != DIRENT_STEP_STRM) { ++ step = DIRENT_STEP_FILE; ++ brelse(bh); ++ continue; ++ } ++ step = DIRENT_STEP_FILE; ++ name_hash = le16_to_cpu( ++ ep->dentry.stream.name_hash); ++ if (p_uniname->name_hash == name_hash && ++ p_uniname->name_len == ++ ep->dentry.stream.name_len) { ++ step = DIRENT_STEP_NAME; ++ order = 1; ++ name_len = 0; ++ } ++ brelse(bh); ++ continue; ++ } ++ ++ brelse(bh); ++ if (entry_type == TYPE_EXTEND) { ++ unsigned short entry_uniname[16], unichar; ++ ++ if (step != DIRENT_STEP_NAME) { ++ step = DIRENT_STEP_FILE; ++ continue; ++ } ++ ++ if (++order == 2) ++ uniname = p_uniname->name; ++ else ++ uniname += EXFAT_FILE_NAME_LEN; ++ ++ len = exfat_extract_uni_name(ep, entry_uniname); ++ name_len += len; ++ ++ unichar = *(uniname+len); ++ *(uniname+len) = 0x0; ++ ++ if (exfat_uniname_ncmp(sb, uniname, ++ entry_uniname, len)) { ++ step = DIRENT_STEP_FILE; ++ } else if (p_uniname->name_len == name_len) { ++ if (order == num_ext) ++ goto found; ++ step = DIRENT_STEP_SECD; ++ } ++ ++ *(uniname+len) = unichar; ++ continue; ++ } ++ ++ if (entry_type & ++ (TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) { ++ if (step == DIRENT_STEP_SECD) { ++ if (++order == num_ext) ++ goto found; ++ continue; ++ } ++ } ++ step = DIRENT_STEP_FILE; ++ } ++ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ if (--clu.size > 0) ++ clu.dir++; ++ else ++ clu.dir = EXFAT_EOF_CLUSTER; ++ } else { ++ if (exfat_get_next_cluster(sb, &clu.dir)) ++ return -EIO; ++ } ++ } ++ ++not_found: ++ /* ++ * We started at not 0 index,so we should try to find target ++ * from 0 index to the index we started at. ++ */ ++ if (!rewind && end_eidx) { ++ rewind = 1; ++ dentry = 0; ++ clu.dir = p_dir->dir; ++ /* reset empty hint */ ++ num_empty = 0; ++ candi_empty.eidx = EXFAT_HINT_NONE; ++ goto rewind; ++ } ++ ++ /* initialized hint_stat */ ++ hint_stat->clu = p_dir->dir; ++ hint_stat->eidx = 0; ++ return -ENOENT; ++ ++found: ++ /* next dentry we'll find is out of this cluster */ ++ if (!((dentry + 1) & (dentries_per_clu - 1))) { ++ int ret = 0; ++ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ if (--clu.size > 0) ++ clu.dir++; ++ else ++ clu.dir = EXFAT_EOF_CLUSTER; ++ } else { ++ ret = exfat_get_next_cluster(sb, &clu.dir); ++ } ++ ++ if (ret || clu.dir != EXFAT_EOF_CLUSTER) { ++ /* just initialized hint_stat */ ++ hint_stat->clu = p_dir->dir; ++ hint_stat->eidx = 0; ++ return (dentry - num_ext); ++ } ++ } ++ ++ hint_stat->clu = clu.dir; ++ hint_stat->eidx = dentry + 1; ++ return dentry - num_ext; ++} ++ ++int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir, ++ int entry, struct exfat_dentry *ep) ++{ ++ int i, count = 0; ++ unsigned int type; ++ struct exfat_dentry *ext_ep; ++ struct buffer_head *bh; ++ ++ for (i = 0, entry++; i < ep->dentry.file.num_ext; i++, entry++) { ++ ext_ep = exfat_get_dentry(sb, p_dir, entry, &bh, NULL); ++ if (!ext_ep) ++ return -EIO; ++ ++ type = exfat_get_entry_type(ext_ep); ++ brelse(bh); ++ if (type == TYPE_EXTEND || type == TYPE_STREAM) ++ count++; ++ else ++ break; ++ } ++ return count; ++} ++ ++int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir) ++{ ++ int i, count = 0; ++ int dentries_per_clu; ++ unsigned int entry_type; ++ struct exfat_chain clu; ++ struct exfat_dentry *ep; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct buffer_head *bh; ++ ++ dentries_per_clu = sbi->dentries_per_clu; ++ ++ exfat_chain_dup(&clu, 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); ++ if (!ep) ++ return -EIO; ++ entry_type = exfat_get_entry_type(ep); ++ brelse(bh); ++ ++ if (entry_type == TYPE_UNUSED) ++ return count; ++ if (entry_type != TYPE_DIR) ++ continue; ++ count++; ++ } ++ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ if (--clu.size > 0) ++ clu.dir++; ++ else ++ clu.dir = EXFAT_EOF_CLUSTER; ++ } else { ++ if (exfat_get_next_cluster(sb, &(clu.dir))) ++ return -EIO; ++ } ++ } ++ ++ return count; ++} +-- +2.31.1 + diff --git a/SOURCES/0005-exfat-add-file-operations.patch b/SOURCES/0005-exfat-add-file-operations.patch new file mode 100644 index 0000000..fa59ab3 --- /dev/null +++ b/SOURCES/0005-exfat-add-file-operations.patch @@ -0,0 +1,390 @@ +From 98d917047e8b7f4bb2ff61d81b0ccd94a00444f9 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:36 +0900 +Subject: [Backport 98d917047e8b] exfat: add file operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of file operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Reviewed-by: Arnd Bergmann +Signed-off-by: Al Viro +--- + src/file.c | 360 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 360 insertions(+) + create mode 100644 src/file.c + +diff --git a/src/file.c b/src/file.c +new file mode 100644 +index 0000000000000000000000000000000000000000..483f683757aa87420ba347afd3c9db9e94b8a14d +--- /dev/null ++++ b/src/file.c +@@ -0,0 +1,360 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++static int exfat_cont_expand(struct inode *inode, loff_t size) ++{ ++ struct address_space *mapping = inode->i_mapping; ++ loff_t start = i_size_read(inode), count = size - i_size_read(inode); ++ int err, err2; ++ ++ err = generic_cont_expand_simple(inode, size); ++ if (err) ++ return err; ++ ++ inode->i_ctime = inode->i_mtime = current_time(inode); ++ mark_inode_dirty(inode); ++ ++ if (!IS_SYNC(inode)) ++ return 0; ++ ++ err = filemap_fdatawrite_range(mapping, start, start + count - 1); ++ err2 = sync_mapping_buffers(mapping); ++ if (!err) ++ err = err2; ++ err2 = write_inode_now(inode, 1); ++ if (!err) ++ err = err2; ++ if (err) ++ return err; ++ ++ return filemap_fdatawait_range(mapping, start, start + count - 1); ++} ++ ++static bool exfat_allow_set_time(struct exfat_sb_info *sbi, struct inode *inode) ++{ ++ mode_t allow_utime = sbi->options.allow_utime; ++ ++ if (!uid_eq(current_fsuid(), inode->i_uid)) { ++ if (in_group_p(inode->i_gid)) ++ allow_utime >>= 3; ++ if (allow_utime & MAY_WRITE) ++ return true; ++ } ++ ++ /* use a default check */ ++ return false; ++} ++ ++static int exfat_sanitize_mode(const struct exfat_sb_info *sbi, ++ struct inode *inode, umode_t *mode_ptr) ++{ ++ mode_t i_mode, mask, perm; ++ ++ i_mode = inode->i_mode; ++ ++ mask = (S_ISREG(i_mode) || S_ISLNK(i_mode)) ? ++ sbi->options.fs_fmask : sbi->options.fs_dmask; ++ perm = *mode_ptr & ~(S_IFMT | mask); ++ ++ /* Of the r and x bits, all (subject to umask) must be present.*/ ++ if ((perm & 0555) != (i_mode & 0555)) ++ return -EPERM; ++ ++ if (exfat_mode_can_hold_ro(inode)) { ++ /* ++ * Of the w bits, either all (subject to umask) or none must ++ * be present. ++ */ ++ if ((perm & 0222) && ((perm & 0222) != (0222 & ~mask))) ++ return -EPERM; ++ } else { ++ /* ++ * If exfat_mode_can_hold_ro(inode) is false, can't change ++ * w bits. ++ */ ++ if ((perm & 0222) != (0222 & ~mask)) ++ return -EPERM; ++ } ++ ++ *mode_ptr &= S_IFMT | perm; ++ ++ return 0; ++} ++ ++/* resize the file length */ ++int __exfat_truncate(struct inode *inode, loff_t new_size) ++{ ++ unsigned int num_clusters_new, num_clusters_phys; ++ unsigned int last_clu = EXFAT_FREE_CLUSTER; ++ struct exfat_chain clu; ++ struct exfat_dentry *ep, *ep2; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct exfat_entry_set_cache *es = NULL; ++ int evict = (ei->dir.dir == DIR_DELETED) ? 1 : 0; ++ ++ /* check if the given file ID is opened */ ++ if (ei->type != TYPE_FILE && ei->type != TYPE_DIR) ++ return -EPERM; ++ ++ exfat_set_vol_flags(sb, VOL_DIRTY); ++ ++ 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); ++ ++ exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags); ++ ++ if (new_size > 0) { ++ /* ++ * Truncate FAT chain num_clusters after the first cluster ++ * num_clusters = min(new, phys); ++ */ ++ unsigned int num_clusters = ++ min(num_clusters_new, num_clusters_phys); ++ ++ /* ++ * Follow FAT chain ++ * (defensive coding - works fine even with corrupted FAT table ++ */ ++ if (clu.flags == ALLOC_NO_FAT_CHAIN) { ++ clu.dir += num_clusters; ++ clu.size -= num_clusters; ++ } else { ++ while (num_clusters > 0) { ++ last_clu = clu.dir; ++ if (exfat_get_next_cluster(sb, &(clu.dir))) ++ return -EIO; ++ ++ num_clusters--; ++ clu.size--; ++ } ++ } ++ } else { ++ ei->flags = ALLOC_NO_FAT_CHAIN; ++ ei->start_clu = EXFAT_EOF_CLUSTER; ++ } ++ ++ i_size_write(inode, new_size); ++ ++ if (ei->type == TYPE_FILE) ++ ei->attr |= ATTR_ARCHIVE; ++ ++ /* update the directory entry */ ++ if (!evict) { ++ struct timespec64 ts; ++ ++ es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ++ ES_ALL_ENTRIES, &ep); ++ if (!es) ++ return -EIO; ++ ep2 = ep + 1; ++ ++ ts = current_time(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_ms); ++ ep->dentry.file.attr = cpu_to_le16(ei->attr); ++ ++ /* File size should be zero if there is no cluster allocated */ ++ if (ei->start_clu == EXFAT_EOF_CLUSTER) { ++ ep->dentry.stream.valid_size = 0; ++ ep->dentry.stream.size = 0; ++ } else { ++ ep->dentry.stream.valid_size = cpu_to_le64(new_size); ++ ep->dentry.stream.size = ep->dentry.stream.valid_size; ++ } ++ ++ if (new_size == 0) { ++ /* Any directory can not be truncated to zero */ ++ WARN_ON(ei->type != TYPE_FILE); ++ ++ ep2->dentry.stream.flags = ALLOC_FAT_CHAIN; ++ ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; ++ } ++ ++ if (exfat_update_dir_chksum_with_entry_set(sb, es, ++ inode_needs_sync(inode))) ++ return -EIO; ++ kfree(es); ++ } ++ ++ /* cut off from the FAT chain */ ++ if (ei->flags == ALLOC_FAT_CHAIN && last_clu != EXFAT_FREE_CLUSTER && ++ last_clu != EXFAT_EOF_CLUSTER) { ++ if (exfat_ent_set(sb, last_clu, EXFAT_EOF_CLUSTER)) ++ return -EIO; ++ } ++ ++ /* invalidate cache and free the clusters */ ++ /* clear exfat cache */ ++ exfat_cache_inval_inode(inode); ++ ++ /* hint information */ ++ ei->hint_bmap.off = EXFAT_EOF_CLUSTER; ++ ei->hint_bmap.clu = EXFAT_EOF_CLUSTER; ++ if (ei->rwoffset > new_size) ++ ei->rwoffset = new_size; ++ ++ /* hint_stat will be used if this is directory. */ ++ ei->hint_stat.eidx = 0; ++ ei->hint_stat.clu = ei->start_clu; ++ ei->hint_femp.eidx = EXFAT_HINT_NONE; ++ ++ /* free the clusters */ ++ if (exfat_free_cluster(inode, &clu)) ++ return -EIO; ++ ++ exfat_set_vol_flags(sb, VOL_CLEAN); ++ ++ return 0; ++} ++ ++void exfat_truncate(struct inode *inode, loff_t size) ++{ ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned int blocksize = 1 << inode->i_blkbits; ++ loff_t aligned_size; ++ int err; ++ ++ mutex_lock(&sbi->s_lock); ++ if (EXFAT_I(inode)->start_clu == 0) { ++ /* ++ * Empty start_clu != ~0 (not allocated) ++ */ ++ exfat_fs_error(sb, "tried to truncate zeroed cluster."); ++ goto write_size; ++ } ++ ++ err = __exfat_truncate(inode, i_size_read(inode)); ++ if (err) ++ goto write_size; ++ ++ inode->i_ctime = inode->i_mtime = current_time(inode); ++ 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; ++write_size: ++ aligned_size = i_size_read(inode); ++ if (aligned_size & (blocksize - 1)) { ++ aligned_size |= (blocksize - 1); ++ aligned_size++; ++ } ++ ++ if (EXFAT_I(inode)->i_size_ondisk > i_size_read(inode)) ++ EXFAT_I(inode)->i_size_ondisk = aligned_size; ++ ++ if (EXFAT_I(inode)->i_size_aligned > i_size_read(inode)) ++ EXFAT_I(inode)->i_size_aligned = aligned_size; ++ mutex_unlock(&sbi->s_lock); ++} ++ ++int exfat_getattr(const struct path *path, struct kstat *stat, ++ unsigned int request_mask, unsigned int query_flags) ++{ ++ struct inode *inode = d_backing_inode(path->dentry); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ ++ generic_fillattr(inode, stat); ++ stat->result_mask |= STATX_BTIME; ++ stat->btime.tv_sec = ei->i_crtime.tv_sec; ++ stat->btime.tv_nsec = ei->i_crtime.tv_nsec; ++ stat->blksize = EXFAT_SB(inode->i_sb)->cluster_size; ++ return 0; ++} ++ ++int exfat_setattr(struct dentry *dentry, struct iattr *attr) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb); ++ struct inode *inode = dentry->d_inode; ++ unsigned int ia_valid; ++ int error; ++ ++ if ((attr->ia_valid & ATTR_SIZE) && ++ attr->ia_size > i_size_read(inode)) { ++ error = exfat_cont_expand(inode, attr->ia_size); ++ if (error || attr->ia_valid == ATTR_SIZE) ++ return error; ++ attr->ia_valid &= ~ATTR_SIZE; ++ } ++ ++ /* Check for setting the inode time. */ ++ ia_valid = attr->ia_valid; ++ if ((ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) && ++ exfat_allow_set_time(sbi, inode)) { ++ attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET | ++ ATTR_TIMES_SET); ++ } ++ ++ error = setattr_prepare(dentry, attr); ++ attr->ia_valid = ia_valid; ++ if (error) ++ goto out; ++ ++ if (((attr->ia_valid & ATTR_UID) && ++ !uid_eq(attr->ia_uid, sbi->options.fs_uid)) || ++ ((attr->ia_valid & ATTR_GID) && ++ !gid_eq(attr->ia_gid, sbi->options.fs_gid)) || ++ ((attr->ia_valid & ATTR_MODE) && ++ (attr->ia_mode & ~(S_IFREG | S_IFLNK | S_IFDIR | 0777)))) { ++ error = -EPERM; ++ goto out; ++ } ++ ++ /* ++ * We don't return -EPERM here. Yes, strange, but this is too ++ * old behavior. ++ */ ++ if (attr->ia_valid & ATTR_MODE) { ++ if (exfat_sanitize_mode(sbi, inode, &attr->ia_mode) < 0) ++ attr->ia_valid &= ~ATTR_MODE; ++ } ++ ++ if (attr->ia_valid & ATTR_SIZE) { ++ error = exfat_block_truncate_page(inode, attr->ia_size); ++ if (error) ++ goto out; ++ ++ down_write(&EXFAT_I(inode)->truncate_lock); ++ truncate_setsize(inode, attr->ia_size); ++ exfat_truncate(inode, attr->ia_size); ++ up_write(&EXFAT_I(inode)->truncate_lock); ++ } ++ ++ setattr_copy(inode, attr); ++ mark_inode_dirty(inode); ++ ++out: ++ return error; ++} ++ ++const struct file_operations exfat_file_operations = { ++ .llseek = generic_file_llseek, ++ .read_iter = generic_file_read_iter, ++ .write_iter = generic_file_write_iter, ++ .mmap = generic_file_mmap, ++ .fsync = generic_file_fsync, ++ .splice_read = generic_file_splice_read, ++}; ++ ++const struct inode_operations exfat_file_inode_operations = { ++ .setattr = exfat_setattr, ++ .getattr = exfat_getattr, ++}; +-- +2.31.1 + diff --git a/SOURCES/0006-exfat-add-fat-entry-operations.patch b/SOURCES/0006-exfat-add-fat-entry-operations.patch new file mode 100644 index 0000000..cb10afb --- /dev/null +++ b/SOURCES/0006-exfat-add-fat-entry-operations.patch @@ -0,0 +1,492 @@ +From 31023864e67a5f390cefbe92f72343027dc3aa33 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:37 +0900 +Subject: [Backport 31023864e67a] exfat: add fat entry operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of fat entry operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/fatent.c | 463 ++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 463 insertions(+) + create mode 100644 src/fatent.c + +diff --git a/src/fatent.c b/src/fatent.c +new file mode 100644 +index 0000000000000000000000000000000000000000..a855b1769a96198264b134bfb45bcd4f6f6c01d1 +--- /dev/null ++++ b/src/fatent.c +@@ -0,0 +1,463 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++static int exfat_mirror_bh(struct super_block *sb, sector_t sec, ++ struct buffer_head *bh) ++{ ++ struct buffer_head *c_bh; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ sector_t sec2; ++ int err = 0; ++ ++ if (sbi->FAT2_start_sector != sbi->FAT1_start_sector) { ++ sec2 = sec - sbi->FAT1_start_sector + sbi->FAT2_start_sector; ++ c_bh = sb_getblk(sb, sec2); ++ if (!c_bh) ++ return -ENOMEM; ++ memcpy(c_bh->b_data, bh->b_data, sb->s_blocksize); ++ set_buffer_uptodate(c_bh); ++ mark_buffer_dirty(c_bh); ++ if (sb->s_flags & SB_SYNCHRONOUS) ++ err = sync_dirty_buffer(c_bh); ++ brelse(c_bh); ++ } ++ ++ return err; ++} ++ ++static int __exfat_ent_get(struct super_block *sb, unsigned int loc, ++ unsigned int *content) ++{ ++ unsigned int off; ++ sector_t sec; ++ struct buffer_head *bh; ++ ++ sec = FAT_ENT_OFFSET_SECTOR(sb, loc); ++ off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc); ++ ++ bh = sb_bread(sb, sec); ++ if (!bh) ++ return -EIO; ++ ++ *content = le32_to_cpu(*(__le32 *)(&bh->b_data[off])); ++ ++ /* remap reserved clusters to simplify code */ ++ if (*content > EXFAT_BAD_CLUSTER) ++ *content = EXFAT_EOF_CLUSTER; ++ ++ brelse(bh); ++ return 0; ++} ++ ++int exfat_ent_set(struct super_block *sb, unsigned int loc, ++ unsigned int content) ++{ ++ unsigned int off; ++ sector_t sec; ++ __le32 *fat_entry; ++ struct buffer_head *bh; ++ ++ sec = FAT_ENT_OFFSET_SECTOR(sb, loc); ++ off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc); ++ ++ bh = sb_bread(sb, sec); ++ if (!bh) ++ return -EIO; ++ ++ fat_entry = (__le32 *)&(bh->b_data[off]); ++ *fat_entry = cpu_to_le32(content); ++ exfat_update_bh(sb, bh, sb->s_flags & SB_SYNCHRONOUS); ++ 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) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ int err; ++ ++ if (!is_valid_cluster(sbi, loc)) { ++ exfat_fs_error(sb, "invalid access to FAT (entry 0x%08x)", ++ loc); ++ return -EIO; ++ } ++ ++ err = __exfat_ent_get(sb, loc, content); ++ if (err) { ++ exfat_fs_error(sb, ++ "failed to access to FAT (entry 0x%08x, err:%d)", ++ loc, err); ++ return err; ++ } ++ ++ if (*content == EXFAT_FREE_CLUSTER) { ++ exfat_fs_error(sb, ++ "invalid access to FAT free cluster (entry 0x%08x)", ++ loc); ++ return -EIO; ++ } ++ ++ if (*content == EXFAT_BAD_CLUSTER) { ++ exfat_fs_error(sb, ++ "invalid access to FAT bad cluster (entry 0x%08x)", ++ loc); ++ return -EIO; ++ } ++ ++ if (*content != EXFAT_EOF_CLUSTER && !is_valid_cluster(sbi, *content)) { ++ exfat_fs_error(sb, ++ "invalid access to FAT (entry 0x%08x) bogus content (0x%08x)", ++ loc, *content); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, ++ unsigned int len) ++{ ++ if (!len) ++ return 0; ++ ++ while (len > 1) { ++ if (exfat_ent_set(sb, chain, chain + 1)) ++ return -EIO; ++ chain++; ++ len--; ++ } ++ ++ if (exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER)) ++ return -EIO; ++ return 0; ++} ++ ++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); ++ ++ /* invalid cluster number */ ++ if (p_chain->dir == EXFAT_FREE_CLUSTER || ++ p_chain->dir == EXFAT_EOF_CLUSTER || ++ p_chain->dir < EXFAT_FIRST_CLUSTER) ++ return 0; ++ ++ /* no cluster to truncate */ ++ if (p_chain->size == 0) ++ return 0; ++ ++ /* check cluster validation */ ++ if (p_chain->dir < 2 && p_chain->dir >= sbi->num_clusters) { ++ exfat_msg(sb, KERN_ERR, "invalid start cluster (%u)", ++ p_chain->dir); ++ return -EIO; ++ } ++ ++ set_bit(EXFAT_SB_DIRTY, &sbi->s_state); ++ clu = p_chain->dir; ++ ++ if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { ++ do { ++ exfat_clear_bitmap(inode, clu); ++ clu++; ++ ++ num_clusters++; ++ } while (num_clusters < p_chain->size); ++ } else { ++ do { ++ exfat_clear_bitmap(inode, clu); ++ ++ if (exfat_get_next_cluster(sb, &clu)) ++ goto dec_used_clus; ++ ++ num_clusters++; ++ } while (clu != EXFAT_EOF_CLUSTER); ++ } ++ ++dec_used_clus: ++ sbi->used_clusters -= num_clusters; ++ return 0; ++} ++ ++int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, ++ unsigned int *ret_clu) ++{ ++ unsigned int clu, next; ++ unsigned int count = 0; ++ ++ next = p_chain->dir; ++ if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { ++ *ret_clu = next + p_chain->size - 1; ++ return 0; ++ } ++ ++ do { ++ count++; ++ clu = next; ++ if (exfat_ent_get(sb, clu, &next)) ++ return -EIO; ++ } while (next != EXFAT_EOF_CLUSTER); ++ ++ if (p_chain->size != count) { ++ exfat_fs_error(sb, ++ "bogus directory size (clus : ondisk(%d) != counted(%d))", ++ p_chain->size, count); ++ return -EIO; ++ } ++ ++ *ret_clu = clu; ++ return 0; ++} ++ ++static inline int exfat_sync_bhs(struct buffer_head **bhs, int nr_bhs) ++{ ++ int i, err = 0; ++ ++ for (i = 0; i < nr_bhs; i++) ++ write_dirty_buffer(bhs[i], 0); ++ ++ for (i = 0; i < nr_bhs; i++) { ++ wait_on_buffer(bhs[i]); ++ if (!err && !buffer_uptodate(bhs[i])) ++ err = -EIO; ++ } ++ return err; ++} ++ ++int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) ++{ ++ struct super_block *sb = dir->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct buffer_head *bhs[MAX_BUF_PER_PAGE]; ++ int nr_bhs = MAX_BUF_PER_PAGE; ++ sector_t blknr, last_blknr; ++ int err, i, n; ++ ++ blknr = exfat_cluster_to_sector(sbi, clu); ++ last_blknr = blknr + sbi->sect_per_clus; ++ ++ if (last_blknr > sbi->num_sectors && sbi->num_sectors > 0) { ++ exfat_fs_error_ratelimit(sb, ++ "%s: out of range(sect:%llu len:%u)", ++ __func__, (unsigned long long)blknr, ++ sbi->sect_per_clus); ++ return -EIO; ++ } ++ ++ /* Zeroing the unused blocks on this cluster */ ++ n = 0; ++ while (blknr < last_blknr) { ++ bhs[n] = sb_getblk(sb, blknr); ++ if (!bhs[n]) { ++ err = -ENOMEM; ++ goto release_bhs; ++ } ++ memset(bhs[n]->b_data, 0, sb->s_blocksize); ++ exfat_update_bh(sb, bhs[n], 0); ++ ++ n++; ++ blknr++; ++ ++ if (n == nr_bhs) { ++ if (IS_DIRSYNC(dir)) { ++ err = exfat_sync_bhs(bhs, n); ++ if (err) ++ goto release_bhs; ++ } ++ ++ for (i = 0; i < n; i++) ++ brelse(bhs[i]); ++ n = 0; ++ } ++ } ++ ++ if (IS_DIRSYNC(dir)) { ++ err = exfat_sync_bhs(bhs, n); ++ if (err) ++ goto release_bhs; ++ } ++ ++ for (i = 0; i < n; i++) ++ brelse(bhs[i]); ++ ++ return 0; ++ ++release_bhs: ++ exfat_msg(sb, KERN_ERR, "failed zeroed sect %llu\n", ++ (unsigned long long)blknr); ++ for (i = 0; i < n; i++) ++ bforget(bhs[i]); ++ return err; ++} ++ ++int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, ++ struct exfat_chain *p_chain) ++{ ++ int ret = -ENOSPC; ++ unsigned int num_clusters = 0, total_cnt; ++ unsigned int hint_clu, new_clu, last_clu = EXFAT_EOF_CLUSTER; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ total_cnt = EXFAT_DATA_CLUSTER_COUNT(sbi); ++ ++ if (unlikely(total_cnt < sbi->used_clusters)) { ++ exfat_fs_error_ratelimit(sb, ++ "%s: invalid used clusters(t:%u,u:%u)\n", ++ __func__, total_cnt, sbi->used_clusters); ++ return -EIO; ++ } ++ ++ if (num_alloc > total_cnt - sbi->used_clusters) ++ return -ENOSPC; ++ ++ hint_clu = p_chain->dir; ++ /* find new cluster */ ++ if (hint_clu == EXFAT_EOF_CLUSTER) { ++ if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) { ++ exfat_msg(sb, KERN_ERR, ++ "sbi->clu_srch_ptr is invalid (%u)\n", ++ sbi->clu_srch_ptr); ++ sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; ++ } ++ ++ hint_clu = exfat_find_free_bitmap(sb, sbi->clu_srch_ptr); ++ if (hint_clu == EXFAT_EOF_CLUSTER) ++ return -ENOSPC; ++ } ++ ++ /* check cluster validation */ ++ if (hint_clu < EXFAT_FIRST_CLUSTER && hint_clu >= sbi->num_clusters) { ++ exfat_msg(sb, KERN_ERR, "hint_cluster is invalid (%u)\n", ++ hint_clu); ++ hint_clu = EXFAT_FIRST_CLUSTER; ++ if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { ++ if (exfat_chain_cont_cluster(sb, p_chain->dir, ++ num_clusters)) ++ return -EIO; ++ p_chain->flags = ALLOC_FAT_CHAIN; ++ } ++ } ++ ++ set_bit(EXFAT_SB_DIRTY, &sbi->s_state); ++ ++ p_chain->dir = EXFAT_EOF_CLUSTER; ++ ++ while ((new_clu = exfat_find_free_bitmap(sb, hint_clu)) != ++ EXFAT_EOF_CLUSTER) { ++ if (new_clu != hint_clu && ++ p_chain->flags == ALLOC_NO_FAT_CHAIN) { ++ if (exfat_chain_cont_cluster(sb, p_chain->dir, ++ num_clusters)) { ++ ret = -EIO; ++ goto free_cluster; ++ } ++ p_chain->flags = ALLOC_FAT_CHAIN; ++ } ++ ++ /* update allocation bitmap */ ++ if (exfat_set_bitmap(inode, new_clu)) { ++ ret = -EIO; ++ goto free_cluster; ++ } ++ ++ num_clusters++; ++ ++ /* update FAT table */ ++ if (p_chain->flags == ALLOC_FAT_CHAIN) { ++ if (exfat_ent_set(sb, new_clu, EXFAT_EOF_CLUSTER)) { ++ ret = -EIO; ++ goto free_cluster; ++ } ++ } ++ ++ if (p_chain->dir == EXFAT_EOF_CLUSTER) { ++ p_chain->dir = new_clu; ++ } else if (p_chain->flags == ALLOC_FAT_CHAIN) { ++ if (exfat_ent_set(sb, last_clu, new_clu)) { ++ ret = -EIO; ++ goto free_cluster; ++ } ++ } ++ last_clu = new_clu; ++ ++ if (--num_alloc == 0) { ++ sbi->clu_srch_ptr = hint_clu; ++ sbi->used_clusters += num_clusters; ++ ++ p_chain->size += num_clusters; ++ return 0; ++ } ++ ++ hint_clu = new_clu + 1; ++ if (hint_clu >= sbi->num_clusters) { ++ hint_clu = EXFAT_FIRST_CLUSTER; ++ ++ if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { ++ if (exfat_chain_cont_cluster(sb, p_chain->dir, ++ num_clusters)) { ++ ret = -EIO; ++ goto free_cluster; ++ } ++ p_chain->flags = ALLOC_FAT_CHAIN; ++ } ++ } ++ } ++free_cluster: ++ if (num_clusters) ++ exfat_free_cluster(inode, p_chain); ++ return ret; ++} ++ ++int exfat_count_num_clusters(struct super_block *sb, ++ struct exfat_chain *p_chain, unsigned int *ret_count) ++{ ++ unsigned int i, count; ++ unsigned int clu; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ if (!p_chain->dir || p_chain->dir == EXFAT_EOF_CLUSTER) { ++ *ret_count = 0; ++ return 0; ++ } ++ ++ if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { ++ *ret_count = p_chain->size; ++ return 0; ++ } ++ ++ clu = p_chain->dir; ++ count = 0; ++ for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters; i++) { ++ count++; ++ if (exfat_ent_get(sb, clu, &clu)) ++ return -EIO; ++ if (clu == EXFAT_EOF_CLUSTER) ++ break; ++ } ++ ++ *ret_count = count; ++ return 0; ++} +-- +2.31.1 + diff --git a/SOURCES/0007-exfat-add-bitmap-operations.patch b/SOURCES/0007-exfat-add-bitmap-operations.patch new file mode 100644 index 0000000..07bf795 --- /dev/null +++ b/SOURCES/0007-exfat-add-bitmap-operations.patch @@ -0,0 +1,309 @@ +From 1e49a94cf707204b66a3fb242f2814712c941f52 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:38 +0900 +Subject: [Backport 1e49a94cf707] exfat: add bitmap operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of bitmap operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/balloc.c | 280 ++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 280 insertions(+) + create mode 100644 src/balloc.c + +diff --git a/src/balloc.c b/src/balloc.c +new file mode 100644 +index 0000000000000000000000000000000000000000..6a04cc02565a1f3823ad9a109238bad9553a82f1 +--- /dev/null ++++ b/src/balloc.c +@@ -0,0 +1,280 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++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*/ ++}; ++ ++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 ++ */ ++static int exfat_allocate_bitmap(struct super_block *sb, ++ struct exfat_dentry *ep) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ long long map_size; ++ unsigned int i, need_map_size; ++ sector_t sector; ++ ++ sbi->map_clu = le32_to_cpu(ep->dentry.bitmap.start_clu); ++ map_size = le64_to_cpu(ep->dentry.bitmap.size); ++ need_map_size = ((EXFAT_DATA_CLUSTER_COUNT(sbi) - 1) / BITS_PER_BYTE) ++ + 1; ++ if (need_map_size != map_size) { ++ exfat_msg(sb, KERN_ERR, ++ "bogus allocation bitmap size(need : %u, cur : %lld)", ++ need_map_size, map_size); ++ /* ++ * Only allowed when bogus allocation ++ * bitmap size is large ++ */ ++ if (need_map_size > map_size) ++ return -EIO; ++ } ++ sbi->map_sectors = ((need_map_size - 1) >> ++ (sb->s_blocksize_bits)) + 1; ++ sbi->vol_amap = kmalloc_array(sbi->map_sectors, ++ sizeof(struct buffer_head *), GFP_KERNEL); ++ if (!sbi->vol_amap) ++ return -ENOMEM; ++ ++ sector = exfat_cluster_to_sector(sbi, sbi->map_clu); ++ for (i = 0; i < sbi->map_sectors; i++) { ++ sbi->vol_amap[i] = sb_bread(sb, sector + i); ++ if (!sbi->vol_amap[i]) { ++ /* release all buffers and free vol_amap */ ++ int j = 0; ++ ++ while (j < i) ++ brelse(sbi->vol_amap[j++]); ++ ++ kfree(sbi->vol_amap); ++ sbi->vol_amap = NULL; ++ return -EIO; ++ } ++ } ++ ++ sbi->pbr_bh = NULL; ++ return 0; ++} ++ ++int exfat_load_bitmap(struct super_block *sb) ++{ ++ unsigned int i, type; ++ struct exfat_chain clu; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ exfat_chain_set(&clu, sbi->root_dir, 0, ALLOC_FAT_CHAIN); ++ while (clu.dir != EXFAT_EOF_CLUSTER) { ++ for (i = 0; i < sbi->dentries_per_clu; i++) { ++ struct exfat_dentry *ep; ++ struct buffer_head *bh; ++ ++ ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); ++ if (!ep) ++ return -EIO; ++ ++ type = exfat_get_entry_type(ep); ++ if (type == TYPE_UNUSED) ++ break; ++ if (type != TYPE_BITMAP) ++ continue; ++ if (ep->dentry.bitmap.flags == 0x0) { ++ int err; ++ ++ err = exfat_allocate_bitmap(sb, ep); ++ brelse(bh); ++ return err; ++ } ++ brelse(bh); ++ } ++ ++ if (exfat_get_next_cluster(sb, &clu.dir)) ++ return -EIO; ++ } ++ ++ return -EINVAL; ++} ++ ++void exfat_free_bitmap(struct exfat_sb_info *sbi) ++{ ++ int i; ++ ++ brelse(sbi->pbr_bh); ++ ++ for (i = 0; i < sbi->map_sectors; i++) ++ __brelse(sbi->vol_amap[i]); ++ ++ 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 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); ++ 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(sb, sbi->vol_amap[i], IS_DIRSYNC(inode)); ++ 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) ++{ ++ int i, b; ++ unsigned int ent_idx; ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_mount_options *opts = &sbi->options; ++ ++ WARN_ON(clu < EXFAT_FIRST_CLUSTER); ++ 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(sb, sbi->vol_amap[i], IS_DIRSYNC(inode)); ++ ++ if (opts->discard) { ++ int ret_discard; ++ ++ ret_discard = sb_issue_discard(sb, ++ exfat_cluster_to_sector(sbi, clu + ++ EXFAT_RESERVED_CLUSTERS), ++ (1 << sbi->sect_per_clus_bits), GFP_NOFS, 0); ++ ++ if (ret_discard == -EOPNOTSUPP) { ++ exfat_msg(sb, KERN_ERR, ++ "discard not supported by device, disabling"); ++ opts->discard = 0; ++ } ++ } ++} ++ ++/* ++ * If the value of "clu" is 0, it means cluster 2 which is the first cluster of ++ * the cluster heap. ++ */ ++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; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ 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)); ++ 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); ++ if (clu_mask > 0) { ++ k |= clu_mask; ++ clu_mask = 0; ++ } ++ if (k < 0xFF) { ++ clu_free = clu_base + free_bit[k]; ++ if (clu_free < sbi->num_clusters) ++ return clu_free; ++ } ++ clu_base += BITS_PER_BYTE; ++ ++ if (++map_b >= sb->s_blocksize || ++ clu_base >= sbi->num_clusters) { ++ if (++map_i >= sbi->map_sectors) { ++ clu_base = EXFAT_FIRST_CLUSTER; ++ map_i = 0; ++ } ++ map_b = 0; ++ } ++ } ++ ++ return EXFAT_EOF_CLUSTER; ++} ++ ++int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ 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}; ++ ++ 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) { ++ 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]; ++ } ++ ++ *ret_count = count; ++ return 0; ++} +-- +2.31.1 + diff --git a/SOURCES/0008-exfat-add-exfat-cache.patch b/SOURCES/0008-exfat-add-exfat-cache.patch new file mode 100644 index 0000000..824dfb0 --- /dev/null +++ b/SOURCES/0008-exfat-add-exfat-cache.patch @@ -0,0 +1,354 @@ +From c35b6810c4952ae5776607e2c1d6a587425d5834 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:39 +0900 +Subject: [Backport c35b6810c495] exfat: add exfat cache +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of exfat cache. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/cache.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 325 insertions(+) + create mode 100644 src/cache.c + +diff --git a/src/cache.c b/src/cache.c +new file mode 100644 +index 0000000000000000000000000000000000000000..03d0824fc368a5b10241082e271dd4bfefd26ada +--- /dev/null ++++ b/src/cache.c +@@ -0,0 +1,325 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * linux/fs/fat/cache.c ++ * ++ * Written 1992,1993 by Werner Almesberger ++ * ++ * Mar 1999. AV. Changed cache, so that it uses the starting cluster instead ++ * of inode number. ++ * May 1999. AV. Fixed the bogosity with FAT32 (read "FAT28"). Fscking lusers. ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++#define EXFAT_CACHE_VALID 0 ++#define EXFAT_MAX_CACHE 16 ++ ++struct exfat_cache { ++ struct list_head cache_list; ++ unsigned int nr_contig; /* number of contiguous clusters */ ++ unsigned int fcluster; /* cluster number in the file. */ ++ unsigned int dcluster; /* cluster number on disk. */ ++}; ++ ++struct exfat_cache_id { ++ unsigned int id; ++ unsigned int nr_contig; ++ unsigned int fcluster; ++ unsigned int dcluster; ++}; ++ ++static struct kmem_cache *exfat_cachep; ++ ++static void exfat_cache_init_once(void *c) ++{ ++ struct exfat_cache *cache = (struct exfat_cache *)c; ++ ++ INIT_LIST_HEAD(&cache->cache_list); ++} ++ ++int exfat_cache_init(void) ++{ ++ exfat_cachep = kmem_cache_create("exfat_cache", ++ sizeof(struct exfat_cache), ++ 0, SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, ++ exfat_cache_init_once); ++ if (!exfat_cachep) ++ return -ENOMEM; ++ return 0; ++} ++ ++void exfat_cache_shutdown(void) ++{ ++ if (!exfat_cachep) ++ return; ++ kmem_cache_destroy(exfat_cachep); ++} ++ ++void exfat_cache_init_inode(struct inode *inode) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ ++ spin_lock_init(&ei->cache_lru_lock); ++ ei->nr_caches = 0; ++ ei->cache_valid_id = EXFAT_CACHE_VALID + 1; ++ INIT_LIST_HEAD(&ei->cache_lru); ++} ++ ++static inline struct exfat_cache *exfat_cache_alloc(void) ++{ ++ return kmem_cache_alloc(exfat_cachep, GFP_NOFS); ++} ++ ++static inline void exfat_cache_free(struct exfat_cache *cache) ++{ ++ WARN_ON(!list_empty(&cache->cache_list)); ++ kmem_cache_free(exfat_cachep, cache); ++} ++ ++static inline void exfat_cache_update_lru(struct inode *inode, ++ struct exfat_cache *cache) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ ++ if (ei->cache_lru.next != &cache->cache_list) ++ list_move(&cache->cache_list, &ei->cache_lru); ++} ++ ++static unsigned int exfat_cache_lookup(struct inode *inode, ++ unsigned int fclus, struct exfat_cache_id *cid, ++ unsigned int *cached_fclus, unsigned int *cached_dclus) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ static struct exfat_cache nohit = { .fcluster = 0, }; ++ struct exfat_cache *hit = &nohit, *p; ++ unsigned int offset = EXFAT_EOF_CLUSTER; ++ ++ spin_lock(&ei->cache_lru_lock); ++ list_for_each_entry(p, &ei->cache_lru, cache_list) { ++ /* Find the cache of "fclus" or nearest cache. */ ++ if (p->fcluster <= fclus && hit->fcluster < p->fcluster) { ++ hit = p; ++ if (hit->fcluster + hit->nr_contig < fclus) { ++ offset = hit->nr_contig; ++ } else { ++ offset = fclus - hit->fcluster; ++ break; ++ } ++ } ++ } ++ if (hit != &nohit) { ++ exfat_cache_update_lru(inode, hit); ++ ++ cid->id = ei->cache_valid_id; ++ cid->nr_contig = hit->nr_contig; ++ cid->fcluster = hit->fcluster; ++ cid->dcluster = hit->dcluster; ++ *cached_fclus = cid->fcluster + offset; ++ *cached_dclus = cid->dcluster + offset; ++ } ++ spin_unlock(&ei->cache_lru_lock); ++ ++ return offset; ++} ++ ++static struct exfat_cache *exfat_cache_merge(struct inode *inode, ++ struct exfat_cache_id *new) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct exfat_cache *p; ++ ++ list_for_each_entry(p, &ei->cache_lru, cache_list) { ++ /* Find the same part as "new" in cluster-chain. */ ++ if (p->fcluster == new->fcluster) { ++ if (new->nr_contig > p->nr_contig) ++ p->nr_contig = new->nr_contig; ++ return p; ++ } ++ } ++ return NULL; ++} ++ ++static void exfat_cache_add(struct inode *inode, ++ struct exfat_cache_id *new) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct exfat_cache *cache, *tmp; ++ ++ if (new->fcluster == EXFAT_EOF_CLUSTER) /* dummy cache */ ++ return; ++ ++ spin_lock(&ei->cache_lru_lock); ++ if (new->id != EXFAT_CACHE_VALID && ++ new->id != ei->cache_valid_id) ++ goto unlock; /* this cache was invalidated */ ++ ++ cache = exfat_cache_merge(inode, new); ++ if (cache == NULL) { ++ if (ei->nr_caches < EXFAT_MAX_CACHE) { ++ ei->nr_caches++; ++ spin_unlock(&ei->cache_lru_lock); ++ ++ tmp = exfat_cache_alloc(); ++ if (!tmp) { ++ spin_lock(&ei->cache_lru_lock); ++ ei->nr_caches--; ++ spin_unlock(&ei->cache_lru_lock); ++ return; ++ } ++ ++ spin_lock(&ei->cache_lru_lock); ++ cache = exfat_cache_merge(inode, new); ++ if (cache != NULL) { ++ ei->nr_caches--; ++ exfat_cache_free(tmp); ++ goto out_update_lru; ++ } ++ cache = tmp; ++ } else { ++ struct list_head *p = ei->cache_lru.prev; ++ ++ cache = list_entry(p, ++ struct exfat_cache, cache_list); ++ } ++ cache->fcluster = new->fcluster; ++ cache->dcluster = new->dcluster; ++ cache->nr_contig = new->nr_contig; ++ } ++out_update_lru: ++ exfat_cache_update_lru(inode, cache); ++unlock: ++ spin_unlock(&ei->cache_lru_lock); ++} ++ ++/* ++ * Cache invalidation occurs rarely, thus the LRU chain is not updated. It ++ * fixes itself after a while. ++ */ ++static void __exfat_cache_inval_inode(struct inode *inode) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct exfat_cache *cache; ++ ++ while (!list_empty(&ei->cache_lru)) { ++ cache = list_entry(ei->cache_lru.next, ++ struct exfat_cache, cache_list); ++ list_del_init(&cache->cache_list); ++ ei->nr_caches--; ++ exfat_cache_free(cache); ++ } ++ /* Update. The copy of caches before this id is discarded. */ ++ ei->cache_valid_id++; ++ if (ei->cache_valid_id == EXFAT_CACHE_VALID) ++ ei->cache_valid_id++; ++} ++ ++void exfat_cache_inval_inode(struct inode *inode) ++{ ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ ++ spin_lock(&ei->cache_lru_lock); ++ __exfat_cache_inval_inode(inode); ++ spin_unlock(&ei->cache_lru_lock); ++} ++ ++static inline int cache_contiguous(struct exfat_cache_id *cid, ++ unsigned int dclus) ++{ ++ cid->nr_contig++; ++ return cid->dcluster + cid->nr_contig == dclus; ++} ++ ++static inline void cache_init(struct exfat_cache_id *cid, ++ unsigned int fclus, unsigned int dclus) ++{ ++ cid->id = EXFAT_CACHE_VALID; ++ cid->fcluster = fclus; ++ cid->dcluster = dclus; ++ cid->nr_contig = 0; ++} ++ ++int exfat_get_cluster(struct inode *inode, unsigned int cluster, ++ unsigned int *fclus, unsigned int *dclus, ++ unsigned int *last_dclus, int allow_eof) ++{ ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned int limit = sbi->num_clusters; ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ struct exfat_cache_id cid; ++ unsigned int content; ++ ++ if (ei->start_clu == EXFAT_FREE_CLUSTER) { ++ exfat_fs_error(sb, ++ "invalid access to exfat cache (entry 0x%08x)", ++ ei->start_clu); ++ return -EIO; ++ } ++ ++ *fclus = 0; ++ *dclus = ei->start_clu; ++ *last_dclus = *dclus; ++ ++ /* ++ * Don`t use exfat_cache if zero offset or non-cluster allocation ++ */ ++ if (cluster == 0 || *dclus == EXFAT_EOF_CLUSTER) ++ return 0; ++ ++ cache_init(&cid, EXFAT_EOF_CLUSTER, EXFAT_EOF_CLUSTER); ++ ++ if (exfat_cache_lookup(inode, cluster, &cid, fclus, dclus) == ++ EXFAT_EOF_CLUSTER) { ++ /* ++ * dummy, always not contiguous ++ * This is reinitialized by cache_init(), later. ++ */ ++ WARN_ON(cid.id != EXFAT_CACHE_VALID || ++ cid.fcluster != EXFAT_EOF_CLUSTER || ++ cid.dcluster != EXFAT_EOF_CLUSTER || ++ cid.nr_contig != 0); ++ } ++ ++ if (*fclus == cluster) ++ return 0; ++ ++ while (*fclus < cluster) { ++ /* prevent the infinite loop of cluster chain */ ++ if (*fclus > limit) { ++ exfat_fs_error(sb, ++ "detected the cluster chain loop (i_pos %u)", ++ (*fclus)); ++ return -EIO; ++ } ++ ++ if (exfat_ent_get(sb, *dclus, &content)) ++ return -EIO; ++ ++ *last_dclus = *dclus; ++ *dclus = content; ++ (*fclus)++; ++ ++ if (content == EXFAT_EOF_CLUSTER) { ++ if (!allow_eof) { ++ exfat_fs_error(sb, ++ "invalid cluster chain (i_pos %u, last_clus 0x%08x is EOF)", ++ *fclus, (*last_dclus)); ++ return -EIO; ++ } ++ ++ break; ++ } ++ ++ if (!cache_contiguous(&cid, *dclus)) ++ cache_init(&cid, *fclus, *dclus); ++ } ++ ++ exfat_cache_add(inode, &cid); ++ return 0; ++} +-- +2.31.1 + diff --git a/SOURCES/0009-exfat-add-misc-operations.patch b/SOURCES/0009-exfat-add-misc-operations.patch new file mode 100644 index 0000000..e4c0cf3 --- /dev/null +++ b/SOURCES/0009-exfat-add-misc-operations.patch @@ -0,0 +1,193 @@ +From 772b29cca528fcb21374bb3e597d848779938d16 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:40 +0900 +Subject: [Backport 772b29cca528] exfat: add misc operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of misc operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Arnd Bergmann +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/misc.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 163 insertions(+) + create mode 100644 src/misc.c + +diff --git a/src/misc.c b/src/misc.c +new file mode 100644 +index 0000000000000000000000000000000000000000..14a3300848f6a0fb754b9d34bdacbc31b90f5d07 +--- /dev/null ++++ b/src/misc.c +@@ -0,0 +1,163 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Written 1992,1993 by Werner Almesberger ++ * 22/11/2000 - Fixed fat_date_unix2dos for dates earlier than 01/01/1980 ++ * and date_dos2unix for date==0 by Igor Zhbanov(bsg@uniyar.ac.ru) ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++/* ++ * exfat_fs_error reports a file system problem that might indicate fa data ++ * corruption/inconsistency. Depending on 'errors' mount option the ++ * panic() is called, or error message is printed FAT and nothing is done, ++ * or filesystem is remounted read-only (default behavior). ++ * In case the file system is remounted read-only, it can be made writable ++ * again by remounting it. ++ */ ++void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) ++{ ++ struct exfat_mount_options *opts = &EXFAT_SB(sb)->options; ++ va_list args; ++ struct va_format vaf; ++ ++ if (report) { ++ va_start(args, fmt); ++ vaf.fmt = fmt; ++ vaf.va = &args; ++ exfat_msg(sb, KERN_ERR, "error, %pV\n", &vaf); ++ va_end(args); ++ } ++ ++ if (opts->errors == EXFAT_ERRORS_PANIC) { ++ panic("exFAT-fs (%s): fs panic from previous error\n", ++ sb->s_id); ++ } else if (opts->errors == EXFAT_ERRORS_RO && !sb_rdonly(sb)) { ++ sb->s_flags |= SB_RDONLY; ++ exfat_msg(sb, KERN_ERR, "Filesystem has been set read-only"); ++ } ++} ++ ++/* ++ * exfat_msg() - print preformated EXFAT specific messages. ++ * All logs except what uses exfat_fs_error() should be written by exfat_msg() ++ */ ++void exfat_msg(struct super_block *sb, const char *level, const char *fmt, ...) ++{ ++ struct va_format vaf; ++ va_list args; ++ ++ va_start(args, fmt); ++ vaf.fmt = fmt; ++ vaf.va = &args; ++ /* level means KERN_ pacility level */ ++ printk("%sexFAT-fs (%s): %pV\n", level, sb->s_id, &vaf); ++ va_end(args); ++} ++ ++#define SECS_PER_MIN (60) ++#define TIMEZONE_SEC(x) ((x) * 15 * SECS_PER_MIN) ++ ++static void exfat_adjust_tz(struct timespec64 *ts, u8 tz_off) ++{ ++ if (tz_off <= 0x3F) ++ ts->tv_sec -= TIMEZONE_SEC(tz_off); ++ else /* 0x40 <= (tz_off & 0x7F) <=0x7F */ ++ ts->tv_sec += TIMEZONE_SEC(0x80 - tz_off); ++} ++ ++/* Convert a EXFAT time/date pair to a UNIX date (seconds since 1 1 70). */ ++void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, ++ u8 tz, __le16 time, __le16 date, u8 time_ms) ++{ ++ u16 t = le16_to_cpu(time); ++ u16 d = le16_to_cpu(date); ++ ++ ts->tv_sec = mktime64(1980 + (d >> 9), d >> 5 & 0x000F, d & 0x001F, ++ t >> 11, (t >> 5) & 0x003F, (t & 0x001F) << 1); ++ ++ ++ /* time_ms field represent 0 ~ 199(1990 ms) */ ++ if (time_ms) { ++ ts->tv_sec += time_ms / 100; ++ ts->tv_nsec = (time_ms % 100) * 10 * NSEC_PER_MSEC; ++ } ++ ++ if (tz & EXFAT_TZ_VALID) ++ /* 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; ++} ++ ++/* Convert linear UNIX date to a EXFAT time/date pair. */ ++void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, ++ u8 *tz, __le16 *time, __le16 *date, u8 *time_ms) ++{ ++ struct tm tm; ++ u16 t, d; ++ ++ time64_to_tm(ts->tv_sec, 0, &tm); ++ 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; ++ ++ *time = cpu_to_le16(t); ++ *date = cpu_to_le16(d); ++ ++ /* time_ms field represent 0 ~ 199(1990 ms) */ ++ if (time_ms) ++ *time_ms = (tm.tm_sec & 1) * 100 + ++ ts->tv_nsec / (10 * NSEC_PER_MSEC); ++ ++ /* ++ * Record 00h value for OffsetFromUtc field and 1 value for OffsetValid ++ * to indicate that local time and UTC are the same. ++ */ ++ *tz = EXFAT_TZ_VALID; ++} ++ ++unsigned short exfat_calc_chksum_2byte(void *data, int len, ++ unsigned short chksum, int type) ++{ ++ int i; ++ unsigned char *c = (unsigned char *)data; ++ ++ for (i = 0; i < len; i++, c++) { ++ if (((i == 2) || (i == 3)) && (type == CS_DIR_ENTRY)) ++ continue; ++ chksum = (((chksum & 1) << 15) | ((chksum & 0xFFFE) >> 1)) + ++ (unsigned short)*c; ++ } ++ return chksum; ++} ++ ++void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync) ++{ ++ set_bit(EXFAT_SB_DIRTY, &EXFAT_SB(sb)->s_state); ++ set_buffer_uptodate(bh); ++ mark_buffer_dirty(bh); ++ ++ if (sync) ++ sync_dirty_buffer(bh); ++} ++ ++void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, ++ unsigned int size, unsigned char flags) ++{ ++ ec->dir = dir; ++ ec->size = size; ++ ec->flags = flags; ++} ++ ++void exfat_chain_dup(struct exfat_chain *dup, struct exfat_chain *ec) ++{ ++ return exfat_chain_set(dup, ec->dir, ec->size, ec->flags); ++} +-- +2.31.1 + diff --git a/SOURCES/0010-exfat-add-nls-operations.patch b/SOURCES/0010-exfat-add-nls-operations.patch new file mode 100644 index 0000000..d08282b --- /dev/null +++ b/SOURCES/0010-exfat-add-nls-operations.patch @@ -0,0 +1,860 @@ +From 370e812b3ec190fa492c9fd5a80c38b086d105c0 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:41 +0900 +Subject: [Backport 370e812b3ec1] exfat: add nls operations +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the implementation of nls operations for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/nls.c | 831 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 831 insertions(+) + create mode 100644 src/nls.c + +diff --git a/src/nls.c b/src/nls.c +new file mode 100644 +index 0000000000000000000000000000000000000000..6d1c3ae130ff177cec9261510b6f7c4a961f5177 +--- /dev/null ++++ b/src/nls.c +@@ -0,0 +1,831 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "exfat_raw.h" ++#include "exfat_fs.h" ++ ++/* Upcase tabel macro */ ++#define EXFAT_NUM_UPCASE (2918) ++#define UTBL_COUNT (0x10000) ++ ++/* ++ * Upcase table in compressed format (7.2.5.1 Recommended Up-case Table ++ * in exfat specification, See: ++ * https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification). ++ */ ++static const unsigned short uni_def_upcase[EXFAT_NUM_UPCASE] = { ++ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, ++ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, ++ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, ++ 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, ++ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, ++ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, ++ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, ++ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, ++ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, ++ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, ++ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, ++ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, ++ 0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, ++ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, ++ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, ++ 0x0058, 0x0059, 0x005a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, ++ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, ++ 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, ++ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, ++ 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, ++ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, ++ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, ++ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, ++ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, ++ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, ++ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, ++ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, ++ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, ++ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, ++ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, ++ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00f7, ++ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x0178, ++ 0x0100, 0x0100, 0x0102, 0x0102, 0x0104, 0x0104, 0x0106, 0x0106, ++ 0x0108, 0x0108, 0x010a, 0x010a, 0x010c, 0x010c, 0x010e, 0x010e, ++ 0x0110, 0x0110, 0x0112, 0x0112, 0x0114, 0x0114, 0x0116, 0x0116, ++ 0x0118, 0x0118, 0x011a, 0x011a, 0x011c, 0x011c, 0x011e, 0x011e, ++ 0x0120, 0x0120, 0x0122, 0x0122, 0x0124, 0x0124, 0x0126, 0x0126, ++ 0x0128, 0x0128, 0x012a, 0x012a, 0x012c, 0x012c, 0x012e, 0x012e, ++ 0x0130, 0x0131, 0x0132, 0x0132, 0x0134, 0x0134, 0x0136, 0x0136, ++ 0x0138, 0x0139, 0x0139, 0x013b, 0x013b, 0x013d, 0x013d, 0x013f, ++ 0x013f, 0x0141, 0x0141, 0x0143, 0x0143, 0x0145, 0x0145, 0x0147, ++ 0x0147, 0x0149, 0x014a, 0x014a, 0x014c, 0x014c, 0x014e, 0x014e, ++ 0x0150, 0x0150, 0x0152, 0x0152, 0x0154, 0x0154, 0x0156, 0x0156, ++ 0x0158, 0x0158, 0x015a, 0x015a, 0x015c, 0x015c, 0x015e, 0x015e, ++ 0x0160, 0x0160, 0x0162, 0x0162, 0x0164, 0x0164, 0x0166, 0x0166, ++ 0x0168, 0x0168, 0x016a, 0x016a, 0x016c, 0x016c, 0x016e, 0x016e, ++ 0x0170, 0x0170, 0x0172, 0x0172, 0x0174, 0x0174, 0x0176, 0x0176, ++ 0x0178, 0x0179, 0x0179, 0x017b, 0x017b, 0x017d, 0x017d, 0x017f, ++ 0x0243, 0x0181, 0x0182, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, ++ 0x0187, 0x0189, 0x018a, 0x018b, 0x018b, 0x018d, 0x018e, 0x018f, ++ 0x0190, 0x0191, 0x0191, 0x0193, 0x0194, 0x01f6, 0x0196, 0x0197, ++ 0x0198, 0x0198, 0x023d, 0x019b, 0x019c, 0x019d, 0x0220, 0x019f, ++ 0x01a0, 0x01a0, 0x01a2, 0x01a2, 0x01a4, 0x01a4, 0x01a6, 0x01a7, ++ 0x01a7, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ac, 0x01ae, 0x01af, ++ 0x01af, 0x01b1, 0x01b2, 0x01b3, 0x01b3, 0x01b5, 0x01b5, 0x01b7, ++ 0x01b8, 0x01b8, 0x01ba, 0x01bb, 0x01bc, 0x01bc, 0x01be, 0x01f7, ++ 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x01c4, 0x01c5, 0x01c4, 0x01c7, ++ 0x01c8, 0x01c7, 0x01ca, 0x01cb, 0x01ca, 0x01cd, 0x01cd, 0x01cf, ++ 0x01cf, 0x01d1, 0x01d1, 0x01d3, 0x01d3, 0x01d5, 0x01d5, 0x01d7, ++ 0x01d7, 0x01d9, 0x01d9, 0x01db, 0x01db, 0x018e, 0x01de, 0x01de, ++ 0x01e0, 0x01e0, 0x01e2, 0x01e2, 0x01e4, 0x01e4, 0x01e6, 0x01e6, ++ 0x01e8, 0x01e8, 0x01ea, 0x01ea, 0x01ec, 0x01ec, 0x01ee, 0x01ee, ++ 0x01f0, 0x01f1, 0x01f2, 0x01f1, 0x01f4, 0x01f4, 0x01f6, 0x01f7, ++ 0x01f8, 0x01f8, 0x01fa, 0x01fa, 0x01fc, 0x01fc, 0x01fe, 0x01fe, ++ 0x0200, 0x0200, 0x0202, 0x0202, 0x0204, 0x0204, 0x0206, 0x0206, ++ 0x0208, 0x0208, 0x020a, 0x020a, 0x020c, 0x020c, 0x020e, 0x020e, ++ 0x0210, 0x0210, 0x0212, 0x0212, 0x0214, 0x0214, 0x0216, 0x0216, ++ 0x0218, 0x0218, 0x021a, 0x021a, 0x021c, 0x021c, 0x021e, 0x021e, ++ 0x0220, 0x0221, 0x0222, 0x0222, 0x0224, 0x0224, 0x0226, 0x0226, ++ 0x0228, 0x0228, 0x022a, 0x022a, 0x022c, 0x022c, 0x022e, 0x022e, ++ 0x0230, 0x0230, 0x0232, 0x0232, 0x0234, 0x0235, 0x0236, 0x0237, ++ 0x0238, 0x0239, 0x2c65, 0x023b, 0x023b, 0x023d, 0x2c66, 0x023f, ++ 0x0240, 0x0241, 0x0241, 0x0243, 0x0244, 0x0245, 0x0246, 0x0246, ++ 0x0248, 0x0248, 0x024a, 0x024a, 0x024c, 0x024c, 0x024e, 0x024e, ++ 0x0250, 0x0251, 0x0252, 0x0181, 0x0186, 0x0255, 0x0189, 0x018a, ++ 0x0258, 0x018f, 0x025a, 0x0190, 0x025c, 0x025d, 0x025e, 0x025f, ++ 0x0193, 0x0261, 0x0262, 0x0194, 0x0264, 0x0265, 0x0266, 0x0267, ++ 0x0197, 0x0196, 0x026a, 0x2c62, 0x026c, 0x026d, 0x026e, 0x019c, ++ 0x0270, 0x0271, 0x019d, 0x0273, 0x0274, 0x019f, 0x0276, 0x0277, ++ 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x2c64, 0x027e, 0x027f, ++ 0x01a6, 0x0281, 0x0282, 0x01a9, 0x0284, 0x0285, 0x0286, 0x0287, ++ 0x01ae, 0x0244, 0x01b1, 0x01b2, 0x0245, 0x028d, 0x028e, 0x028f, ++ 0x0290, 0x0291, 0x01b7, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, ++ 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f, ++ 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7, ++ 0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af, ++ 0x02b0, 0x02b1, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x02b6, 0x02b7, ++ 0x02b8, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf, ++ 0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7, ++ 0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf, ++ 0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7, ++ 0x02d8, 0x02d9, 0x02da, 0x02db, 0x02dc, 0x02dd, 0x02de, 0x02df, ++ 0x02e0, 0x02e1, 0x02e2, 0x02e3, 0x02e4, 0x02e5, 0x02e6, 0x02e7, ++ 0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef, ++ 0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7, ++ 0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff, ++ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, ++ 0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f, ++ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, ++ 0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f, ++ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, ++ 0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f, ++ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, ++ 0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f, ++ 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0345, 0x0346, 0x0347, ++ 0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f, ++ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, ++ 0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f, ++ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, ++ 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f, ++ 0x0370, 0x0371, 0x0372, 0x0373, 0x0374, 0x0375, 0x0376, 0x0377, ++ 0x0378, 0x0379, 0x037a, 0x03fd, 0x03fe, 0x03ff, 0x037e, 0x037f, ++ 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0386, 0x0387, ++ 0x0388, 0x0389, 0x038a, 0x038b, 0x038c, 0x038d, 0x038e, 0x038f, ++ 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, ++ 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, ++ 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, ++ 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x0386, 0x0388, 0x0389, 0x038a, ++ 0x03b0, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, ++ 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, ++ 0x03a0, 0x03a1, 0x03a3, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, ++ 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x038c, 0x038e, 0x038f, 0x03cf, ++ 0x03d0, 0x03d1, 0x03d2, 0x03d3, 0x03d4, 0x03d5, 0x03d6, 0x03d7, ++ 0x03d8, 0x03d8, 0x03da, 0x03da, 0x03dc, 0x03dc, 0x03de, 0x03de, ++ 0x03e0, 0x03e0, 0x03e2, 0x03e2, 0x03e4, 0x03e4, 0x03e6, 0x03e6, ++ 0x03e8, 0x03e8, 0x03ea, 0x03ea, 0x03ec, 0x03ec, 0x03ee, 0x03ee, ++ 0x03f0, 0x03f1, 0x03f9, 0x03f3, 0x03f4, 0x03f5, 0x03f6, 0x03f7, ++ 0x03f7, 0x03f9, 0x03fa, 0x03fa, 0x03fc, 0x03fd, 0x03fe, 0x03ff, ++ 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, ++ 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x040d, 0x040e, 0x040f, ++ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, ++ 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, ++ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, ++ 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, ++ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, ++ 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, ++ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, ++ 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, ++ 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, ++ 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x040d, 0x040e, 0x040f, ++ 0x0460, 0x0460, 0x0462, 0x0462, 0x0464, 0x0464, 0x0466, 0x0466, ++ 0x0468, 0x0468, 0x046a, 0x046a, 0x046c, 0x046c, 0x046e, 0x046e, ++ 0x0470, 0x0470, 0x0472, 0x0472, 0x0474, 0x0474, 0x0476, 0x0476, ++ 0x0478, 0x0478, 0x047a, 0x047a, 0x047c, 0x047c, 0x047e, 0x047e, ++ 0x0480, 0x0480, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, ++ 0x0488, 0x0489, 0x048a, 0x048a, 0x048c, 0x048c, 0x048e, 0x048e, ++ 0x0490, 0x0490, 0x0492, 0x0492, 0x0494, 0x0494, 0x0496, 0x0496, ++ 0x0498, 0x0498, 0x049a, 0x049a, 0x049c, 0x049c, 0x049e, 0x049e, ++ 0x04a0, 0x04a0, 0x04a2, 0x04a2, 0x04a4, 0x04a4, 0x04a6, 0x04a6, ++ 0x04a8, 0x04a8, 0x04aa, 0x04aa, 0x04ac, 0x04ac, 0x04ae, 0x04ae, ++ 0x04b0, 0x04b0, 0x04b2, 0x04b2, 0x04b4, 0x04b4, 0x04b6, 0x04b6, ++ 0x04b8, 0x04b8, 0x04ba, 0x04ba, 0x04bc, 0x04bc, 0x04be, 0x04be, ++ 0x04c0, 0x04c1, 0x04c1, 0x04c3, 0x04c3, 0x04c5, 0x04c5, 0x04c7, ++ 0x04c7, 0x04c9, 0x04c9, 0x04cb, 0x04cb, 0x04cd, 0x04cd, 0x04c0, ++ 0x04d0, 0x04d0, 0x04d2, 0x04d2, 0x04d4, 0x04d4, 0x04d6, 0x04d6, ++ 0x04d8, 0x04d8, 0x04da, 0x04da, 0x04dc, 0x04dc, 0x04de, 0x04de, ++ 0x04e0, 0x04e0, 0x04e2, 0x04e2, 0x04e4, 0x04e4, 0x04e6, 0x04e6, ++ 0x04e8, 0x04e8, 0x04ea, 0x04ea, 0x04ec, 0x04ec, 0x04ee, 0x04ee, ++ 0x04f0, 0x04f0, 0x04f2, 0x04f2, 0x04f4, 0x04f4, 0x04f6, 0x04f6, ++ 0x04f8, 0x04f8, 0x04fa, 0x04fa, 0x04fc, 0x04fc, 0x04fe, 0x04fe, ++ 0x0500, 0x0500, 0x0502, 0x0502, 0x0504, 0x0504, 0x0506, 0x0506, ++ 0x0508, 0x0508, 0x050a, 0x050a, 0x050c, 0x050c, 0x050e, 0x050e, ++ 0x0510, 0x0510, 0x0512, 0x0512, 0x0514, 0x0515, 0x0516, 0x0517, ++ 0x0518, 0x0519, 0x051a, 0x051b, 0x051c, 0x051d, 0x051e, 0x051f, ++ 0x0520, 0x0521, 0x0522, 0x0523, 0x0524, 0x0525, 0x0526, 0x0527, ++ 0x0528, 0x0529, 0x052a, 0x052b, 0x052c, 0x052d, 0x052e, 0x052f, ++ 0x0530, 0x0531, 0x0532, 0x0533, 0x0534, 0x0535, 0x0536, 0x0537, ++ 0x0538, 0x0539, 0x053a, 0x053b, 0x053c, 0x053d, 0x053e, 0x053f, ++ 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0546, 0x0547, ++ 0x0548, 0x0549, 0x054a, 0x054b, 0x054c, 0x054d, 0x054e, 0x054f, ++ 0x0550, 0x0551, 0x0552, 0x0553, 0x0554, 0x0555, 0x0556, 0x0557, ++ 0x0558, 0x0559, 0x055a, 0x055b, 0x055c, 0x055d, 0x055e, 0x055f, ++ 0x0560, 0x0531, 0x0532, 0x0533, 0x0534, 0x0535, 0x0536, 0x0537, ++ 0x0538, 0x0539, 0x053a, 0x053b, 0x053c, 0x053d, 0x053e, 0x053f, ++ 0x0540, 0x0541, 0x0542, 0x0543, 0x0544, 0x0545, 0x0546, 0x0547, ++ 0x0548, 0x0549, 0x054a, 0x054b, 0x054c, 0x054d, 0x054e, 0x054f, ++ 0x0550, 0x0551, 0x0552, 0x0553, 0x0554, 0x0555, 0x0556, 0xffff, ++ 0x17f6, 0x2c63, 0x1d7e, 0x1d7f, 0x1d80, 0x1d81, 0x1d82, 0x1d83, ++ 0x1d84, 0x1d85, 0x1d86, 0x1d87, 0x1d88, 0x1d89, 0x1d8a, 0x1d8b, ++ 0x1d8c, 0x1d8d, 0x1d8e, 0x1d8f, 0x1d90, 0x1d91, 0x1d92, 0x1d93, ++ 0x1d94, 0x1d95, 0x1d96, 0x1d97, 0x1d98, 0x1d99, 0x1d9a, 0x1d9b, ++ 0x1d9c, 0x1d9d, 0x1d9e, 0x1d9f, 0x1da0, 0x1da1, 0x1da2, 0x1da3, ++ 0x1da4, 0x1da5, 0x1da6, 0x1da7, 0x1da8, 0x1da9, 0x1daa, 0x1dab, ++ 0x1dac, 0x1dad, 0x1dae, 0x1daf, 0x1db0, 0x1db1, 0x1db2, 0x1db3, ++ 0x1db4, 0x1db5, 0x1db6, 0x1db7, 0x1db8, 0x1db9, 0x1dba, 0x1dbb, ++ 0x1dbc, 0x1dbd, 0x1dbe, 0x1dbf, 0x1dc0, 0x1dc1, 0x1dc2, 0x1dc3, ++ 0x1dc4, 0x1dc5, 0x1dc6, 0x1dc7, 0x1dc8, 0x1dc9, 0x1dca, 0x1dcb, ++ 0x1dcc, 0x1dcd, 0x1dce, 0x1dcf, 0x1dd0, 0x1dd1, 0x1dd2, 0x1dd3, ++ 0x1dd4, 0x1dd5, 0x1dd6, 0x1dd7, 0x1dd8, 0x1dd9, 0x1dda, 0x1ddb, ++ 0x1ddc, 0x1ddd, 0x1dde, 0x1ddf, 0x1de0, 0x1de1, 0x1de2, 0x1de3, ++ 0x1de4, 0x1de5, 0x1de6, 0x1de7, 0x1de8, 0x1de9, 0x1dea, 0x1deb, ++ 0x1dec, 0x1ded, 0x1dee, 0x1def, 0x1df0, 0x1df1, 0x1df2, 0x1df3, ++ 0x1df4, 0x1df5, 0x1df6, 0x1df7, 0x1df8, 0x1df9, 0x1dfa, 0x1dfb, ++ 0x1dfc, 0x1dfd, 0x1dfe, 0x1dff, 0x1e00, 0x1e00, 0x1e02, 0x1e02, ++ 0x1e04, 0x1e04, 0x1e06, 0x1e06, 0x1e08, 0x1e08, 0x1e0a, 0x1e0a, ++ 0x1e0c, 0x1e0c, 0x1e0e, 0x1e0e, 0x1e10, 0x1e10, 0x1e12, 0x1e12, ++ 0x1e14, 0x1e14, 0x1e16, 0x1e16, 0x1e18, 0x1e18, 0x1e1a, 0x1e1a, ++ 0x1e1c, 0x1e1c, 0x1e1e, 0x1e1e, 0x1e20, 0x1e20, 0x1e22, 0x1e22, ++ 0x1e24, 0x1e24, 0x1e26, 0x1e26, 0x1e28, 0x1e28, 0x1e2a, 0x1e2a, ++ 0x1e2c, 0x1e2c, 0x1e2e, 0x1e2e, 0x1e30, 0x1e30, 0x1e32, 0x1e32, ++ 0x1e34, 0x1e34, 0x1e36, 0x1e36, 0x1e38, 0x1e38, 0x1e3a, 0x1e3a, ++ 0x1e3c, 0x1e3c, 0x1e3e, 0x1e3e, 0x1e40, 0x1e40, 0x1e42, 0x1e42, ++ 0x1e44, 0x1e44, 0x1e46, 0x1e46, 0x1e48, 0x1e48, 0x1e4a, 0x1e4a, ++ 0x1e4c, 0x1e4c, 0x1e4e, 0x1e4e, 0x1e50, 0x1e50, 0x1e52, 0x1e52, ++ 0x1e54, 0x1e54, 0x1e56, 0x1e56, 0x1e58, 0x1e58, 0x1e5a, 0x1e5a, ++ 0x1e5c, 0x1e5c, 0x1e5e, 0x1e5e, 0x1e60, 0x1e60, 0x1e62, 0x1e62, ++ 0x1e64, 0x1e64, 0x1e66, 0x1e66, 0x1e68, 0x1e68, 0x1e6a, 0x1e6a, ++ 0x1e6c, 0x1e6c, 0x1e6e, 0x1e6e, 0x1e70, 0x1e70, 0x1e72, 0x1e72, ++ 0x1e74, 0x1e74, 0x1e76, 0x1e76, 0x1e78, 0x1e78, 0x1e7a, 0x1e7a, ++ 0x1e7c, 0x1e7c, 0x1e7e, 0x1e7e, 0x1e80, 0x1e80, 0x1e82, 0x1e82, ++ 0x1e84, 0x1e84, 0x1e86, 0x1e86, 0x1e88, 0x1e88, 0x1e8a, 0x1e8a, ++ 0x1e8c, 0x1e8c, 0x1e8e, 0x1e8e, 0x1e90, 0x1e90, 0x1e92, 0x1e92, ++ 0x1e94, 0x1e94, 0x1e96, 0x1e97, 0x1e98, 0x1e99, 0x1e9a, 0x1e9b, ++ 0x1e9c, 0x1e9d, 0x1e9e, 0x1e9f, 0x1ea0, 0x1ea0, 0x1ea2, 0x1ea2, ++ 0x1ea4, 0x1ea4, 0x1ea6, 0x1ea6, 0x1ea8, 0x1ea8, 0x1eaa, 0x1eaa, ++ 0x1eac, 0x1eac, 0x1eae, 0x1eae, 0x1eb0, 0x1eb0, 0x1eb2, 0x1eb2, ++ 0x1eb4, 0x1eb4, 0x1eb6, 0x1eb6, 0x1eb8, 0x1eb8, 0x1eba, 0x1eba, ++ 0x1ebc, 0x1ebc, 0x1ebe, 0x1ebe, 0x1ec0, 0x1ec0, 0x1ec2, 0x1ec2, ++ 0x1ec4, 0x1ec4, 0x1ec6, 0x1ec6, 0x1ec8, 0x1ec8, 0x1eca, 0x1eca, ++ 0x1ecc, 0x1ecc, 0x1ece, 0x1ece, 0x1ed0, 0x1ed0, 0x1ed2, 0x1ed2, ++ 0x1ed4, 0x1ed4, 0x1ed6, 0x1ed6, 0x1ed8, 0x1ed8, 0x1eda, 0x1eda, ++ 0x1edc, 0x1edc, 0x1ede, 0x1ede, 0x1ee0, 0x1ee0, 0x1ee2, 0x1ee2, ++ 0x1ee4, 0x1ee4, 0x1ee6, 0x1ee6, 0x1ee8, 0x1ee8, 0x1eea, 0x1eea, ++ 0x1eec, 0x1eec, 0x1eee, 0x1eee, 0x1ef0, 0x1ef0, 0x1ef2, 0x1ef2, ++ 0x1ef4, 0x1ef4, 0x1ef6, 0x1ef6, 0x1ef8, 0x1ef8, 0x1efa, 0x1efb, ++ 0x1efc, 0x1efd, 0x1efe, 0x1eff, 0x1f08, 0x1f09, 0x1f0a, 0x1f0b, ++ 0x1f0c, 0x1f0d, 0x1f0e, 0x1f0f, 0x1f08, 0x1f09, 0x1f0a, 0x1f0b, ++ 0x1f0c, 0x1f0d, 0x1f0e, 0x1f0f, 0x1f18, 0x1f19, 0x1f1a, 0x1f1b, ++ 0x1f1c, 0x1f1d, 0x1f16, 0x1f17, 0x1f18, 0x1f19, 0x1f1a, 0x1f1b, ++ 0x1f1c, 0x1f1d, 0x1f1e, 0x1f1f, 0x1f28, 0x1f29, 0x1f2a, 0x1f2b, ++ 0x1f2c, 0x1f2d, 0x1f2e, 0x1f2f, 0x1f28, 0x1f29, 0x1f2a, 0x1f2b, ++ 0x1f2c, 0x1f2d, 0x1f2e, 0x1f2f, 0x1f38, 0x1f39, 0x1f3a, 0x1f3b, ++ 0x1f3c, 0x1f3d, 0x1f3e, 0x1f3f, 0x1f38, 0x1f39, 0x1f3a, 0x1f3b, ++ 0x1f3c, 0x1f3d, 0x1f3e, 0x1f3f, 0x1f48, 0x1f49, 0x1f4a, 0x1f4b, ++ 0x1f4c, 0x1f4d, 0x1f46, 0x1f47, 0x1f48, 0x1f49, 0x1f4a, 0x1f4b, ++ 0x1f4c, 0x1f4d, 0x1f4e, 0x1f4f, 0x1f50, 0x1f59, 0x1f52, 0x1f5b, ++ 0x1f54, 0x1f5d, 0x1f56, 0x1f5f, 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, ++ 0x1f5c, 0x1f5d, 0x1f5e, 0x1f5f, 0x1f68, 0x1f69, 0x1f6a, 0x1f6b, ++ 0x1f6c, 0x1f6d, 0x1f6e, 0x1f6f, 0x1f68, 0x1f69, 0x1f6a, 0x1f6b, ++ 0x1f6c, 0x1f6d, 0x1f6e, 0x1f6f, 0x1fba, 0x1fbb, 0x1fc8, 0x1fc9, ++ 0x1fca, 0x1fcb, 0x1fda, 0x1fdb, 0x1ff8, 0x1ff9, 0x1fea, 0x1feb, ++ 0x1ffa, 0x1ffb, 0x1f7e, 0x1f7f, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b, ++ 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b, ++ 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b, ++ 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b, ++ 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, 0x1faa, 0x1fab, ++ 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fa8, 0x1fa9, 0x1faa, 0x1fab, ++ 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fb8, 0x1fb9, 0x1fb2, 0x1fbc, ++ 0x1fb4, 0x1fb5, 0x1fb6, 0x1fb7, 0x1fb8, 0x1fb9, 0x1fba, 0x1fbb, ++ 0x1fbc, 0x1fbd, 0x1fbe, 0x1fbf, 0x1fc0, 0x1fc1, 0x1fc2, 0x1fc3, ++ 0x1fc4, 0x1fc5, 0x1fc6, 0x1fc7, 0x1fc8, 0x1fc9, 0x1fca, 0x1fcb, ++ 0x1fc3, 0x1fcd, 0x1fce, 0x1fcf, 0x1fd8, 0x1fd9, 0x1fd2, 0x1fd3, ++ 0x1fd4, 0x1fd5, 0x1fd6, 0x1fd7, 0x1fd8, 0x1fd9, 0x1fda, 0x1fdb, ++ 0x1fdc, 0x1fdd, 0x1fde, 0x1fdf, 0x1fe8, 0x1fe9, 0x1fe2, 0x1fe3, ++ 0x1fe4, 0x1fec, 0x1fe6, 0x1fe7, 0x1fe8, 0x1fe9, 0x1fea, 0x1feb, ++ 0x1fec, 0x1fed, 0x1fee, 0x1fef, 0x1ff0, 0x1ff1, 0x1ff2, 0x1ff3, ++ 0x1ff4, 0x1ff5, 0x1ff6, 0x1ff7, 0x1ff8, 0x1ff9, 0x1ffa, 0x1ffb, ++ 0x1ff3, 0x1ffd, 0x1ffe, 0x1fff, 0x2000, 0x2001, 0x2002, 0x2003, ++ 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x200b, ++ 0x200c, 0x200d, 0x200e, 0x200f, 0x2010, 0x2011, 0x2012, 0x2013, ++ 0x2014, 0x2015, 0x2016, 0x2017, 0x2018, 0x2019, 0x201a, 0x201b, ++ 0x201c, 0x201d, 0x201e, 0x201f, 0x2020, 0x2021, 0x2022, 0x2023, ++ 0x2024, 0x2025, 0x2026, 0x2027, 0x2028, 0x2029, 0x202a, 0x202b, ++ 0x202c, 0x202d, 0x202e, 0x202f, 0x2030, 0x2031, 0x2032, 0x2033, ++ 0x2034, 0x2035, 0x2036, 0x2037, 0x2038, 0x2039, 0x203a, 0x203b, ++ 0x203c, 0x203d, 0x203e, 0x203f, 0x2040, 0x2041, 0x2042, 0x2043, ++ 0x2044, 0x2045, 0x2046, 0x2047, 0x2048, 0x2049, 0x204a, 0x204b, ++ 0x204c, 0x204d, 0x204e, 0x204f, 0x2050, 0x2051, 0x2052, 0x2053, ++ 0x2054, 0x2055, 0x2056, 0x2057, 0x2058, 0x2059, 0x205a, 0x205b, ++ 0x205c, 0x205d, 0x205e, 0x205f, 0x2060, 0x2061, 0x2062, 0x2063, ++ 0x2064, 0x2065, 0x2066, 0x2067, 0x2068, 0x2069, 0x206a, 0x206b, ++ 0x206c, 0x206d, 0x206e, 0x206f, 0x2070, 0x2071, 0x2072, 0x2073, ++ 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, 0x207a, 0x207b, ++ 0x207c, 0x207d, 0x207e, 0x207f, 0x2080, 0x2081, 0x2082, 0x2083, ++ 0x2084, 0x2085, 0x2086, 0x2087, 0x2088, 0x2089, 0x208a, 0x208b, ++ 0x208c, 0x208d, 0x208e, 0x208f, 0x2090, 0x2091, 0x2092, 0x2093, ++ 0x2094, 0x2095, 0x2096, 0x2097, 0x2098, 0x2099, 0x209a, 0x209b, ++ 0x209c, 0x209d, 0x209e, 0x209f, 0x20a0, 0x20a1, 0x20a2, 0x20a3, ++ 0x20a4, 0x20a5, 0x20a6, 0x20a7, 0x20a8, 0x20a9, 0x20aa, 0x20ab, ++ 0x20ac, 0x20ad, 0x20ae, 0x20af, 0x20b0, 0x20b1, 0x20b2, 0x20b3, ++ 0x20b4, 0x20b5, 0x20b6, 0x20b7, 0x20b8, 0x20b9, 0x20ba, 0x20bb, ++ 0x20bc, 0x20bd, 0x20be, 0x20bf, 0x20c0, 0x20c1, 0x20c2, 0x20c3, ++ 0x20c4, 0x20c5, 0x20c6, 0x20c7, 0x20c8, 0x20c9, 0x20ca, 0x20cb, ++ 0x20cc, 0x20cd, 0x20ce, 0x20cf, 0x20d0, 0x20d1, 0x20d2, 0x20d3, ++ 0x20d4, 0x20d5, 0x20d6, 0x20d7, 0x20d8, 0x20d9, 0x20da, 0x20db, ++ 0x20dc, 0x20dd, 0x20de, 0x20df, 0x20e0, 0x20e1, 0x20e2, 0x20e3, ++ 0x20e4, 0x20e5, 0x20e6, 0x20e7, 0x20e8, 0x20e9, 0x20ea, 0x20eb, ++ 0x20ec, 0x20ed, 0x20ee, 0x20ef, 0x20f0, 0x20f1, 0x20f2, 0x20f3, ++ 0x20f4, 0x20f5, 0x20f6, 0x20f7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, ++ 0x20fc, 0x20fd, 0x20fe, 0x20ff, 0x2100, 0x2101, 0x2102, 0x2103, ++ 0x2104, 0x2105, 0x2106, 0x2107, 0x2108, 0x2109, 0x210a, 0x210b, ++ 0x210c, 0x210d, 0x210e, 0x210f, 0x2110, 0x2111, 0x2112, 0x2113, ++ 0x2114, 0x2115, 0x2116, 0x2117, 0x2118, 0x2119, 0x211a, 0x211b, ++ 0x211c, 0x211d, 0x211e, 0x211f, 0x2120, 0x2121, 0x2122, 0x2123, ++ 0x2124, 0x2125, 0x2126, 0x2127, 0x2128, 0x2129, 0x212a, 0x212b, ++ 0x212c, 0x212d, 0x212e, 0x212f, 0x2130, 0x2131, 0x2132, 0x2133, ++ 0x2134, 0x2135, 0x2136, 0x2137, 0x2138, 0x2139, 0x213a, 0x213b, ++ 0x213c, 0x213d, 0x213e, 0x213f, 0x2140, 0x2141, 0x2142, 0x2143, ++ 0x2144, 0x2145, 0x2146, 0x2147, 0x2148, 0x2149, 0x214a, 0x214b, ++ 0x214c, 0x214d, 0x2132, 0x214f, 0x2150, 0x2151, 0x2152, 0x2153, ++ 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215a, 0x215b, ++ 0x215c, 0x215d, 0x215e, 0x215f, 0x2160, 0x2161, 0x2162, 0x2163, ++ 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216a, 0x216b, ++ 0x216c, 0x216d, 0x216e, 0x216f, 0x2160, 0x2161, 0x2162, 0x2163, ++ 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216a, 0x216b, ++ 0x216c, 0x216d, 0x216e, 0x216f, 0x2180, 0x2181, 0x2182, 0x2183, ++ 0x2183, 0xffff, 0x034b, 0x24b6, 0x24b7, 0x24b8, 0x24b9, 0x24ba, ++ 0x24bb, 0x24bc, 0x24bd, 0x24be, 0x24bf, 0x24c0, 0x24c1, 0x24c2, ++ 0x24c3, 0x24c4, 0x24c5, 0x24c6, 0x24c7, 0x24c8, 0x24c9, 0x24ca, ++ 0x24cb, 0x24cc, 0x24cd, 0x24ce, 0x24cf, 0xffff, 0x0746, 0x2c00, ++ 0x2c01, 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x2c08, ++ 0x2c09, 0x2c0a, 0x2c0b, 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x2c10, ++ 0x2c11, 0x2c12, 0x2c13, 0x2c14, 0x2c15, 0x2c16, 0x2c17, 0x2c18, ++ 0x2c19, 0x2c1a, 0x2c1b, 0x2c1c, 0x2c1d, 0x2c1e, 0x2c1f, 0x2c20, ++ 0x2c21, 0x2c22, 0x2c23, 0x2c24, 0x2c25, 0x2c26, 0x2c27, 0x2c28, ++ 0x2c29, 0x2c2a, 0x2c2b, 0x2c2c, 0x2c2d, 0x2c2e, 0x2c5f, 0x2c60, ++ 0x2c60, 0x2c62, 0x2c63, 0x2c64, 0x2c65, 0x2c66, 0x2c67, 0x2c67, ++ 0x2c69, 0x2c69, 0x2c6b, 0x2c6b, 0x2c6d, 0x2c6e, 0x2c6f, 0x2c70, ++ 0x2c71, 0x2c72, 0x2c73, 0x2c74, 0x2c75, 0x2c75, 0x2c77, 0x2c78, ++ 0x2c79, 0x2c7a, 0x2c7b, 0x2c7c, 0x2c7d, 0x2c7e, 0x2c7f, 0x2c80, ++ 0x2c80, 0x2c82, 0x2c82, 0x2c84, 0x2c84, 0x2c86, 0x2c86, 0x2c88, ++ 0x2c88, 0x2c8a, 0x2c8a, 0x2c8c, 0x2c8c, 0x2c8e, 0x2c8e, 0x2c90, ++ 0x2c90, 0x2c92, 0x2c92, 0x2c94, 0x2c94, 0x2c96, 0x2c96, 0x2c98, ++ 0x2c98, 0x2c9a, 0x2c9a, 0x2c9c, 0x2c9c, 0x2c9e, 0x2c9e, 0x2ca0, ++ 0x2ca0, 0x2ca2, 0x2ca2, 0x2ca4, 0x2ca4, 0x2ca6, 0x2ca6, 0x2ca8, ++ 0x2ca8, 0x2caa, 0x2caa, 0x2cac, 0x2cac, 0x2cae, 0x2cae, 0x2cb0, ++ 0x2cb0, 0x2cb2, 0x2cb2, 0x2cb4, 0x2cb4, 0x2cb6, 0x2cb6, 0x2cb8, ++ 0x2cb8, 0x2cba, 0x2cba, 0x2cbc, 0x2cbc, 0x2cbe, 0x2cbe, 0x2cc0, ++ 0x2cc0, 0x2cc2, 0x2cc2, 0x2cc4, 0x2cc4, 0x2cc6, 0x2cc6, 0x2cc8, ++ 0x2cc8, 0x2cca, 0x2cca, 0x2ccc, 0x2ccc, 0x2cce, 0x2cce, 0x2cd0, ++ 0x2cd0, 0x2cd2, 0x2cd2, 0x2cd4, 0x2cd4, 0x2cd6, 0x2cd6, 0x2cd8, ++ 0x2cd8, 0x2cda, 0x2cda, 0x2cdc, 0x2cdc, 0x2cde, 0x2cde, 0x2ce0, ++ 0x2ce0, 0x2ce2, 0x2ce2, 0x2ce4, 0x2ce5, 0x2ce6, 0x2ce7, 0x2ce8, ++ 0x2ce9, 0x2cea, 0x2ceb, 0x2cec, 0x2ced, 0x2cee, 0x2cef, 0x2cf0, ++ 0x2cf1, 0x2cf2, 0x2cf3, 0x2cf4, 0x2cf5, 0x2cf6, 0x2cf7, 0x2cf8, ++ 0x2cf9, 0x2cfa, 0x2cfb, 0x2cfc, 0x2cfd, 0x2cfe, 0x2cff, 0x10a0, ++ 0x10a1, 0x10a2, 0x10a3, 0x10a4, 0x10a5, 0x10a6, 0x10a7, 0x10a8, ++ 0x10a9, 0x10aa, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0, ++ 0x10b1, 0x10b2, 0x10b3, 0x10b4, 0x10b5, 0x10b6, 0x10b7, 0x10b8, ++ 0x10b9, 0x10ba, 0x10bb, 0x10bc, 0x10bd, 0x10be, 0x10bf, 0x10c0, ++ 0x10c1, 0x10c2, 0x10c3, 0x10c4, 0x10c5, 0xffff, 0xd21b, 0xff21, ++ 0xff22, 0xff23, 0xff24, 0xff25, 0xff26, 0xff27, 0xff28, 0xff29, ++ 0xff2a, 0xff2b, 0xff2c, 0xff2d, 0xff2e, 0xff2f, 0xff30, 0xff31, ++ 0xff32, 0xff33, 0xff34, 0xff35, 0xff36, 0xff37, 0xff38, 0xff39, ++ 0xff3a, 0xff5b, 0xff5c, 0xff5d, 0xff5e, 0xff5f, 0xff60, 0xff61, ++ 0xff62, 0xff63, 0xff64, 0xff65, 0xff66, 0xff67, 0xff68, 0xff69, ++ 0xff6a, 0xff6b, 0xff6c, 0xff6d, 0xff6e, 0xff6f, 0xff70, 0xff71, ++ 0xff72, 0xff73, 0xff74, 0xff75, 0xff76, 0xff77, 0xff78, 0xff79, ++ 0xff7a, 0xff7b, 0xff7c, 0xff7d, 0xff7e, 0xff7f, 0xff80, 0xff81, ++ 0xff82, 0xff83, 0xff84, 0xff85, 0xff86, 0xff87, 0xff88, 0xff89, ++ 0xff8a, 0xff8b, 0xff8c, 0xff8d, 0xff8e, 0xff8f, 0xff90, 0xff91, ++ 0xff92, 0xff93, 0xff94, 0xff95, 0xff96, 0xff97, 0xff98, 0xff99, ++ 0xff9a, 0xff9b, 0xff9c, 0xff9d, 0xff9e, 0xff9f, 0xffa0, 0xffa1, ++ 0xffa2, 0xffa3, 0xffa4, 0xffa5, 0xffa6, 0xffa7, 0xffa8, 0xffa9, ++ 0xffaa, 0xffab, 0xffac, 0xffad, 0xffae, 0xffaf, 0xffb0, 0xffb1, ++ 0xffb2, 0xffb3, 0xffb4, 0xffb5, 0xffb6, 0xffb7, 0xffb8, 0xffb9, ++ 0xffba, 0xffbb, 0xffbc, 0xffbd, 0xffbe, 0xffbf, 0xffc0, 0xffc1, ++ 0xffc2, 0xffc3, 0xffc4, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, ++ 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf, 0xffd0, 0xffd1, ++ 0xffd2, 0xffd3, 0xffd4, 0xffd5, 0xffd6, 0xffd7, 0xffd8, 0xffd9, ++ 0xffda, 0xffdb, 0xffdc, 0xffdd, 0xffde, 0xffdf, 0xffe0, 0xffe1, ++ 0xffe2, 0xffe3, 0xffe4, 0xffe5, 0xffe6, 0xffe7, 0xffe8, 0xffe9, ++ 0xffea, 0xffeb, 0xffec, 0xffed, 0xffee, 0xffef, 0xfff0, 0xfff1, ++ 0xfff2, 0xfff3, 0xfff4, 0xfff5, 0xfff6, 0xfff7, 0xfff8, 0xfff9, ++ 0xfffa, 0xfffb, 0xfffc, 0xfffd, 0xfffe, 0xffff, ++}; ++ ++/* ++ * Allow full-width illegal characters : ++ * "MS windows 7" supports full-width-invalid-name-characters. ++ * So we should check half-width-invalid-name-characters(ASCII) only ++ * for compatibility. ++ * ++ * " * / : < > ? \ | ++ */ ++static unsigned short bad_uni_chars[] = { ++ 0x0022, 0x002A, 0x002F, 0x003A, ++ 0x003C, 0x003E, 0x003F, 0x005C, 0x007C, ++ 0 ++}; ++ ++static int exfat_convert_char_to_ucs2(struct nls_table *nls, ++ const unsigned char *ch, int ch_len, unsigned short *ucs2, ++ int *lossy) ++{ ++ int len; ++ ++ *ucs2 = 0x0; ++ ++ if (ch[0] < 0x80) { ++ *ucs2 = ch[0]; ++ return 1; ++ } ++ ++ len = nls->char2uni(ch, ch_len, ucs2); ++ if (len < 0) { ++ /* conversion failed */ ++ if (lossy != NULL) ++ *lossy |= NLS_NAME_LOSSY; ++ *ucs2 = '_'; ++ return 1; ++ } ++ return len; ++} ++ ++static int exfat_convert_ucs2_to_char(struct nls_table *nls, ++ unsigned short ucs2, unsigned char *ch, int *lossy) ++{ ++ int len; ++ ++ ch[0] = 0x0; ++ ++ if (ucs2 < 0x0080) { ++ ch[0] = ucs2; ++ return 1; ++ } ++ ++ len = nls->uni2char(ucs2, ch, MAX_CHARSET_SIZE); ++ if (len < 0) { ++ /* conversion failed */ ++ if (lossy != NULL) ++ *lossy |= NLS_NAME_LOSSY; ++ ch[0] = '_'; ++ return 1; ++ } ++ return len; ++} ++ ++unsigned short exfat_toupper(struct super_block *sb, unsigned short a) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ return sbi->vol_utbl[a] ? sbi->vol_utbl[a] : a; ++} ++ ++static unsigned short *exfat_wstrchr(unsigned short *str, unsigned short wchar) ++{ ++ while (*str) { ++ if (*(str++) == wchar) ++ return str; ++ } ++ return NULL; ++} ++ ++int exfat_uniname_ncmp(struct super_block *sb, unsigned short *a, ++ unsigned short *b, unsigned int len) ++{ ++ int i; ++ ++ for (i = 0; i < len; i++, a++, b++) ++ if (exfat_toupper(sb, *a) != exfat_toupper(sb, *b)) ++ return 1; ++ return 0; ++} ++ ++static int exfat_utf16_to_utf8(struct super_block *sb, ++ struct exfat_uni_name *p_uniname, unsigned char *p_cstring, ++ int buflen) ++{ ++ int len; ++ const unsigned short *uniname = p_uniname->name; ++ ++ /* always len >= 0 */ ++ len = utf16s_to_utf8s(uniname, MAX_NAME_LENGTH, UTF16_HOST_ENDIAN, ++ p_cstring, buflen); ++ p_cstring[len] = '\0'; ++ return len; ++} ++ ++static int exfat_utf8_to_utf16(struct super_block *sb, ++ const unsigned char *p_cstring, const int len, ++ struct exfat_uni_name *p_uniname, int *p_lossy) ++{ ++ int i, unilen, lossy = NLS_NAME_NO_LOSSY; ++ unsigned short upname[MAX_NAME_LENGTH + 1]; ++ unsigned short *uniname = p_uniname->name; ++ ++ WARN_ON(!len); ++ ++ unilen = utf8s_to_utf16s(p_cstring, len, UTF16_HOST_ENDIAN, ++ (wchar_t *)uniname, MAX_NAME_LENGTH + 2); ++ if (unilen < 0) { ++ exfat_msg(sb, KERN_ERR, ++ "failed to %s (err : %d) nls len : %d", ++ __func__, unilen, len); ++ return unilen; ++ } ++ ++ if (unilen > MAX_NAME_LENGTH) { ++ exfat_msg(sb, KERN_ERR, ++ "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", ++ __func__, len, unilen, MAX_NAME_LENGTH); ++ return -ENAMETOOLONG; ++ } ++ ++ p_uniname->name_len = unilen & 0xFF; ++ ++ for (i = 0; i < unilen; i++) { ++ if (*uniname < 0x0020 || ++ exfat_wstrchr(bad_uni_chars, *uniname)) ++ lossy |= NLS_NAME_LOSSY; ++ ++ upname[i] = exfat_toupper(sb, *uniname); ++ uniname++; ++ } ++ ++ *uniname = '\0'; ++ p_uniname->name_len = unilen; ++ p_uniname->name_hash = exfat_calc_chksum_2byte(upname, unilen << 1, 0, ++ CS_DEFAULT); ++ ++ if (p_lossy) ++ *p_lossy = lossy; ++ return unilen; ++} ++ ++#define PLANE_SIZE 0x00010000 ++#define SURROGATE_MASK 0xfffff800 ++#define SURROGATE_PAIR 0x0000d800 ++#define SURROGATE_LOW 0x00000400 ++#define SURROGATE_BITS 0x000003ff ++ ++unsigned short exfat_high_surrogate(unicode_t u) ++{ ++ return ((u - PLANE_SIZE) >> 10) + SURROGATE_PAIR; ++} ++ ++unsigned short exfat_low_surrogate(unicode_t u) ++{ ++ return ((u - PLANE_SIZE) & SURROGATE_BITS) | SURROGATE_PAIR | ++ SURROGATE_LOW; ++} ++ ++static int __exfat_utf16_to_nls(struct super_block *sb, ++ struct exfat_uni_name *p_uniname, unsigned char *p_cstring, ++ int buflen) ++{ ++ int i, j, len, out_len = 0; ++ unsigned char buf[MAX_CHARSET_SIZE]; ++ const unsigned short *uniname = p_uniname->name; ++ struct nls_table *nls = EXFAT_SB(sb)->nls_io; ++ ++ i = 0; ++ while (i < MAX_NAME_LENGTH && out_len < (buflen - 1)) { ++ if (*uniname == '\0') ++ break; ++ if ((*uniname & SURROGATE_MASK) != SURROGATE_PAIR) { ++ len = exfat_convert_ucs2_to_char(nls, *uniname, buf, ++ NULL); ++ } else { ++ /* Process UTF-16 surrogate pair as one character */ ++ if (!(*uniname & SURROGATE_LOW) && ++ i+1 < MAX_NAME_LENGTH && ++ (*(uniname+1) & SURROGATE_MASK) == SURROGATE_PAIR && ++ (*(uniname+1) & SURROGATE_LOW)) { ++ uniname++; ++ i++; ++ } ++ ++ /* ++ * UTF-16 surrogate pair encodes code points above ++ * U+FFFF. Code points above U+FFFF are not supported ++ * by kernel NLS framework therefore use replacement ++ * character ++ */ ++ len = 1; ++ buf[0] = '_'; ++ } ++ ++ if (out_len + len >= buflen) ++ len = buflen - 1 - out_len; ++ out_len += len; ++ ++ if (len > 1) { ++ for (j = 0; j < len; j++) ++ *p_cstring++ = buf[j]; ++ } else { /* len == 1 */ ++ *p_cstring++ = *buf; ++ } ++ ++ uniname++; ++ i++; ++ } ++ ++ *p_cstring = '\0'; ++ return out_len; ++} ++ ++static int exfat_nls_to_ucs2(struct super_block *sb, ++ const unsigned char *p_cstring, const int len, ++ struct exfat_uni_name *p_uniname, int *p_lossy) ++{ ++ int i = 0, unilen = 0, lossy = NLS_NAME_NO_LOSSY; ++ unsigned short upname[MAX_NAME_LENGTH + 1]; ++ unsigned short *uniname = p_uniname->name; ++ struct nls_table *nls = EXFAT_SB(sb)->nls_io; ++ ++ WARN_ON(!len); ++ ++ while (unilen < MAX_NAME_LENGTH && i < len) { ++ i += exfat_convert_char_to_ucs2(nls, p_cstring + i, len - i, ++ uniname, &lossy); ++ ++ if (*uniname < 0x0020 || ++ exfat_wstrchr(bad_uni_chars, *uniname)) ++ lossy |= NLS_NAME_LOSSY; ++ ++ upname[unilen] = exfat_toupper(sb, *uniname); ++ uniname++; ++ unilen++; ++ } ++ ++ if (p_cstring[i] != '\0') ++ lossy |= NLS_NAME_OVERLEN; ++ ++ *uniname = '\0'; ++ p_uniname->name_len = unilen; ++ p_uniname->name_hash = exfat_calc_chksum_2byte(upname, unilen << 1, 0, ++ CS_DEFAULT); ++ ++ if (p_lossy) ++ *p_lossy = lossy; ++ return unilen; ++} ++ ++int exfat_utf16_to_nls(struct super_block *sb, struct exfat_uni_name *uniname, ++ unsigned char *p_cstring, int buflen) ++{ ++ if (EXFAT_SB(sb)->options.utf8) ++ return exfat_utf16_to_utf8(sb, uniname, p_cstring, ++ buflen); ++ return __exfat_utf16_to_nls(sb, uniname, p_cstring, buflen); ++} ++ ++int exfat_nls_to_utf16(struct super_block *sb, const unsigned char *p_cstring, ++ const int len, struct exfat_uni_name *uniname, int *p_lossy) ++{ ++ if (EXFAT_SB(sb)->options.utf8) ++ return exfat_utf8_to_utf16(sb, p_cstring, len, ++ uniname, p_lossy); ++ return exfat_nls_to_ucs2(sb, p_cstring, len, uniname, p_lossy); ++} ++ ++static int exfat_load_upcase_table(struct super_block *sb, ++ sector_t sector, unsigned long long num_sectors, ++ unsigned int utbl_checksum) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned int sect_size = sb->s_blocksize; ++ unsigned int i, index = 0, checksum = 0; ++ int ret; ++ unsigned char skip = false; ++ unsigned short *upcase_table; ++ ++ upcase_table = kcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); ++ if (!upcase_table) ++ return -ENOMEM; ++ ++ sbi->vol_utbl = upcase_table; ++ num_sectors += sector; ++ ++ while (sector < num_sectors) { ++ struct buffer_head *bh; ++ ++ bh = sb_bread(sb, sector); ++ if (!bh) { ++ exfat_msg(sb, KERN_ERR, ++ "failed to read sector(0x%llx)\n", ++ (unsigned long long)sector); ++ ret = -EIO; ++ goto free_table; ++ } ++ sector++; ++ for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) { ++ unsigned short uni = get_unaligned_le16(bh->b_data + i); ++ ++ checksum = ((checksum & 1) ? 0x80000000 : 0) + ++ (checksum >> 1) + ++ *(((unsigned char *)bh->b_data) + i); ++ checksum = ((checksum & 1) ? 0x80000000 : 0) + ++ (checksum >> 1) + ++ *(((unsigned char *)bh->b_data) + (i + 1)); ++ ++ if (skip) { ++ index += uni; ++ skip = false; ++ } else if (uni == index) { ++ index++; ++ } else if (uni == 0xFFFF) { ++ skip = true; ++ } else { /* uni != index , uni != 0xFFFF */ ++ upcase_table[index] = uni; ++ index++; ++ } ++ } ++ brelse(bh); ++ } ++ ++ if (index >= 0xFFFF && utbl_checksum == checksum) ++ return 0; ++ ++ exfat_msg(sb, KERN_ERR, ++ "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)\n", ++ index, checksum, utbl_checksum); ++ ret = -EINVAL; ++free_table: ++ exfat_free_upcase_table(sbi); ++ return ret; ++} ++ ++static int exfat_load_default_upcase_table(struct super_block *sb) ++{ ++ int i, ret = -EIO; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned char skip = false; ++ unsigned short uni = 0, *upcase_table; ++ unsigned int index = 0; ++ ++ upcase_table = kcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); ++ if (!upcase_table) ++ return -ENOMEM; ++ ++ sbi->vol_utbl = upcase_table; ++ ++ for (i = 0; index <= 0xFFFF && i < EXFAT_NUM_UPCASE; i++) { ++ uni = uni_def_upcase[i]; ++ if (skip) { ++ index += uni; ++ skip = false; ++ } else if (uni == index) { ++ index++; ++ } else if (uni == 0xFFFF) { ++ skip = true; ++ } else { ++ upcase_table[index] = uni; ++ index++; ++ } ++ } ++ ++ if (index >= 0xFFFF) ++ return 0; ++ ++ /* FATAL error: default upcase table has error */ ++ exfat_free_upcase_table(sbi); ++ return ret; ++} ++ ++int exfat_create_upcase_table(struct super_block *sb) ++{ ++ int i, ret; ++ unsigned int tbl_clu, type; ++ sector_t sector; ++ unsigned long long tbl_size, num_sectors; ++ unsigned char blksize_bits = sb->s_blocksize_bits; ++ struct exfat_chain clu; ++ struct exfat_dentry *ep; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct buffer_head *bh; ++ ++ clu.dir = sbi->root_dir; ++ clu.flags = ALLOC_FAT_CHAIN; ++ ++ while (clu.dir != EXFAT_EOF_CLUSTER) { ++ for (i = 0; i < sbi->dentries_per_clu; i++) { ++ ep = exfat_get_dentry(sb, &clu, i, &bh, NULL); ++ if (!ep) ++ return -EIO; ++ ++ type = exfat_get_entry_type(ep); ++ if (type == TYPE_UNUSED) { ++ brelse(bh); ++ break; ++ } ++ ++ if (type != TYPE_UPCASE) { ++ brelse(bh); ++ continue; ++ } ++ ++ tbl_clu = le32_to_cpu(ep->dentry.upcase.start_clu); ++ tbl_size = le64_to_cpu(ep->dentry.upcase.size); ++ ++ sector = exfat_cluster_to_sector(sbi, tbl_clu); ++ num_sectors = ((tbl_size - 1) >> blksize_bits) + 1; ++ ret = exfat_load_upcase_table(sb, sector, num_sectors, ++ le32_to_cpu(ep->dentry.upcase.checksum)); ++ ++ brelse(bh); ++ if (ret && ret != -EIO) ++ goto load_default; ++ ++ /* load successfully */ ++ return ret; ++ } ++ ++ if (exfat_get_next_cluster(sb, &(clu.dir))) ++ return -EIO; ++ } ++ ++load_default: ++ /* load default upcase table */ ++ return exfat_load_default_upcase_table(sb); ++} ++ ++void exfat_free_upcase_table(struct exfat_sb_info *sbi) ++{ ++ kfree(sbi->vol_utbl); ++} +-- +2.31.1 + diff --git a/SOURCES/0011-exfat-add-Kconfig-and-Makefile.patch b/SOURCES/0011-exfat-add-Kconfig-and-Makefile.patch new file mode 100644 index 0000000..ef01333 --- /dev/null +++ b/SOURCES/0011-exfat-add-Kconfig-and-Makefile.patch @@ -0,0 +1,37 @@ +From b9d1e2e6265f5dc25e9f5dbfbde3e53d8a4958ac Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 2 Mar 2020 15:21:42 +0900 +Subject: [Backport b9d1e2e6265f] exfat: add Kconfig and Makefile +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds the Kconfig and Makefile for exfat. + +Signed-off-by: Namjae Jeon +Signed-off-by: Sungjong Seo +Reviewed-by: Pali Rohár +Reviewed-by: Christoph Hellwig +Signed-off-by: Al Viro +--- + src/Makefile | 8 ++++++++ + 1 file changed, 8 insertions(+) + create mode 100644 src/Makefile + +diff --git a/src/Makefile b/src/Makefile +new file mode 100644 +index 0000000000000000000000000000000000000000..ed51926a497175b9f1f723b2d498771180c26351 +--- /dev/null ++++ b/src/Makefile +@@ -0,0 +1,8 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the linux exFAT filesystem support. ++# ++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 +-- +2.31.1 + diff --git a/SOURCES/0012-exfat-update-file-system-parameter-handling.patch b/SOURCES/0012-exfat-update-file-system-parameter-handling.patch new file mode 100644 index 0000000..b009297 --- /dev/null +++ b/SOURCES/0012-exfat-update-file-system-parameter-handling.patch @@ -0,0 +1,84 @@ +From 9acd0d53800c55c6e2186e29b6433daf24617451 Mon Sep 17 00:00:00 2001 +From: Valdis Kletnieks +Date: Mon, 2 Mar 2020 15:21:45 +0900 +Subject: [Backport 9acd0d53800c] exfat: update file system parameter handling + +Al Viro recently reworked the way file system parameters are handled +Update super.c to work with it in linux-next 20200203. + +Signed-off-by: Valdis Kletnieks +Reviewed-by: Christoph Hellwig +Acked-by: Namjae Jeon +Signed-off-by: Al Viro +--- + src/super.c | 28 +++++++++++----------------- + 1 file changed, 11 insertions(+), 17 deletions(-) + +diff --git a/src/super.c b/src/super.c +index f06e0b53e39325937be7512f01c031c93917d9a8..16ed202ef5279abc40b23d03cd346b4baf4c23d3 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -214,7 +214,14 @@ enum { + Opt_time_offset, + }; + +-static const struct fs_parameter_spec exfat_param_specs[] = { ++static const struct constant_table exfat_param_enums[] = { ++ { "continue", EXFAT_ERRORS_CONT }, ++ { "panic", EXFAT_ERRORS_PANIC }, ++ { "remount-ro", EXFAT_ERRORS_RO }, ++ {} ++}; ++ ++static const struct fs_parameter_spec exfat_parameters[] = { + fsparam_u32("uid", Opt_uid), + fsparam_u32("gid", Opt_gid), + fsparam_u32oct("umask", Opt_umask), +@@ -222,25 +229,12 @@ 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), +- fsparam_enum("errors", Opt_errors), ++ fsparam_enum("errors", Opt_errors, exfat_param_enums), + fsparam_flag("discard", Opt_discard), + fsparam_s32("time_offset", Opt_time_offset), + {} + }; + +-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 }, +- {} +-}; +- +-static const struct fs_parameter_description exfat_parameters = { +- .name = "exfat", +- .specs = exfat_param_specs, +- .enums = exfat_param_enums, +-}; +- + static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) + { + struct exfat_sb_info *sbi = fc->s_fs_info; +@@ -248,7 +242,7 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) + struct fs_parse_result result; + int opt; + +- opt = fs_parse(fc, &exfat_parameters, param, &result); ++ opt = fs_parse(fc, exfat_parameters, param, &result); + if (opt < 0) + return opt; + +@@ -665,7 +659,7 @@ static struct file_system_type exfat_fs_type = { + .owner = THIS_MODULE, + .name = "exfat", + .init_fs_context = exfat_init_fs_context, +- .parameters = &exfat_parameters, ++ .parameters = exfat_parameters, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, + }; +-- +2.31.1 + diff --git a/SOURCES/0013-exfat-Fix-discard-support.patch b/SOURCES/0013-exfat-Fix-discard-support.patch new file mode 100644 index 0000000..925518b --- /dev/null +++ b/SOURCES/0013-exfat-Fix-discard-support.patch @@ -0,0 +1,38 @@ +From b7e038a92449f191395fbb86538b14093c529833 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Tue, 17 Mar 2020 22:46:52 +0100 +Subject: [Backport b7e038a92449] exfat: Fix discard support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Discard support was always unconditionally disabled. Now it is disabled +only in the case when blk_queue_discard() returns false. + +Signed-off-by: Pali Rohár +Signed-off-by: Namjae Jeon +--- + src/super.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/super.c b/src/super.c +index 16ed202ef5279abc40b23d03cd346b4baf4c23d3..30e914ad17b59643bb9ad99beb7868a79659dcee 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -531,10 +531,11 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + if (opts->discard) { + struct request_queue *q = bdev_get_queue(sb->s_bdev); + +- if (!blk_queue_discard(q)) ++ if (!blk_queue_discard(q)) { + exfat_msg(sb, KERN_WARNING, + "mounting with \"discard\" option, but the device does not support discard"); +- opts->discard = 0; ++ opts->discard = 0; ++ } + } + + sb->s_flags |= SB_NODIRATIME; +-- +2.31.1 + diff --git a/SOURCES/0014-exfat-add-missing-MODULE_ALIAS_FS.patch b/SOURCES/0014-exfat-add-missing-MODULE_ALIAS_FS.patch new file mode 100644 index 0000000..d48a985 --- /dev/null +++ b/SOURCES/0014-exfat-add-missing-MODULE_ALIAS_FS.patch @@ -0,0 +1,29 @@ +From cd76ac258cd92a6425cb68f95a76d6abdce5395a Mon Sep 17 00:00:00 2001 +From: Thomas Backlund +Date: Sat, 4 Apr 2020 23:29:43 +0300 +Subject: [Backport cd76ac258cd9] exfat: add missing MODULE_ALIAS_FS() + +This adds the necessary MODULE_ALIAS_FS() to exfat so the module gets +automatically loaded when an exfat filesystem is mounted. + +Signed-off-by: Thomas Backlund +Signed-off-by: Namjae Jeon +--- + src/super.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/super.c b/src/super.c +index 30e914ad17b59643bb9ad99beb7868a79659dcee..86c87560602400ec2bc7587268bf025cc54e9ce7 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -718,6 +718,7 @@ static void __exit exit_exfat_fs(void) + module_init(init_exfat_fs); + module_exit(exit_exfat_fs); + ++MODULE_ALIAS_FS("exfat"); + MODULE_LICENSE("GPL"); + MODULE_DESCRIPTION("exFAT filesystem support"); + MODULE_AUTHOR("Samsung Electronics Co., Ltd."); +-- +2.31.1 + diff --git a/SOURCES/0015-exfat-Unify-access-to-the-boot-sector.patch b/SOURCES/0015-exfat-Unify-access-to-the-boot-sector.patch new file mode 100644 index 0000000..337e2d4 --- /dev/null +++ b/SOURCES/0015-exfat-Unify-access-to-the-boot-sector.patch @@ -0,0 +1,174 @@ +From b0516833d8096c40ead3169c90d87392fb5f8f45 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Tue, 21 Apr 2020 10:58:58 +0900 +Subject: [Backport b0516833d809] exfat: Unify access to the boot sector + +Unify access to boot sector via 'sbi->pbr_bh'. +This fixes vol_flags inconsistency at read failed in fs_set_vol_flags(), +and buffer_head leak in __exfat_fill_super(). + +Signed-off-by: Tetsuhiro Kohada +Signed-off-by: Namjae Jeon +--- + src/balloc.c | 3 --- + src/super.c | 43 ++++++++++++++++--------------------------- + 2 files changed, 16 insertions(+), 30 deletions(-) + +diff --git a/src/balloc.c b/src/balloc.c +index 6a04cc02565a1f3823ad9a109238bad9553a82f1..6774a5a6ded8290fb790bf70f04a1c6e3253cd7a 100644 +--- a/src/balloc.c ++++ b/src/balloc.c +@@ -91,7 +91,6 @@ static int exfat_allocate_bitmap(struct super_block *sb, + } + } + +- sbi->pbr_bh = NULL; + return 0; + } + +@@ -137,8 +136,6 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) + { + int i; + +- brelse(sbi->pbr_bh); +- + for (i = 0; i < sbi->map_sectors; i++) + __brelse(sbi->vol_amap[i]); + +diff --git a/src/super.c b/src/super.c +index 86c87560602400ec2bc7587268bf025cc54e9ce7..6b63e5092628143ba5c8f3a5437eb3f5a1b68ea7 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -49,6 +49,7 @@ static void exfat_put_super(struct super_block *sb) + sync_blockdev(sb->s_bdev); + exfat_set_vol_flags(sb, VOL_CLEAN); + exfat_free_bitmap(sbi); ++ brelse(sbi->pbr_bh); + mutex_unlock(&sbi->s_lock); + + call_rcu(&sbi->rcu, exfat_delayed_free); +@@ -100,7 +101,7 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) + int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); +- struct pbr64 *bpb; ++ struct pbr64 *bpb = (struct pbr64 *)sbi->pbr_bh->b_data; + bool sync = 0; + + /* flags are not changed */ +@@ -115,15 +116,6 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) + if (sb_rdonly(sb)) + return 0; + +- if (!sbi->pbr_bh) { +- sbi->pbr_bh = sb_bread(sb, 0); +- if (!sbi->pbr_bh) { +- exfat_msg(sb, KERN_ERR, "failed to read boot sector"); +- return -ENOMEM; +- } +- } +- +- bpb = (struct pbr64 *)sbi->pbr_bh->b_data; + bpb->bsx.vol_flags = cpu_to_le16(new_flag); + + if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->pbr_bh)) +@@ -355,10 +347,10 @@ static int exfat_read_root(struct inode *inode) + return 0; + } + +-static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb, +- struct buffer_head **prev_bh) ++static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) + { +- struct pbr *p_pbr = (struct pbr *) (*prev_bh)->b_data; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct pbr *p_pbr = (struct pbr *) (sbi->pbr_bh)->b_data; + unsigned short logical_sect = 0; + + logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; +@@ -378,26 +370,23 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb, + } + + if (logical_sect > sb->s_blocksize) { +- struct buffer_head *bh = NULL; +- +- __brelse(*prev_bh); +- *prev_bh = NULL; ++ brelse(sbi->pbr_bh); ++ sbi->pbr_bh = NULL; + + if (!sb_set_blocksize(sb, logical_sect)) { + exfat_msg(sb, KERN_ERR, + "unable to set blocksize %u", logical_sect); + return NULL; + } +- bh = sb_bread(sb, 0); +- if (!bh) { ++ sbi->pbr_bh = sb_bread(sb, 0); ++ if (!sbi->pbr_bh) { + exfat_msg(sb, KERN_ERR, + "unable to read boot sector (logical sector size = %lu)", + sb->s_blocksize); + return NULL; + } + +- *prev_bh = bh; +- p_pbr = (struct pbr *) bh->b_data; ++ p_pbr = (struct pbr *)sbi->pbr_bh->b_data; + } + return p_pbr; + } +@@ -408,21 +397,20 @@ static int __exfat_fill_super(struct super_block *sb) + int ret; + struct pbr *p_pbr; + struct pbr64 *p_bpb; +- struct buffer_head *bh; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + /* set block size to read super block */ + sb_min_blocksize(sb, 512); + + /* read boot sector */ +- bh = sb_bread(sb, 0); +- if (!bh) { ++ sbi->pbr_bh = sb_bread(sb, 0); ++ if (!sbi->pbr_bh) { + exfat_msg(sb, KERN_ERR, "unable to read boot sector"); + return -EIO; + } + + /* PRB is read */ +- p_pbr = (struct pbr *)bh->b_data; ++ p_pbr = (struct pbr *)sbi->pbr_bh->b_data; + + /* check the validity of PBR */ + if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { +@@ -433,7 +421,7 @@ static int __exfat_fill_super(struct super_block *sb) + + + /* check logical sector size */ +- p_pbr = exfat_read_pbr_with_logical_sector(sb, &bh); ++ p_pbr = exfat_read_pbr_with_logical_sector(sb); + if (!p_pbr) { + ret = -EIO; + goto free_bh; +@@ -514,7 +502,7 @@ static int __exfat_fill_super(struct super_block *sb) + free_upcase_table: + exfat_free_upcase_table(sbi); + free_bh: +- brelse(bh); ++ brelse(sbi->pbr_bh); + return ret; + } + +@@ -606,6 +594,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + free_table: + exfat_free_upcase_table(sbi); + exfat_free_bitmap(sbi); ++ brelse(sbi->pbr_bh); + + check_nls_io: + unload_nls(sbi->nls_io); +-- +2.31.1 + diff --git a/SOURCES/0016-exfat-remove-bps-mount-option.patch b/SOURCES/0016-exfat-remove-bps-mount-option.patch new file mode 100644 index 0000000..cfeafaf --- /dev/null +++ b/SOURCES/0016-exfat-remove-bps-mount-option.patch @@ -0,0 +1,33 @@ +From cbd445d9a9bdb7020beee2477577b9472cca60be Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Tue, 7 Apr 2020 17:34:10 +0900 +Subject: [Backport cbd445d9a9bd] exfat: remove 'bps' mount-option + +remount fails because exfat_show_options() returns unsupported +option 'bps'. +> # mount -o ro,remount +> exfat: Unknown parameter 'bps' + +To fix the problem, just remove 'bps' option from exfat_show_options(). + +Signed-off-by: Tetsuhiro Kohada +Signed-off-by: Namjae Jeon +--- + src/super.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/super.c b/src/super.c +index 6b63e5092628143ba5c8f3a5437eb3f5a1b68ea7..cb5eac38102d92d9312667fdaa722f6a00d2ad16 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -151,7 +151,6 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) + seq_puts(m, ",iocharset=utf8"); + else if (sbi->nls_io) + seq_printf(m, ",iocharset=%s", sbi->nls_io->charset); +- seq_printf(m, ",bps=%ld", sb->s_blocksize); + if (opts->errors == EXFAT_ERRORS_CONT) + seq_puts(m, ",errors=continue"); + else if (opts->errors == EXFAT_ERRORS_PANIC) +-- +2.31.1 + diff --git a/SOURCES/0017-exfat-properly-set-s_time_gran.patch b/SOURCES/0017-exfat-properly-set-s_time_gran.patch new file mode 100644 index 0000000..d29d37a --- /dev/null +++ b/SOURCES/0017-exfat-properly-set-s_time_gran.patch @@ -0,0 +1,32 @@ +From 674a9985b8e35288225f2b67829a7dee3bf761da Mon Sep 17 00:00:00 2001 +From: Eric Sandeen +Date: Fri, 17 Apr 2020 14:43:49 +0900 +Subject: [Backport 674a9985b8e3] exfat: properly set s_time_gran + +The s_time_gran superblock field indicates the on-disk nanosecond +granularity of timestamps, and for exfat that seems to be 10ms, so +set s_time_gran to 10000000ns. Without this, in-memory timestamps +change when they get re-read from disk. + +Signed-off-by: Eric Sandeen +Signed-off-by: Namjae Jeon +--- + src/super.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/super.c b/src/super.c +index cb5eac38102d92d9312667fdaa722f6a00d2ad16..ee42ecf59059a6c9bf61f5b6ec19efde21a4d897 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -529,7 +529,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + sb->s_magic = EXFAT_SUPER_MAGIC; + sb->s_op = &exfat_sops; + +- sb->s_time_gran = 1; ++ sb->s_time_gran = 10 * NSEC_PER_MSEC; + sb->s_time_min = EXFAT_MIN_TIMESTAMP_SECS; + sb->s_time_max = EXFAT_MAX_TIMESTAMP_SECS; + +-- +2.31.1 + diff --git a/SOURCES/0018-exfat-truncate-atimes-to-2s-granularity.patch b/SOURCES/0018-exfat-truncate-atimes-to-2s-granularity.patch new file mode 100644 index 0000000..8f3d3c0 --- /dev/null +++ b/SOURCES/0018-exfat-truncate-atimes-to-2s-granularity.patch @@ -0,0 +1,160 @@ +From 81df1ad40644b706a1cdbd28a1471f9f0c0ea3e8 Mon Sep 17 00:00:00 2001 +From: Eric Sandeen +Date: Tue, 21 Apr 2020 11:13:10 +0900 +Subject: [Backport 81df1ad40644] exfat: truncate atimes to 2s granularity + +The timestamp for access_time has double seconds granularity(There is no +10msIncrement field for access_time unlike create/modify_time). +exfat's atimes are restricted to only 2s granularity so after +we set an atime, round it down to the nearest 2s and set the +sub-second component of the timestamp to 0. + +Signed-off-by: Eric Sandeen +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 1 + + src/file.c | 2 ++ + src/misc.c | 14 +++++++++++++- + src/namei.c | 7 +++++++ + src/super.c | 1 + + 5 files changed, 24 insertions(+), 1 deletion(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 67d4e46fb81003d0c367862ee986b37c80e7fa4e..d67fb8a6f770c28db8f1618c78fde493e2a4b965 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -507,6 +507,7 @@ void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) + __printf(3, 4) __cold; + void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + u8 tz, __le16 time, __le16 date, u8 time_ms); ++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_ms); + unsigned short exfat_calc_chksum_2byte(void *data, int len, +diff --git a/src/file.c b/src/file.c +index 483f683757aa87420ba347afd3c9db9e94b8a14d..4f76764165cf6f63a99c5e192ab33685e8e987b7 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -273,6 +273,7 @@ int exfat_getattr(const struct path *path, struct kstat *stat, + struct exfat_inode_info *ei = EXFAT_I(inode); + + generic_fillattr(inode, stat); ++ exfat_truncate_atime(&stat->atime); + stat->result_mask |= STATX_BTIME; + stat->btime.tv_sec = ei->i_crtime.tv_sec; + stat->btime.tv_nsec = ei->i_crtime.tv_nsec; +@@ -339,6 +340,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) + } + + setattr_copy(inode, attr); ++ exfat_truncate_atime(&inode->i_atime); + mark_inode_dirty(inode); + + out: +diff --git a/src/misc.c b/src/misc.c +index 14a3300848f6a0fb754b9d34bdacbc31b90f5d07..ebd2cbe3cbc11c2aede3819c414020b9344ec1f9 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -88,7 +88,8 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + if (time_ms) { + ts->tv_sec += time_ms / 100; + ts->tv_nsec = (time_ms % 100) * 10 * NSEC_PER_MSEC; +- } ++ } else ++ ts->tv_nsec = 0; + + if (tz & EXFAT_TZ_VALID) + /* Adjust timezone to UTC0. */ +@@ -124,6 +125,17 @@ 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. ++ */ ++void exfat_truncate_atime(struct timespec64 *ts) ++{ ++ ts->tv_sec = round_down(ts->tv_sec, 2); ++ ts->tv_nsec = 0; ++} ++ + unsigned short exfat_calc_chksum_2byte(void *data, int len, + unsigned short chksum, int type) + { +diff --git a/src/namei.c b/src/namei.c +index a8681d91f56900107ea3df90e2b6dd986045a7bc..b72d782568b811ece222db0dd3575fd8bf2c958a 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -595,6 +595,7 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, + inode_inc_iversion(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = + EXFAT_I(inode)->i_crtime = current_time(inode); ++ exfat_truncate_atime(&inode->i_atime); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + d_instantiate(dentry, inode); +@@ -854,6 +855,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) + + inode_inc_iversion(dir); + dir->i_mtime = dir->i_atime = current_time(dir); ++ exfat_truncate_atime(&dir->i_atime); + if (IS_DIRSYNC(dir)) + exfat_sync_inode(dir); + else +@@ -861,6 +863,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) + + clear_nlink(inode); + inode->i_mtime = inode->i_atime = current_time(inode); ++ exfat_truncate_atime(&inode->i_atime); + exfat_unhash_inode(inode); + exfat_d_version_set(dentry, inode_query_iversion(dir)); + unlock: +@@ -903,6 +906,7 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) + inode_inc_iversion(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = + EXFAT_I(inode)->i_crtime = current_time(inode); ++ exfat_truncate_atime(&inode->i_atime); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + d_instantiate(dentry, inode); +@@ -1019,6 +1023,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + + inode_inc_iversion(dir); + dir->i_mtime = dir->i_atime = current_time(dir); ++ exfat_truncate_atime(&dir->i_atime); + if (IS_DIRSYNC(dir)) + exfat_sync_inode(dir); + else +@@ -1027,6 +1032,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + + clear_nlink(inode); + inode->i_mtime = inode->i_atime = current_time(inode); ++ exfat_truncate_atime(&inode->i_atime); + exfat_unhash_inode(inode); + exfat_d_version_set(dentry, inode_query_iversion(dir)); + unlock: +@@ -1387,6 +1393,7 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, + inode_inc_iversion(new_dir); + new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = + EXFAT_I(new_dir)->i_crtime = current_time(new_dir); ++ exfat_truncate_atime(&new_dir->i_atime); + if (IS_DIRSYNC(new_dir)) + exfat_sync_inode(new_dir); + else +diff --git a/src/super.c b/src/super.c +index ee42ecf59059a6c9bf61f5b6ec19efde21a4d897..0565d5539d57c6678c81997228dc3b8116eb864c 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -342,6 +342,7 @@ static int exfat_read_root(struct inode *inode) + exfat_save_attr(inode, ATTR_SUBDIR); + inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = + current_time(inode); ++ exfat_truncate_atime(&inode->i_atime); + exfat_cache_init_inode(inode); + return 0; + } +-- +2.31.1 + diff --git a/SOURCES/0019-exfat-use-iter_file_splice_write.patch b/SOURCES/0019-exfat-use-iter_file_splice_write.patch new file mode 100644 index 0000000..3e7f918 --- /dev/null +++ b/SOURCES/0019-exfat-use-iter_file_splice_write.patch @@ -0,0 +1,66 @@ +From 035779483072ff7854943dc0cbae82c4e0070d15 Mon Sep 17 00:00:00 2001 +From: Eric Sandeen +Date: Fri, 1 May 2020 20:34:25 -0500 +Subject: [Backport 035779483072] exfat: use iter_file_splice_write + +Doing copy_file_range() on exfat with a file opened for direct IO leads +to an -EFAULT: + +# xfs_io -f -d -c "truncate 32768" \ + -c "copy_range -d 16384 -l 16384 -f 0" /mnt/test/junk +copy_range: Bad address + +and the reason seems to be that we go through: + +default_file_splice_write + splice_from_pipe + __splice_from_pipe + write_pipe_buf + __kernel_write + new_sync_write + generic_file_write_iter + generic_file_direct_write + exfat_direct_IO + do_blockdev_direct_IO + iov_iter_get_pages + +and land in iterate_all_kinds(), which does "return -EFAULT" for our kvec +iter. + +Setting exfat's splice_write to iter_file_splice_write fixes this and lets +fsx (which originally detected the problem) run to success from +the xfstests harness. + +Signed-off-by: Eric Sandeen +Signed-off-by: Namjae Jeon +--- + src/file.c | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +diff --git a/src/file.c b/src/file.c +index 4f76764165cf6f63a99c5e192ab33685e8e987b7..c9db8eb0cfc3ee267d5e3b051974ceae8de861b5 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -348,12 +348,13 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) + } + + const struct file_operations exfat_file_operations = { +- .llseek = generic_file_llseek, +- .read_iter = generic_file_read_iter, +- .write_iter = generic_file_write_iter, +- .mmap = generic_file_mmap, +- .fsync = generic_file_fsync, +- .splice_read = generic_file_splice_read, ++ .llseek = generic_file_llseek, ++ .read_iter = generic_file_read_iter, ++ .write_iter = generic_file_write_iter, ++ .mmap = generic_file_mmap, ++ .fsync = generic_file_fsync, ++ .splice_read = generic_file_splice_read, ++ .splice_write = iter_file_splice_write, + }; + + const struct inode_operations exfat_file_inode_operations = { +-- +2.31.1 + diff --git a/SOURCES/0020-exfat-fix-possible-memory-leak-in-exfat_find.patch b/SOURCES/0020-exfat-fix-possible-memory-leak-in-exfat_find.patch new file mode 100644 index 0000000..824af9f --- /dev/null +++ b/SOURCES/0020-exfat-fix-possible-memory-leak-in-exfat_find.patch @@ -0,0 +1,32 @@ +From 94182167ec730dadcaea5fbc6bb8f1136966ef66 Mon Sep 17 00:00:00 2001 +From: Wei Yongjun +Date: Wed, 6 May 2020 14:25:54 +0000 +Subject: [Backport 94182167ec73] exfat: fix possible memory leak in + exfat_find() + +'es' is malloced from exfat_get_dentry_set() in exfat_find() and should +be freed before leaving from the error handling cases, otherwise it will +cause memory leak. + +Fixes: 5f2aa075070c ("exfat: add inode operations") +Signed-off-by: Wei Yongjun +Signed-off-by: Namjae Jeon +--- + src/namei.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/namei.c b/src/namei.c +index b72d782568b811ece222db0dd3575fd8bf2c958a..a2659a8a68a14273eeff16dd467d771ebe038b43 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -692,6 +692,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + exfat_fs_error(sb, + "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", + i_size_read(dir), ei->dir.dir, ei->entry); ++ kfree(es); + return -EIO; + } + +-- +2.31.1 + diff --git a/SOURCES/0021-exfat-add-the-dummy-mount-options-to-be-backward-com.patch b/SOURCES/0021-exfat-add-the-dummy-mount-options-to-be-backward-com.patch new file mode 100644 index 0000000..0fa4703 --- /dev/null +++ b/SOURCES/0021-exfat-add-the-dummy-mount-options-to-be-backward-com.patch @@ -0,0 +1,77 @@ +From 907fa893258ba6076f5fff32900a461decb9e8c5 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Fri, 22 May 2020 08:10:10 +0900 +Subject: [Backport 907fa893258b] exfat: add the dummy mount options to be + backward compatible with staging/exfat + +As Ubuntu and Fedora release new version used kernel version equal to or +higher than v5.4, They started to support kernel exfat filesystem. + +Linus reported a mount error with new version of exfat on Fedora: + + exfat: Unknown parameter 'namecase' + +This is because there is a difference in mount option between old +staging/exfat and new exfat. And utf8, debug, and codepage options as +well as namecase have been removed from new exfat. + +This patch add the dummy mount options as deprecated option to be +backward compatible with old one. + +Reported-by: Linus Torvalds +Signed-off-by: Namjae Jeon +Cc: Matthew Wilcox +Cc: Al Viro +Cc: Eric Sandeen +Signed-off-by: Linus Torvalds +--- + src/super.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/src/super.c b/src/super.c +index 0565d5539d57c6678c81997228dc3b8116eb864c..a846ff555656cd876e77ae014b35e192b5ccd183 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -203,6 +203,12 @@ enum { + Opt_errors, + Opt_discard, + Opt_time_offset, ++ ++ /* Deprecated options */ ++ Opt_utf8, ++ Opt_debug, ++ Opt_namecase, ++ Opt_codepage, + }; + + static const struct constant_table exfat_param_enums[] = { +@@ -223,6 +229,14 @@ static const struct fs_parameter_spec exfat_parameters[] = { + fsparam_enum("errors", Opt_errors, exfat_param_enums), + fsparam_flag("discard", Opt_discard), + 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), + {} + }; + +@@ -278,6 +292,11 @@ 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; + } +-- +2.31.1 + diff --git a/SOURCES/0022-fs-convert-mpage_readpages-to-mpage_readahead.patch b/SOURCES/0022-fs-convert-mpage_readpages-to-mpage_readahead.patch new file mode 100644 index 0000000..8238c13 --- /dev/null +++ b/SOURCES/0022-fs-convert-mpage_readpages-to-mpage_readahead.patch @@ -0,0 +1,65 @@ +From d4388340ae0bc8397ef5b24342279f7739982918 Mon Sep 17 00:00:00 2001 +From: "Matthew Wilcox (Oracle)" +Date: Mon, 1 Jun 2020 21:47:02 -0700 +Subject: [Backport d4388340ae0b] fs: convert mpage_readpages to + mpage_readahead + +Implement the new readahead aop and convert all callers (block_dev, +exfat, ext2, fat, gfs2, hpfs, isofs, jfs, nilfs2, ocfs2, omfs, qnx6, +reiserfs & udf). + +The callers are all trivial except for GFS2 & OCFS2. + +Signed-off-by: Matthew Wilcox (Oracle) +Signed-off-by: Andrew Morton +Reviewed-by: Junxiao Bi # ocfs2 +Reviewed-by: Joseph Qi # ocfs2 +Reviewed-by: Dave Chinner +Reviewed-by: John Hubbard +Reviewed-by: Christoph Hellwig +Reviewed-by: William Kucharski +Cc: Chao Yu +Cc: Cong Wang +Cc: Darrick J. Wong +Cc: Eric Biggers +Cc: Gao Xiang +Cc: Jaegeuk Kim +Cc: Michal Hocko +Cc: Zi Yan +Cc: Johannes Thumshirn +Cc: Miklos Szeredi +Link: http://lkml.kernel.org/r/20200414150233.24495-17-willy@infradead.org +Signed-off-by: Linus Torvalds +--- + src/inode.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/src/inode.c b/src/inode.c +index 06887492f54b791506d2f7c76eb3ef073b72026d..785ead346543cb54d440ee1797bee941810f72b7 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -372,10 +372,9 @@ static int exfat_readpage(struct file *file, struct page *page) + return mpage_readpage(page, exfat_get_block); + } + +-static int exfat_readpages(struct file *file, struct address_space *mapping, +- struct list_head *pages, unsigned int nr_pages) ++static void exfat_readahead(struct readahead_control *rac) + { +- return mpage_readpages(mapping, pages, nr_pages, exfat_get_block); ++ mpage_readahead(rac, exfat_get_block); + } + + static int exfat_writepage(struct page *page, struct writeback_control *wbc) +@@ -502,7 +501,7 @@ int exfat_block_truncate_page(struct inode *inode, loff_t from) + + static const struct address_space_operations exfat_aops = { + .readpage = exfat_readpage, +- .readpages = exfat_readpages, ++ .readahead = exfat_readahead, + .writepage = exfat_writepage, + .writepages = exfat_writepages, + .write_begin = exfat_write_begin, +-- +2.31.1 + diff --git a/SOURCES/0023-exfat-Simplify-exfat_utf8_d_cmp-for-code-points-abov.patch b/SOURCES/0023-exfat-Simplify-exfat_utf8_d_cmp-for-code-points-abov.patch new file mode 100644 index 0000000..3479f29 --- /dev/null +++ b/SOURCES/0023-exfat-Simplify-exfat_utf8_d_cmp-for-code-points-abov.patch @@ -0,0 +1,43 @@ +From 197298a64983e2beaf1a87413daff3044b4f3821 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Tue, 17 Mar 2020 22:34:33 +0100 +Subject: [Backport 197298a64983] exfat: Simplify exfat_utf8_d_cmp() for code + points above U+FFFF +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If two Unicode code points represented in UTF-16 are different then also +their UTF-32 representation must be different. Therefore conversion from +UTF-32 to UTF-16 is not needed. + +Signed-off-by: Pali Rohár +Signed-off-by: Namjae Jeon +--- + src/namei.c | 9 ++------- + 1 file changed, 2 insertions(+), 7 deletions(-) + +diff --git a/src/namei.c b/src/namei.c +index a2659a8a68a14273eeff16dd467d771ebe038b43..731da41cabbf7b9dfd38f001fc502573bfd6c078 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -185,14 +185,9 @@ static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len, + if (u_a <= 0xFFFF && u_b <= 0xFFFF) { + if (exfat_toupper(sb, u_a) != exfat_toupper(sb, u_b)) + return 1; +- } else if (u_a > 0xFFFF && u_b > 0xFFFF) { +- if (exfat_low_surrogate(u_a) != +- exfat_low_surrogate(u_b) || +- exfat_high_surrogate(u_a) != +- exfat_high_surrogate(u_b)) +- return 1; + } else { +- return 1; ++ if (u_a != u_b) ++ return 1; + } + } + +-- +2.31.1 + diff --git a/SOURCES/0024-exfat-Use-a-more-common-logging-style.patch b/SOURCES/0024-exfat-Use-a-more-common-logging-style.patch new file mode 100644 index 0000000..99a0360 --- /dev/null +++ b/SOURCES/0024-exfat-Use-a-more-common-logging-style.patch @@ -0,0 +1,469 @@ +From d1727d55c0327efdba2a5bad7801d509b721fef3 Mon Sep 17 00:00:00 2001 +From: Joe Perches +Date: Fri, 24 Apr 2020 13:31:12 +0900 +Subject: [Backport d1727d55c032] exfat: Use a more common logging style + +Remove the direct use of KERN_ in functions by creating +separate exfat_ macros. + +Miscellanea: + +o Remove several unnecessary terminating newlines in formats +o Realign arguments and fit to 80 columns where appropriate + +Signed-off-by: Joe Perches +Signed-off-by: Namjae Jeon +--- + src/balloc.c | 8 +++----- + src/dir.c | 9 ++++----- + src/exfat_fs.h | 7 +++++++ + src/fatent.c | 13 +++++-------- + src/misc.c | 4 ++-- + src/namei.c | 26 ++++++++++--------------- + src/nls.c | 20 ++++++++----------- + src/super.c | 47 ++++++++++++++++++++------------------------- + 8 files changed, 60 insertions(+), 74 deletions(-) + +diff --git a/src/balloc.c b/src/balloc.c +index 6774a5a6ded8290fb790bf70f04a1c6e3253cd7a..4055eb00ea9b84a3550dea0f831fafa2df3b3121 100644 +--- a/src/balloc.c ++++ b/src/balloc.c +@@ -58,9 +58,8 @@ static int exfat_allocate_bitmap(struct super_block *sb, + need_map_size = ((EXFAT_DATA_CLUSTER_COUNT(sbi) - 1) / BITS_PER_BYTE) + + 1; + if (need_map_size != map_size) { +- exfat_msg(sb, KERN_ERR, +- "bogus allocation bitmap size(need : %u, cur : %lld)", +- need_map_size, map_size); ++ exfat_err(sb, "bogus allocation bitmap size(need : %u, cur : %lld)", ++ need_map_size, map_size); + /* + * Only allowed when bogus allocation + * bitmap size is large +@@ -192,8 +191,7 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu) + (1 << sbi->sect_per_clus_bits), GFP_NOFS, 0); + + if (ret_discard == -EOPNOTSUPP) { +- exfat_msg(sb, KERN_ERR, +- "discard not supported by device, disabling"); ++ exfat_err(sb, "discard not supported by device, disabling"); + opts->discard = 0; + } + } +diff --git a/src/dir.c b/src/dir.c +index 4b91afb0f0515c06036821468b667ea14b43654d..53ae965da7ec583fb4e31cc548c40daf08ae941a 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -720,9 +720,8 @@ static int exfat_dir_readahead(struct super_block *sb, sector_t sec) + return 0; + + if (sec < sbi->data_start_sector) { +- exfat_msg(sb, KERN_ERR, +- "requested sector is invalid(sect:%llu, root:%llu)", +- (unsigned long long)sec, sbi->data_start_sector); ++ exfat_err(sb, "requested sector is invalid(sect:%llu, root:%llu)", ++ (unsigned long long)sec, sbi->data_start_sector); + return -EIO; + } + +@@ -750,7 +749,7 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb, + sector_t sec; + + if (p_dir->dir == DIR_DELETED) { +- exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry\n"); ++ exfat_err(sb, "abnormal access to deleted dentry"); + return NULL; + } + +@@ -853,7 +852,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, + struct buffer_head *bh; + + if (p_dir->dir == DIR_DELETED) { +- exfat_msg(sb, KERN_ERR, "access to deleted dentry\n"); ++ exfat_err(sb, "access to deleted dentry"); + return NULL; + } + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index d67fb8a6f770c28db8f1618c78fde493e2a4b965..1ebfb9085f1f719180051ee87aea94962accd238 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -505,6 +505,13 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) + fmt, ## args) + void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) + __printf(3, 4) __cold; ++#define exfat_err(sb, fmt, ...) \ ++ exfat_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__) ++#define exfat_warn(sb, fmt, ...) \ ++ exfat_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__) ++#define exfat_info(sb, fmt, ...) \ ++ exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) ++ + void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + u8 tz, __le16 time, __le16 date, u8 time_ms); + void exfat_truncate_atime(struct timespec64 *ts); +diff --git a/src/fatent.c b/src/fatent.c +index a855b1769a96198264b134bfb45bcd4f6f6c01d1..267e5e09eb134c34990ac2d5ce17e2692c955be8 100644 +--- a/src/fatent.c ++++ b/src/fatent.c +@@ -170,8 +170,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) + + /* check cluster validation */ + if (p_chain->dir < 2 && p_chain->dir >= sbi->num_clusters) { +- exfat_msg(sb, KERN_ERR, "invalid start cluster (%u)", +- p_chain->dir); ++ exfat_err(sb, "invalid start cluster (%u)", p_chain->dir); + return -EIO; + } + +@@ -305,8 +304,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) + return 0; + + release_bhs: +- exfat_msg(sb, KERN_ERR, "failed zeroed sect %llu\n", +- (unsigned long long)blknr); ++ exfat_err(sb, "failed zeroed sect %llu\n", (unsigned long long)blknr); + for (i = 0; i < n; i++) + bforget(bhs[i]); + return err; +@@ -337,9 +335,8 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, + /* find new cluster */ + if (hint_clu == EXFAT_EOF_CLUSTER) { + if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) { +- exfat_msg(sb, KERN_ERR, +- "sbi->clu_srch_ptr is invalid (%u)\n", +- sbi->clu_srch_ptr); ++ exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)\n", ++ sbi->clu_srch_ptr); + sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; + } + +@@ -350,7 +347,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, + + /* check cluster validation */ + if (hint_clu < EXFAT_FIRST_CLUSTER && hint_clu >= sbi->num_clusters) { +- exfat_msg(sb, KERN_ERR, "hint_cluster is invalid (%u)\n", ++ exfat_err(sb, "hint_cluster is invalid (%u)", + hint_clu); + hint_clu = EXFAT_FIRST_CLUSTER; + if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { +diff --git a/src/misc.c b/src/misc.c +index ebd2cbe3cbc11c2aede3819c414020b9344ec1f9..ce5e8a1b0726c6778db86daebe39c437f23c7fba 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -32,7 +32,7 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; +- exfat_msg(sb, KERN_ERR, "error, %pV\n", &vaf); ++ exfat_err(sb, "error, %pV", &vaf); + va_end(args); + } + +@@ -41,7 +41,7 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) + sb->s_id); + } else if (opts->errors == EXFAT_ERRORS_RO && !sb_rdonly(sb)) { + sb->s_flags |= SB_RDONLY; +- exfat_msg(sb, KERN_ERR, "Filesystem has been set read-only"); ++ exfat_err(sb, "Filesystem has been set read-only"); + } + } + +diff --git a/src/namei.c b/src/namei.c +index 731da41cabbf7b9dfd38f001fc502573bfd6c078..585b47b2db3da5d208fa1d53da9b18ae3d07c2dd 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -773,8 +773,8 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, + if (d_unhashed(alias)) { + WARN_ON(alias->d_name.hash_len != + dentry->d_name.hash_len); +- exfat_msg(sb, KERN_INFO, +- "rehashed a dentry(%p) in read lookup", alias); ++ exfat_info(sb, "rehashed a dentry(%p) in read lookup", ++ alias); + d_drop(dentry); + d_rehash(alias); + } else if (!S_ISDIR(i_mode)) { +@@ -819,7 +819,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) + exfat_chain_dup(&cdir, &ei->dir); + entry = ei->entry; + if (ei->dir.dir == DIR_DELETED) { +- exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry"); ++ exfat_err(sb, "abnormal access to deleted dentry"); + err = -ENOENT; + goto unlock; + } +@@ -974,7 +974,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + entry = ei->entry; + + if (ei->dir.dir == DIR_DELETED) { +- exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry"); ++ exfat_err(sb, "abnormal access to deleted dentry"); + err = -ENOENT; + goto unlock; + } +@@ -986,9 +986,8 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + err = exfat_check_dir_empty(sb, &clu_to_free); + if (err) { + if (err == -EIO) +- exfat_msg(sb, KERN_ERR, +- "failed to exfat_check_dir_empty : err(%d)", +- err); ++ exfat_err(sb, "failed to exfat_check_dir_empty : err(%d)", ++ err); + goto unlock; + } + +@@ -1009,9 +1008,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + + err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries); + if (err) { +- exfat_msg(sb, KERN_ERR, +- "failed to exfat_remove_entries : err(%d)", +- err); ++ exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err); + goto unlock; + } + ei->dir.dir = DIR_DELETED; +@@ -1240,8 +1237,7 @@ static int __exfat_rename(struct inode *old_parent_inode, + return -EINVAL; + + if (ei->dir.dir == DIR_DELETED) { +- exfat_msg(sb, KERN_ERR, +- "abnormal access to deleted source dentry"); ++ exfat_err(sb, "abnormal access to deleted source dentry"); + return -ENOENT; + } + +@@ -1263,8 +1259,7 @@ static int __exfat_rename(struct inode *old_parent_inode, + new_ei = EXFAT_I(new_inode); + + if (new_ei->dir.dir == DIR_DELETED) { +- exfat_msg(sb, KERN_ERR, +- "abnormal access to deleted target dentry"); ++ exfat_err(sb, "abnormal access to deleted target dentry"); + goto out; + } + +@@ -1426,8 +1421,7 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, + if (S_ISDIR(new_inode->i_mode)) + drop_nlink(new_inode); + } else { +- exfat_msg(sb, KERN_WARNING, +- "abnormal access to an inode dropped"); ++ exfat_warn(sb, "abnormal access to an inode dropped"); + WARN_ON(new_inode->i_nlink == 0); + } + new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = +diff --git a/src/nls.c b/src/nls.c +index 6d1c3ae130ff177cec9261510b6f7c4a961f5177..2178786b708b577a385137994a41e078bedcb8b7 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -503,16 +503,14 @@ static int exfat_utf8_to_utf16(struct super_block *sb, + unilen = utf8s_to_utf16s(p_cstring, len, UTF16_HOST_ENDIAN, + (wchar_t *)uniname, MAX_NAME_LENGTH + 2); + if (unilen < 0) { +- exfat_msg(sb, KERN_ERR, +- "failed to %s (err : %d) nls len : %d", +- __func__, unilen, len); ++ exfat_err(sb, "failed to %s (err : %d) nls len : %d", ++ __func__, unilen, len); + return unilen; + } + + if (unilen > MAX_NAME_LENGTH) { +- exfat_msg(sb, KERN_ERR, +- "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", +- __func__, len, unilen, MAX_NAME_LENGTH); ++ exfat_err(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", ++ __func__, len, unilen, MAX_NAME_LENGTH); + return -ENAMETOOLONG; + } + +@@ -687,9 +685,8 @@ static int exfat_load_upcase_table(struct super_block *sb, + + bh = sb_bread(sb, sector); + if (!bh) { +- exfat_msg(sb, KERN_ERR, +- "failed to read sector(0x%llx)\n", +- (unsigned long long)sector); ++ exfat_err(sb, "failed to read sector(0x%llx)\n", ++ (unsigned long long)sector); + ret = -EIO; + goto free_table; + } +@@ -722,9 +719,8 @@ static int exfat_load_upcase_table(struct super_block *sb, + if (index >= 0xFFFF && utbl_checksum == checksum) + return 0; + +- exfat_msg(sb, KERN_ERR, +- "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)\n", +- index, checksum, utbl_checksum); ++ exfat_err(sb, "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)", ++ index, checksum, utbl_checksum); + ret = -EINVAL; + free_table: + exfat_free_upcase_table(sbi); +diff --git a/src/super.c b/src/super.c +index a846ff555656cd876e77ae014b35e192b5ccd183..f9aa1e5dc2381a4f9e4f9dc6f3d61dc525f1095c 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -376,15 +376,13 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) + + if (!is_power_of_2(logical_sect) || + logical_sect < 512 || logical_sect > 4096) { +- exfat_msg(sb, KERN_ERR, "bogus logical sector size %u", +- logical_sect); ++ exfat_err(sb, "bogus logical sector size %u", logical_sect); + return NULL; + } + + if (logical_sect < sb->s_blocksize) { +- exfat_msg(sb, KERN_ERR, +- "logical sector size too small for device (logical sector size = %u)", +- logical_sect); ++ exfat_err(sb, "logical sector size too small for device (logical sector size = %u)", ++ logical_sect); + return NULL; + } + +@@ -393,15 +391,14 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) + sbi->pbr_bh = NULL; + + if (!sb_set_blocksize(sb, logical_sect)) { +- exfat_msg(sb, KERN_ERR, +- "unable to set blocksize %u", logical_sect); ++ exfat_err(sb, "unable to set blocksize %u", ++ logical_sect); + return NULL; + } + sbi->pbr_bh = sb_bread(sb, 0); + if (!sbi->pbr_bh) { +- exfat_msg(sb, KERN_ERR, +- "unable to read boot sector (logical sector size = %lu)", +- sb->s_blocksize); ++ exfat_err(sb, "unable to read boot sector (logical sector size = %lu)", ++ sb->s_blocksize); + return NULL; + } + +@@ -424,7 +421,7 @@ static int __exfat_fill_super(struct super_block *sb) + /* read boot sector */ + sbi->pbr_bh = sb_bread(sb, 0); + if (!sbi->pbr_bh) { +- exfat_msg(sb, KERN_ERR, "unable to read boot sector"); ++ exfat_err(sb, "unable to read boot sector"); + return -EIO; + } + +@@ -433,7 +430,7 @@ static int __exfat_fill_super(struct super_block *sb) + + /* check the validity of PBR */ + if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { +- exfat_msg(sb, KERN_ERR, "invalid boot record signature"); ++ exfat_err(sb, "invalid boot record signature"); + ret = -EINVAL; + goto free_bh; + } +@@ -458,7 +455,7 @@ static int __exfat_fill_super(struct super_block *sb) + + p_bpb = (struct pbr64 *)p_pbr; + if (!p_bpb->bsx.num_fats) { +- exfat_msg(sb, KERN_ERR, "bogus number of FAT structure"); ++ exfat_err(sb, "bogus number of FAT structure"); + ret = -EINVAL; + goto free_bh; + } +@@ -488,8 +485,7 @@ static int __exfat_fill_super(struct super_block *sb) + + if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) { + sbi->vol_flag |= VOL_DIRTY; +- exfat_msg(sb, KERN_WARNING, +- "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); ++ exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); + } + + /* exFAT file size is limited by a disk volume size */ +@@ -498,19 +494,19 @@ static int __exfat_fill_super(struct super_block *sb) + + ret = exfat_create_upcase_table(sb); + if (ret) { +- exfat_msg(sb, KERN_ERR, "failed to load upcase table"); ++ exfat_err(sb, "failed to load upcase table"); + goto free_bh; + } + + ret = exfat_load_bitmap(sb); + if (ret) { +- exfat_msg(sb, KERN_ERR, "failed to load alloc-bitmap"); ++ exfat_err(sb, "failed to load alloc-bitmap"); + goto free_upcase_table; + } + + ret = exfat_count_used_clusters(sb, &sbi->used_clusters); + if (ret) { +- exfat_msg(sb, KERN_ERR, "failed to scan clusters"); ++ exfat_err(sb, "failed to scan clusters"); + goto free_alloc_bitmap; + } + +@@ -539,8 +535,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + 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"); ++ exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } + } +@@ -555,7 +550,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + + err = __exfat_fill_super(sb); + if (err) { +- exfat_msg(sb, KERN_ERR, "failed to recognize exfat type"); ++ exfat_err(sb, "failed to recognize exfat type"); + goto check_nls_io; + } + +@@ -567,8 +562,8 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + else { + sbi->nls_io = load_nls(sbi->options.iocharset); + if (!sbi->nls_io) { +- exfat_msg(sb, KERN_ERR, "IO charset %s not found", +- sbi->options.iocharset); ++ exfat_err(sb, "IO charset %s not found", ++ sbi->options.iocharset); + err = -EINVAL; + goto free_table; + } +@@ -581,7 +576,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + + root_inode = new_inode(sb); + if (!root_inode) { +- exfat_msg(sb, KERN_ERR, "failed to allocate root inode."); ++ exfat_err(sb, "failed to allocate root inode"); + err = -ENOMEM; + goto free_table; + } +@@ -590,7 +585,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + inode_set_iversion(root_inode, 1); + err = exfat_read_root(root_inode); + if (err) { +- exfat_msg(sb, KERN_ERR, "failed to initialize root inode."); ++ exfat_err(sb, "failed to initialize root inode"); + goto put_inode; + } + +@@ -599,7 +594,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + + sb->s_root = d_make_root(root_inode); + if (!sb->s_root) { +- exfat_msg(sb, KERN_ERR, "failed to get the root dentry"); ++ exfat_err(sb, "failed to get the root dentry"); + err = -ENOMEM; + goto put_inode; + } +-- +2.31.1 + diff --git a/SOURCES/0025-exfat-Simplify-exfat_utf8_d_hash-for-code-points-abo.patch b/SOURCES/0025-exfat-Simplify-exfat_utf8_d_hash-for-code-points-abo.patch new file mode 100644 index 0000000..92eb280 --- /dev/null +++ b/SOURCES/0025-exfat-Simplify-exfat_utf8_d_hash-for-code-points-abo.patch @@ -0,0 +1,45 @@ +From dddf7da3985ee144b46ee287e81e47b9ee8bb980 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Tue, 17 Mar 2020 23:25:52 +0100 +Subject: [Backport dddf7da3985e] exfat: Simplify exfat_utf8_d_hash() for code + points above U+FFFF +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Function partial_name_hash() takes long type value into which can be stored +one Unicode code point. Therefore conversion from UTF-32 to UTF-16 is not +needed. + +Signed-off-by: Pali Rohár +Signed-off-by: Namjae Jeon +--- + src/namei.c | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +diff --git a/src/namei.c b/src/namei.c +index 585b47b2db3da5d208fa1d53da9b18ae3d07c2dd..fa926b9c883adf353f3e911d96d6efcd71677dcc 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -147,16 +147,10 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr) + return charlen; + + /* +- * Convert to UTF-16: code points above U+FFFF are encoded as +- * surrogate pairs. + * exfat_toupper() works only for code points up to the U+FFFF. + */ +- if (u > 0xFFFF) { +- hash = partial_name_hash(exfat_high_surrogate(u), hash); +- hash = partial_name_hash(exfat_low_surrogate(u), hash); +- } else { +- hash = partial_name_hash(exfat_toupper(sb, u), hash); +- } ++ hash = partial_name_hash(u <= 0xFFFF ? exfat_toupper(sb, u) : u, ++ hash); + } + + qstr->hash = end_name_hash(hash); +-- +2.31.1 + diff --git a/SOURCES/0026-exfat-Remove-unused-functions-exfat_high_surrogate-a.patch b/SOURCES/0026-exfat-Remove-unused-functions-exfat_high_surrogate-a.patch new file mode 100644 index 0000000..8b947b4 --- /dev/null +++ b/SOURCES/0026-exfat-Remove-unused-functions-exfat_high_surrogate-a.patch @@ -0,0 +1,61 @@ +From 6778337a7a4e51ec9fa4a76846d34e8a66ca6418 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Tue, 17 Mar 2020 23:25:54 +0100 +Subject: [Backport 6778337a7a4e] exfat: Remove unused functions + exfat_high_surrogate() and exfat_low_surrogate() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +After applying previous two patches, these functions are not used anymore. + +Signed-off-by: Pali Rohár +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 2 -- + src/nls.c | 13 ------------- + 2 files changed, 15 deletions(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 1ebfb9085f1f719180051ee87aea94962accd238..3862df6af7386bd5e54ed338cedcee64c3cb6ddd 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -492,8 +492,6 @@ int exfat_nls_to_utf16(struct super_block *sb, + struct exfat_uni_name *uniname, int *p_lossy); + int exfat_create_upcase_table(struct super_block *sb); + void exfat_free_upcase_table(struct exfat_sb_info *sbi); +-unsigned short exfat_high_surrogate(unicode_t u); +-unsigned short exfat_low_surrogate(unicode_t u); + + /* exfat/misc.c */ + void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) +diff --git a/src/nls.c b/src/nls.c +index 2178786b708b577a385137994a41e078bedcb8b7..1ebda90cbdd7c81c507f662332a53eb52bf92606 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -535,22 +535,9 @@ static int exfat_utf8_to_utf16(struct super_block *sb, + return unilen; + } + +-#define PLANE_SIZE 0x00010000 + #define SURROGATE_MASK 0xfffff800 + #define SURROGATE_PAIR 0x0000d800 + #define SURROGATE_LOW 0x00000400 +-#define SURROGATE_BITS 0x000003ff +- +-unsigned short exfat_high_surrogate(unicode_t u) +-{ +- return ((u - PLANE_SIZE) >> 10) + SURROGATE_PAIR; +-} +- +-unsigned short exfat_low_surrogate(unicode_t u) +-{ +- return ((u - PLANE_SIZE) & SURROGATE_BITS) | SURROGATE_PAIR | +- SURROGATE_LOW; +-} + + static int __exfat_utf16_to_nls(struct super_block *sb, + struct exfat_uni_name *p_uniname, unsigned char *p_cstring, +-- +2.31.1 + diff --git a/SOURCES/0027-exfat-remove-the-assignment-of-0-to-bool-variable.patch b/SOURCES/0027-exfat-remove-the-assignment-of-0-to-bool-variable.patch new file mode 100644 index 0000000..f150c3e --- /dev/null +++ b/SOURCES/0027-exfat-remove-the-assignment-of-0-to-bool-variable.patch @@ -0,0 +1,33 @@ +From cdc06129a6cea0e4863fa2b34a0c132c6eb7278b Mon Sep 17 00:00:00 2001 +From: Jason Yan +Date: Thu, 16 Apr 2020 13:34:26 +0900 +Subject: [Backport cdc06129a6ce] exfat: remove the assignment of 0 to bool + variable + +There is no need to init 'sync' in exfat_set_vol_flags(). +This also fixes the following coccicheck warning: + +src/super.c:104:6-10: WARNING: Assignment of 0/1 to bool variable + +Signed-off-by: Jason Yan +Signed-off-by: Namjae Jeon +--- + src/super.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/super.c b/src/super.c +index f9aa1e5dc2381a4f9e4f9dc6f3d61dc525f1095c..c1f47f4071a80449d7fbbdf81d4f1d7079419d56 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -102,7 +102,7 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct pbr64 *bpb = (struct pbr64 *)sbi->pbr_bh->b_data; +- bool sync = 0; ++ bool sync; + + /* flags are not changed */ + if (sbi->vol_flag == new_flag) +-- +2.31.1 + diff --git a/SOURCES/0028-exfat-replace-time_ms-with-time_cs.patch b/SOURCES/0028-exfat-replace-time_ms-with-time_cs.patch new file mode 100644 index 0000000..b0c1597 --- /dev/null +++ b/SOURCES/0028-exfat-replace-time_ms-with-time_cs.patch @@ -0,0 +1,195 @@ +From ed0f84d30ba65f44bed2739572c7ab0fdeed4004 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Wed, 22 Apr 2020 08:30:56 +0900 +Subject: [Backport ed0f84d30ba6] exfat: replace 'time_ms' with 'time_cs' + +Replace time_ms with time_cs in the file directory entry structure +and related functions. + +The unit of create_time_ms/modify_time_ms in File Directory Entry are not +'milli-second', but 'centi-second'. +The exfat specification uses the term '10ms', but instead use 'cs' as in +msdos_fs.h. + +Signed-off-by: Tetsuhiro Kohada +Signed-off-by: Namjae Jeon +--- + src/dir.c | 8 ++++---- + src/exfat_fs.h | 4 ++-- + src/exfat_raw.h | 4 ++-- + src/file.c | 2 +- + src/inode.c | 4 ++-- + src/misc.c | 18 +++++++++--------- + src/namei.c | 4 ++-- + 7 files changed, 22 insertions(+), 22 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 53ae965da7ec583fb4e31cc548c40daf08ae941a..b5a237c33d50c02c632ec82627f78b4e70507d7a 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -137,12 +137,12 @@ static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) + ep->dentry.file.create_tz, + ep->dentry.file.create_time, + ep->dentry.file.create_date, +- ep->dentry.file.create_time_ms); ++ ep->dentry.file.create_time_cs); + exfat_get_entry_time(sbi, &dir_entry->mtime, + ep->dentry.file.modify_tz, + ep->dentry.file.modify_time, + ep->dentry.file.modify_date, +- ep->dentry.file.modify_time_ms); ++ ep->dentry.file.modify_time_cs); + exfat_get_entry_time(sbi, &dir_entry->atime, + ep->dentry.file.access_tz, + ep->dentry.file.access_time, +@@ -461,12 +461,12 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, + &ep->dentry.file.create_tz, + &ep->dentry.file.create_time, + &ep->dentry.file.create_date, +- &ep->dentry.file.create_time_ms); ++ &ep->dentry.file.create_time_cs); + 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_ms); ++ &ep->dentry.file.modify_time_cs); + exfat_set_entry_time(sbi, &ts, + &ep->dentry.file.access_tz, + &ep->dentry.file.access_time, +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 3862df6af7386bd5e54ed338cedcee64c3cb6ddd..294aa7792bc3ca6c7e334110cba9160577d9affe 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -511,10 +511,10 @@ void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) + exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) + + void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, +- u8 tz, __le16 time, __le16 date, u8 time_ms); ++ 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_ms); ++ u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); + unsigned short exfat_calc_chksum_2byte(void *data, int len, + unsigned short chksum, int type); + void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync); +diff --git a/src/exfat_raw.h b/src/exfat_raw.h +index 2a841010e6490ccafd5d12844a48a93fc6ee4bbf..8d6c64a7546d8bc8899e677bfd7f9d91f4e0cb4d 100644 +--- a/src/exfat_raw.h ++++ b/src/exfat_raw.h +@@ -136,8 +136,8 @@ struct exfat_dentry { + __le16 modify_date; + __le16 access_time; + __le16 access_date; +- __u8 create_time_ms; +- __u8 modify_time_ms; ++ __u8 create_time_cs; ++ __u8 modify_time_cs; + __u8 create_tz; + __u8 modify_tz; + __u8 access_tz; +diff --git a/src/file.c b/src/file.c +index c9db8eb0cfc3ee267d5e3b051974ceae8de861b5..84f3d31a3a555729b5d2e655f354929018b6b290 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -165,7 +165,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + &ep->dentry.file.modify_tz, + &ep->dentry.file.modify_time, + &ep->dentry.file.modify_date, +- &ep->dentry.file.modify_time_ms); ++ &ep->dentry.file.modify_time_cs); + ep->dentry.file.attr = cpu_to_le16(ei->attr); + + /* File size should be zero if there is no cluster allocated */ +diff --git a/src/inode.c b/src/inode.c +index 06887492f54b791506d2f7c76eb3ef073b72026d..3f367d081cd6dfe6c7cdb475d77e130b64d14d6d 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -56,12 +56,12 @@ static int __exfat_write_inode(struct inode *inode, int sync) + &ep->dentry.file.create_tz, + &ep->dentry.file.create_time, + &ep->dentry.file.create_date, +- &ep->dentry.file.create_time_ms); ++ &ep->dentry.file.create_time_cs); + exfat_set_entry_time(sbi, &inode->i_mtime, + &ep->dentry.file.modify_tz, + &ep->dentry.file.modify_time, + &ep->dentry.file.modify_date, +- &ep->dentry.file.modify_time_ms); ++ &ep->dentry.file.modify_time_cs); + exfat_set_entry_time(sbi, &inode->i_atime, + &ep->dentry.file.access_tz, + &ep->dentry.file.access_time, +diff --git a/src/misc.c b/src/misc.c +index ce5e8a1b0726c6778db86daebe39c437f23c7fba..ab7f88b1f6d3065cd3fca409526d50e833bbbfb0 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -75,7 +75,7 @@ static void exfat_adjust_tz(struct timespec64 *ts, u8 tz_off) + + /* Convert a EXFAT time/date pair to a UNIX date (seconds since 1 1 70). */ + void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, +- u8 tz, __le16 time, __le16 date, u8 time_ms) ++ u8 tz, __le16 time, __le16 date, u8 time_cs) + { + u16 t = le16_to_cpu(time); + u16 d = le16_to_cpu(date); +@@ -84,10 +84,10 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + t >> 11, (t >> 5) & 0x003F, (t & 0x001F) << 1); + + +- /* time_ms field represent 0 ~ 199(1990 ms) */ +- if (time_ms) { +- ts->tv_sec += time_ms / 100; +- ts->tv_nsec = (time_ms % 100) * 10 * NSEC_PER_MSEC; ++ /* time_cs field represent 0 ~ 199cs(1990 ms) */ ++ if (time_cs) { ++ ts->tv_sec += time_cs / 100; ++ ts->tv_nsec = (time_cs % 100) * 10 * NSEC_PER_MSEC; + } else + ts->tv_nsec = 0; + +@@ -101,7 +101,7 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + + /* Convert linear UNIX date to a EXFAT time/date pair. */ + void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, +- u8 *tz, __le16 *time, __le16 *date, u8 *time_ms) ++ u8 *tz, __le16 *time, __le16 *date, u8 *time_cs) + { + struct tm tm; + u16 t, d; +@@ -113,9 +113,9 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + *time = cpu_to_le16(t); + *date = cpu_to_le16(d); + +- /* time_ms field represent 0 ~ 199(1990 ms) */ +- if (time_ms) +- *time_ms = (tm.tm_sec & 1) * 100 + ++ /* time_cs field represent 0 ~ 199cs(1990 ms) */ ++ if (time_cs) ++ *time_cs = (tm.tm_sec & 1) * 100 + + ts->tv_nsec / (10 * NSEC_PER_MSEC); + + /* +diff --git a/src/namei.c b/src/namei.c +index fa926b9c883adf353f3e911d96d6efcd71677dcc..48f4df883f3ba5a30219f8f038a025530a8f8fa2 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -689,12 +689,12 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + ep->dentry.file.create_tz, + ep->dentry.file.create_time, + ep->dentry.file.create_date, +- ep->dentry.file.create_time_ms); ++ ep->dentry.file.create_time_cs); + exfat_get_entry_time(sbi, &info->mtime, + ep->dentry.file.modify_tz, + ep->dentry.file.modify_time, + ep->dentry.file.modify_date, +- ep->dentry.file.modify_time_ms); ++ ep->dentry.file.modify_time_cs); + exfat_get_entry_time(sbi, &info->atime, + ep->dentry.file.access_tz, + ep->dentry.file.access_time, +-- +2.31.1 + diff --git a/SOURCES/0029-exfat-optimize-dir-cache.patch b/SOURCES/0029-exfat-optimize-dir-cache.patch new file mode 100644 index 0000000..e5afffd --- /dev/null +++ b/SOURCES/0029-exfat-optimize-dir-cache.patch @@ -0,0 +1,580 @@ +From 943af1fdacfebe4ff13430655abc460a5e1072f7 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Wed, 20 May 2020 16:56:41 +0900 +Subject: [Backport 943af1fdacfe] exfat: optimize dir-cache + +Optimize directory access based on exfat_entry_set_cache. + - Hold bh instead of copied d-entry. + - Modify bh->data directly instead of the copied d-entry. + - Write back the retained bh instead of rescanning the d-entry-set. +And + - Remove unused cache related definitions. + +Signed-off-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 197 +++++++++++++++++--------------------------- + src/exfat_fs.h | 27 +++--- + src/file.c | 15 ++-- + src/inode.c | 53 +++++------- + src/namei.c | 14 ++-- + 5 files changed, 124 insertions(+), 182 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index b5a237c33d50c02c632ec82627f78b4e70507d7a..2902d285bf20412af3a57b0e96a6768e49d6c5ac 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -32,35 +32,30 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, + struct exfat_chain *p_dir, int entry, unsigned short *uniname) + { + int i; +- struct exfat_dentry *ep; + struct exfat_entry_set_cache *es; + +- es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES, &ep); ++ es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES); + if (!es) + return; + +- if (es->num_entries < 3) +- goto free_es; +- +- ep += 2; +- + /* + * First entry : file entry + * Second entry : stream-extension entry + * Third entry : first file-name entry + * So, the index of first file-name dentry should start from 2. + */ +- for (i = 2; i < es->num_entries; i++, ep++) { ++ for (i = 2; i < es->num_entries; i++) { ++ struct exfat_dentry *ep = exfat_get_dentry_cached(es, i); ++ + /* end of name entry */ + if (exfat_get_entry_type(ep) != TYPE_EXTEND) +- goto free_es; ++ break; + + exfat_extract_uni_name(ep, uniname); + uniname += EXFAT_FILE_NAME_LEN; + } + +-free_es: +- kfree(es); ++ exfat_free_dentry_set(es, false); + } + + /* read a directory entry from the opened directory */ +@@ -590,62 +585,33 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, + return 0; + } + +-int exfat_update_dir_chksum_with_entry_set(struct super_block *sb, +- struct exfat_entry_set_cache *es, int sync) ++void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) + { +- struct exfat_sb_info *sbi = EXFAT_SB(sb); +- struct buffer_head *bh; +- sector_t sec = es->sector; +- unsigned int off = es->offset; +- int chksum_type = CS_DIR_ENTRY, i, num_entries = es->num_entries; +- unsigned int buf_off = (off - es->offset); +- unsigned int remaining_byte_in_sector, copy_entries, clu; ++ int chksum_type = CS_DIR_ENTRY, i; + unsigned short chksum = 0; ++ struct exfat_dentry *ep; + +- for (i = 0; i < num_entries; i++) { +- chksum = exfat_calc_chksum_2byte(&es->entries[i], DENTRY_SIZE, +- chksum, chksum_type); ++ for (i = 0; i < es->num_entries; i++) { ++ ep = exfat_get_dentry_cached(es, i); ++ chksum = exfat_calc_chksum_2byte(ep, DENTRY_SIZE, chksum, ++ chksum_type); + chksum_type = CS_DEFAULT; + } ++ ep = exfat_get_dentry_cached(es, 0); ++ ep->dentry.file.checksum = cpu_to_le16(chksum); ++ es->modified = true; ++} + +- es->entries[0].dentry.file.checksum = cpu_to_le16(chksum); ++void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) ++{ ++ int i; + +- while (num_entries) { +- /* write per sector base */ +- remaining_byte_in_sector = (1 << sb->s_blocksize_bits) - off; +- copy_entries = min_t(int, +- EXFAT_B_TO_DEN(remaining_byte_in_sector), +- num_entries); +- bh = sb_bread(sb, sec); +- if (!bh) +- goto err_out; +- memcpy(bh->b_data + off, +- (unsigned char *)&es->entries[0] + buf_off, +- EXFAT_DEN_TO_B(copy_entries)); +- exfat_update_bh(sb, bh, sync); +- brelse(bh); +- num_entries -= copy_entries; +- +- if (num_entries) { +- /* get next sector */ +- if (exfat_is_last_sector_in_cluster(sbi, sec)) { +- clu = exfat_sector_to_cluster(sbi, sec); +- if (es->alloc_flag == ALLOC_NO_FAT_CHAIN) +- clu++; +- else if (exfat_get_next_cluster(sb, &clu)) +- goto err_out; +- sec = exfat_cluster_to_sector(sbi, clu); +- } else { +- sec++; +- } +- off = 0; +- buf_off += EXFAT_DEN_TO_B(copy_entries); +- } ++ for (i = 0; i < es->num_bh; i++) { ++ if (es->modified) ++ exfat_update_bh(es->sb, es->bh[i], sync); ++ brelse(es->bh[i]); + } +- +- return 0; +-err_out: +- return -EIO; ++ kfree(es); + } + + static int exfat_walk_fat_chain(struct super_block *sb, +@@ -820,34 +786,40 @@ static bool exfat_validate_entry(unsigned int type, + } + } + ++struct exfat_dentry *exfat_get_dentry_cached( ++ struct exfat_entry_set_cache *es, int num) ++{ ++ int off = es->start_off + num * DENTRY_SIZE; ++ struct buffer_head *bh = es->bh[EXFAT_B_TO_BLK(off, es->sb)]; ++ char *p = bh->b_data + EXFAT_BLK_OFFSET(off, es->sb); ++ ++ return (struct exfat_dentry *)p; ++} ++ + /* + * Returns a set of dentries for a file or dir. + * +- * Note that this is a copy (dump) of dentries so that user should +- * call write_entry_set() to apply changes made in this entry set +- * to the real device. ++ * Note It provides a direct pointer to bh->data via exfat_get_dentry_cached(). ++ * User should call exfat_get_dentry_set() after setting 'modified' to apply ++ * changes made in this entry set to the real device. + * + * in: + * sb+p_dir+entry: indicates a file/dir + * type: specifies how many dentries should be included. +- * out: +- * file_ep: will point the first dentry(= file dentry) on success + * return: + * pointer of entry set on success, + * NULL on failure. + */ + struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, +- struct exfat_chain *p_dir, int entry, unsigned int type, +- struct exfat_dentry **file_ep) ++ struct exfat_chain *p_dir, int entry, unsigned int type) + { +- int ret; ++ int ret, i, num_bh; + unsigned int off, byte_offset, clu = 0; +- unsigned int entry_type; + sector_t sec; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_entry_set_cache *es; +- struct exfat_dentry *ep, *pos; +- unsigned char num_entries; ++ struct exfat_dentry *ep; ++ int num_entries; + enum exfat_validate_dentry_mode mode = ES_MODE_STARTED; + struct buffer_head *bh; + +@@ -861,11 +833,18 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, + if (ret) + return NULL; + ++ es = kzalloc(sizeof(*es), GFP_KERNEL); ++ if (!es) ++ return NULL; ++ es->sb = sb; ++ es->modified = false; ++ + /* byte offset in cluster */ + byte_offset = EXFAT_CLU_OFFSET(byte_offset, sbi); + + /* byte offset in sector */ + off = EXFAT_BLK_OFFSET(byte_offset, sb); ++ es->start_off = off; + + /* sector offset in cluster */ + sec = EXFAT_B_TO_BLK(byte_offset, sb); +@@ -873,72 +852,46 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, + + bh = sb_bread(sb, sec); + if (!bh) +- return NULL; +- +- ep = (struct exfat_dentry *)(bh->b_data + off); +- entry_type = exfat_get_entry_type(ep); ++ goto free_es; ++ es->bh[es->num_bh++] = bh; + +- if (entry_type != TYPE_FILE && entry_type != TYPE_DIR) +- goto release_bh; ++ ep = exfat_get_dentry_cached(es, 0); ++ if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) ++ goto free_es; + + num_entries = type == ES_ALL_ENTRIES ? + ep->dentry.file.num_ext + 1 : type; +- es = kmalloc(struct_size(es, entries, num_entries), GFP_KERNEL); +- if (!es) +- goto release_bh; +- + es->num_entries = num_entries; +- es->sector = sec; +- es->offset = off; +- es->alloc_flag = p_dir->flags; +- +- pos = &es->entries[0]; +- +- while (num_entries) { +- if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) +- goto free_es; + +- /* copy dentry */ +- memcpy(pos, ep, sizeof(struct exfat_dentry)); +- +- if (--num_entries == 0) +- break; +- +- if (((off + DENTRY_SIZE) & (sb->s_blocksize - 1)) < +- (off & (sb->s_blocksize - 1))) { +- /* get the next sector */ +- if (exfat_is_last_sector_in_cluster(sbi, sec)) { +- if (es->alloc_flag == ALLOC_NO_FAT_CHAIN) +- clu++; +- else if (exfat_get_next_cluster(sb, &clu)) +- goto free_es; +- sec = exfat_cluster_to_sector(sbi, clu); +- } else { +- sec++; +- } +- +- brelse(bh); +- bh = sb_bread(sb, sec); +- if (!bh) ++ num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); ++ for (i = 1; i < num_bh; i++) { ++ /* get the next sector */ ++ if (exfat_is_last_sector_in_cluster(sbi, sec)) { ++ if (p_dir->flags == ALLOC_NO_FAT_CHAIN) ++ clu++; ++ else if (exfat_get_next_cluster(sb, &clu)) + goto free_es; +- off = 0; +- ep = (struct exfat_dentry *)bh->b_data; ++ sec = exfat_cluster_to_sector(sbi, clu); + } else { +- ep++; +- off += DENTRY_SIZE; ++ sec++; + } +- pos++; ++ ++ bh = sb_bread(sb, sec); ++ if (!bh) ++ goto free_es; ++ es->bh[es->num_bh++] = bh; + } + +- if (file_ep) +- *file_ep = &es->entries[0]; +- brelse(bh); ++ /* validiate 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)) ++ goto free_es; ++ } + return es; + + free_es: +- kfree(es); +-release_bh: +- brelse(bh); ++ exfat_free_dentry_set(es, false); + return NULL; + } + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 294aa7792bc3ca6c7e334110cba9160577d9affe..c84ae9e605085e0a5b19a2dbe8a9c36ab7efdce6 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -71,10 +71,8 @@ enum { + #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ + #define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH + 1) * MAX_CHARSET_SIZE) + +-#define FAT_CACHE_SIZE 128 +-#define FAT_CACHE_HASH_SIZE 64 +-#define BUF_CACHE_SIZE 256 +-#define BUF_CACHE_HASH_SIZE 64 ++/* Enough size to hold 256 dentry (even 512 Byte sector) */ ++#define DIR_CACHE_SIZE (256*sizeof(struct exfat_dentry)/512+1) + + #define EXFAT_HINT_NONE -1 + #define EXFAT_MIN_SUBDIR 2 +@@ -170,14 +168,12 @@ struct exfat_hint { + }; + + struct exfat_entry_set_cache { +- /* sector number that contains file_entry */ +- sector_t sector; +- /* byte offset in the sector */ +- unsigned int offset; +- /* flag in stream entry. 01 for cluster chain, 03 for contig. */ +- int alloc_flag; ++ struct super_block *sb; ++ bool modified; ++ unsigned int start_off; ++ int num_bh; ++ struct buffer_head *bh[DIR_CACHE_SIZE]; + unsigned int num_entries; +- struct exfat_dentry entries[]; + }; + + struct exfat_dir_entry { +@@ -451,8 +447,7 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, + int entry, int order, int num_entries); + int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, + int entry); +-int exfat_update_dir_chksum_with_entry_set(struct super_block *sb, +- struct exfat_entry_set_cache *es, int sync); ++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, +@@ -463,9 +458,11 @@ int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir, + 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_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, ++ int num); + struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, +- struct exfat_chain *p_dir, int entry, unsigned int type, +- struct exfat_dentry **file_ep); ++ struct exfat_chain *p_dir, int entry, unsigned int type); ++void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); + int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); + + /* inode.c */ +diff --git a/src/file.c b/src/file.c +index 84f3d31a3a555729b5d2e655f354929018b6b290..8e3f0eef45d77345e6f57ef58f3e86c51c69e905 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -96,11 +96,9 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + unsigned int num_clusters_new, num_clusters_phys; + unsigned int last_clu = EXFAT_FREE_CLUSTER; + struct exfat_chain clu; +- struct exfat_dentry *ep, *ep2; + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *ei = EXFAT_I(inode); +- struct exfat_entry_set_cache *es = NULL; + int evict = (ei->dir.dir == DIR_DELETED) ? 1 : 0; + + /* check if the given file ID is opened */ +@@ -153,12 +151,15 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + /* update the directory entry */ + if (!evict) { + struct timespec64 ts; ++ struct exfat_dentry *ep, *ep2; ++ struct exfat_entry_set_cache *es; + + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, +- ES_ALL_ENTRIES, &ep); ++ ES_ALL_ENTRIES); + if (!es) + return -EIO; +- ep2 = ep + 1; ++ ep = exfat_get_dentry_cached(es, 0); ++ ep2 = exfat_get_dentry_cached(es, 1); + + ts = current_time(inode); + exfat_set_entry_time(sbi, &ts, +@@ -185,10 +186,8 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; + } + +- if (exfat_update_dir_chksum_with_entry_set(sb, es, +- inode_needs_sync(inode))) +- return -EIO; +- kfree(es); ++ exfat_update_dir_chksum_with_entry_set(es); ++ exfat_free_dentry_set(es, inode_needs_sync(inode)); + } + + /* cut off from the FAT chain */ +diff --git a/src/inode.c b/src/inode.c +index 3f367d081cd6dfe6c7cdb475d77e130b64d14d6d..ef7cf7a6d187b8d806383ba7b94daa06342edd22 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -19,7 +19,6 @@ + + static int __exfat_write_inode(struct inode *inode, int sync) + { +- int ret = -EIO; + unsigned long long on_disk_size; + struct exfat_dentry *ep, *ep2; + struct exfat_entry_set_cache *es = NULL; +@@ -43,11 +42,11 @@ static int __exfat_write_inode(struct inode *inode, int sync) + exfat_set_vol_flags(sb, VOL_DIRTY); + + /* get the directory entry of given file or directory */ +- es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES, +- &ep); ++ es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES); + if (!es) + return -EIO; +- ep2 = ep + 1; ++ ep = exfat_get_dentry_cached(es, 0); ++ ep2 = exfat_get_dentry_cached(es, 1); + + ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); + +@@ -77,9 +76,9 @@ static int __exfat_write_inode(struct inode *inode, int sync) + ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size); + ep2->dentry.stream.size = ep2->dentry.stream.valid_size; + +- ret = exfat_update_dir_chksum_with_entry_set(sb, es, sync); +- kfree(es); +- return ret; ++ exfat_update_dir_chksum_with_entry_set(es); ++ exfat_free_dentry_set(es, sync); ++ return 0; + } + + int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) +@@ -110,8 +109,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, + int ret, modified = false; + unsigned int last_clu; + struct exfat_chain new_clu; +- struct exfat_dentry *ep; +- struct exfat_entry_set_cache *es = NULL; + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *ei = EXFAT_I(inode); +@@ -222,34 +219,28 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, + num_clusters += num_to_be_allocated; + *clu = new_clu.dir; + +- if (ei->dir.dir != DIR_DELETED) { ++ if (ei->dir.dir != DIR_DELETED && modified) { ++ struct exfat_dentry *ep; ++ struct exfat_entry_set_cache *es; ++ + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, +- ES_ALL_ENTRIES, &ep); ++ ES_ALL_ENTRIES); + if (!es) + return -EIO; + /* get stream entry */ +- ep++; ++ ep = exfat_get_dentry_cached(es, 1); + + /* update directory entry */ +- if (modified) { +- if (ep->dentry.stream.flags != ei->flags) +- ep->dentry.stream.flags = ei->flags; +- +- if (le32_to_cpu(ep->dentry.stream.start_clu) != +- ei->start_clu) +- ep->dentry.stream.start_clu = +- cpu_to_le32(ei->start_clu); +- +- ep->dentry.stream.valid_size = +- cpu_to_le64(i_size_read(inode)); +- ep->dentry.stream.size = +- ep->dentry.stream.valid_size; +- } +- +- if (exfat_update_dir_chksum_with_entry_set(sb, es, +- inode_needs_sync(inode))) +- return -EIO; +- kfree(es); ++ ep->dentry.stream.flags = ei->flags; ++ ep->dentry.stream.start_clu = ++ cpu_to_le32(ei->start_clu); ++ ep->dentry.stream.valid_size = ++ cpu_to_le64(i_size_read(inode)); ++ ep->dentry.stream.size = ++ ep->dentry.stream.valid_size; ++ ++ exfat_update_dir_chksum_with_entry_set(es); ++ exfat_free_dentry_set(es, inode_needs_sync(inode)); + + } /* end of if != DIR_DELETED */ + +diff --git a/src/namei.c b/src/namei.c +index 48f4df883f3ba5a30219f8f038a025530a8f8fa2..5b0f35329d63e0b9ab52162e4fbad129601c458d 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -600,8 +600,6 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + int ret, dentry, num_entries, count; + struct exfat_chain cdir; + struct exfat_uni_name uni_name; +- struct exfat_dentry *ep, *ep2; +- struct exfat_entry_set_cache *es = NULL; + struct super_block *sb = dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *ei = EXFAT_I(dir); +@@ -660,10 +658,14 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + + info->num_subdirs = count; + } else { +- es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES, &ep); ++ struct exfat_dentry *ep, *ep2; ++ struct exfat_entry_set_cache *es; ++ ++ es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); + if (!es) + return -EIO; +- ep2 = ep + 1; ++ ep = exfat_get_dentry_cached(es, 0); ++ ep2 = exfat_get_dentry_cached(es, 1); + + info->type = exfat_get_entry_type(ep); + info->attr = le16_to_cpu(ep->dentry.file.attr); +@@ -681,7 +683,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + exfat_fs_error(sb, + "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", + i_size_read(dir), ei->dir.dir, ei->entry); +- kfree(es); ++ exfat_free_dentry_set(es, false); + return -EIO; + } + +@@ -700,7 +702,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + ep->dentry.file.access_time, + ep->dentry.file.access_date, + 0); +- kfree(es); ++ exfat_free_dentry_set(es, false); + + if (info->type == TYPE_DIR) { + exfat_chain_set(&cdir, info->start_clu, +-- +2.31.1 + diff --git a/SOURCES/0030-exfat-redefine-PBR-as-boot_sector.patch b/SOURCES/0030-exfat-redefine-PBR-as-boot_sector.patch new file mode 100644 index 0000000..4cd1fb5 --- /dev/null +++ b/SOURCES/0030-exfat-redefine-PBR-as-boot_sector.patch @@ -0,0 +1,355 @@ +From 181a9e8009a8a8bdb19c2e24eeeae7d8e77c8c47 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Fri, 29 May 2020 19:14:56 +0900 +Subject: [Backport 181a9e8009a8] exfat: redefine PBR as boot_sector + +Aggregate PBR related definitions and redefine as "boot_sector" to comply +with the exFAT specification. +And, rename variable names including 'pbr'. + +Signed-off-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 2 +- + src/exfat_raw.h | 79 +++++++++++++++-------------------------- + src/super.c | 84 ++++++++++++++++++++++---------------------- + 3 files changed, 72 insertions(+), 93 deletions(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index c84ae9e605085e0a5b19a2dbe8a9c36ab7efdce6..911f58b93f3d257bce04b645a2ada62d0f51a3c0 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -227,7 +227,7 @@ struct exfat_sb_info { + unsigned int root_dir; /* root dir cluster */ + unsigned int dentries_per_clu; /* num of dentries per cluster */ + unsigned int vol_flag; /* volume dirty flag */ +- struct buffer_head *pbr_bh; /* buffer_head of PBR sector */ ++ struct buffer_head *boot_bh; /* buffer_head of BOOT sector */ + + unsigned int map_clu; /* allocation bitmap start cluster */ + unsigned int map_sectors; /* num of allocation bitmap sectors */ +diff --git a/src/exfat_raw.h b/src/exfat_raw.h +index 8d6c64a7546d8bc8899e677bfd7f9d91f4e0cb4d..07f74190df44b215128d4dcf2a8ee33d32a7cf21 100644 +--- a/src/exfat_raw.h ++++ b/src/exfat_raw.h +@@ -8,7 +8,8 @@ + + #include + +-#define PBR_SIGNATURE 0xAA55 ++#define BOOT_SIGNATURE 0xAA55 ++#define EXBOOT_SIGNATURE 0xAA550000 + + #define EXFAT_MAX_FILE_LEN 255 + +@@ -55,7 +56,7 @@ + + /* checksum types */ + #define CS_DIR_ENTRY 0 +-#define CS_PBR_SECTOR 1 ++#define CS_BOOT_SECTOR 1 + #define CS_DEFAULT 2 + + /* file attributes */ +@@ -69,57 +70,35 @@ + #define ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ + ATTR_SUBDIR | ATTR_ARCHIVE) + +-#define PBR64_JUMP_BOOT_LEN 3 +-#define PBR64_OEM_NAME_LEN 8 +-#define PBR64_RESERVED_LEN 53 ++#define BOOTSEC_JUMP_BOOT_LEN 3 ++#define BOOTSEC_FS_NAME_LEN 8 ++#define BOOTSEC_OLDBPB_LEN 53 + + #define EXFAT_FILE_NAME_LEN 15 + +-/* EXFAT BIOS parameter block (64 bytes) */ +-struct bpb64 { +- __u8 jmp_boot[PBR64_JUMP_BOOT_LEN]; +- __u8 oem_name[PBR64_OEM_NAME_LEN]; +- __u8 res_zero[PBR64_RESERVED_LEN]; +-} __packed; +- +-/* EXFAT EXTEND BIOS parameter block (56 bytes) */ +-struct bsx64 { +- __le64 vol_offset; +- __le64 vol_length; +- __le32 fat_offset; +- __le32 fat_length; +- __le32 clu_offset; +- __le32 clu_count; +- __le32 root_cluster; +- __le32 vol_serial; +- __u8 fs_version[2]; +- __le16 vol_flags; +- __u8 sect_size_bits; +- __u8 sect_per_clus_bits; +- __u8 num_fats; +- __u8 phy_drv_no; +- __u8 perc_in_use; +- __u8 reserved2[7]; +-} __packed; +- +-/* EXFAT PBR[BPB+BSX] (120 bytes) */ +-struct pbr64 { +- struct bpb64 bpb; +- struct bsx64 bsx; +-} __packed; +- +-/* Common PBR[Partition Boot Record] (512 bytes) */ +-struct pbr { +- union { +- __u8 raw[64]; +- struct bpb64 f64; +- } bpb; +- union { +- __u8 raw[56]; +- struct bsx64 f64; +- } bsx; +- __u8 boot_code[390]; +- __le16 signature; ++/* EXFAT: Main and Backup Boot Sector (512 bytes) */ ++struct boot_sector { ++ __u8 jmp_boot[BOOTSEC_JUMP_BOOT_LEN]; ++ __u8 fs_name[BOOTSEC_FS_NAME_LEN]; ++ __u8 must_be_zero[BOOTSEC_OLDBPB_LEN]; ++ __le64 partition_offset; ++ __le64 vol_length; ++ __le32 fat_offset; ++ __le32 fat_length; ++ __le32 clu_offset; ++ __le32 clu_count; ++ __le32 root_cluster; ++ __le32 vol_serial; ++ __u8 fs_revision[2]; ++ __le16 vol_flags; ++ __u8 sect_size_bits; ++ __u8 sect_per_clus_bits; ++ __u8 num_fats; ++ __u8 drv_sel; ++ __u8 percent_in_use; ++ __u8 reserved[7]; ++ __u8 boot_code[390]; ++ __le16 signature; + } __packed; + + struct exfat_dentry { +diff --git a/src/super.c b/src/super.c +index c1f47f4071a80449d7fbbdf81d4f1d7079419d56..e60d28e73ff007b85eafc751693d332911fbcfd3 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -49,7 +49,7 @@ static void exfat_put_super(struct super_block *sb) + sync_blockdev(sb->s_bdev); + exfat_set_vol_flags(sb, VOL_CLEAN); + exfat_free_bitmap(sbi); +- brelse(sbi->pbr_bh); ++ brelse(sbi->boot_bh); + mutex_unlock(&sbi->s_lock); + + call_rcu(&sbi->rcu, exfat_delayed_free); +@@ -101,7 +101,7 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) + int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); +- struct pbr64 *bpb = (struct pbr64 *)sbi->pbr_bh->b_data; ++ struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; + bool sync; + + /* flags are not changed */ +@@ -116,18 +116,18 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) + if (sb_rdonly(sb)) + return 0; + +- bpb->bsx.vol_flags = cpu_to_le16(new_flag); ++ p_boot->vol_flags = cpu_to_le16(new_flag); + +- if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->pbr_bh)) ++ if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->boot_bh)) + sync = true; + else + sync = false; + +- set_buffer_uptodate(sbi->pbr_bh); +- mark_buffer_dirty(sbi->pbr_bh); ++ set_buffer_uptodate(sbi->boot_bh); ++ mark_buffer_dirty(sbi->boot_bh); + + if (sync) +- sync_dirty_buffer(sbi->pbr_bh); ++ sync_dirty_buffer(sbi->boot_bh); + return 0; + } + +@@ -366,13 +366,14 @@ static int exfat_read_root(struct inode *inode) + return 0; + } + +-static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) ++static struct boot_sector *exfat_read_boot_with_logical_sector( ++ struct super_block *sb) + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); +- struct pbr *p_pbr = (struct pbr *) (sbi->pbr_bh)->b_data; ++ struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; + unsigned short logical_sect = 0; + +- logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; ++ logical_sect = 1 << p_boot->sect_size_bits; + + if (!is_power_of_2(logical_sect) || + logical_sect < 512 || logical_sect > 4096) { +@@ -387,49 +388,48 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) + } + + if (logical_sect > sb->s_blocksize) { +- brelse(sbi->pbr_bh); +- sbi->pbr_bh = NULL; ++ brelse(sbi->boot_bh); ++ sbi->boot_bh = NULL; + + if (!sb_set_blocksize(sb, logical_sect)) { + exfat_err(sb, "unable to set blocksize %u", + logical_sect); + return NULL; + } +- sbi->pbr_bh = sb_bread(sb, 0); +- if (!sbi->pbr_bh) { ++ sbi->boot_bh = sb_bread(sb, 0); ++ if (!sbi->boot_bh) { + exfat_err(sb, "unable to read boot sector (logical sector size = %lu)", + sb->s_blocksize); + return NULL; + } + +- p_pbr = (struct pbr *)sbi->pbr_bh->b_data; ++ p_boot = (struct boot_sector *)sbi->boot_bh->b_data; + } +- return p_pbr; ++ return p_boot; + } + + /* mount the file system volume */ + static int __exfat_fill_super(struct super_block *sb) + { + int ret; +- struct pbr *p_pbr; +- struct pbr64 *p_bpb; ++ struct boot_sector *p_boot; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + /* set block size to read super block */ + sb_min_blocksize(sb, 512); + + /* read boot sector */ +- sbi->pbr_bh = sb_bread(sb, 0); +- if (!sbi->pbr_bh) { ++ sbi->boot_bh = sb_bread(sb, 0); ++ if (!sbi->boot_bh) { + exfat_err(sb, "unable to read boot sector"); + return -EIO; + } + + /* PRB is read */ +- p_pbr = (struct pbr *)sbi->pbr_bh->b_data; ++ p_boot = (struct boot_sector *)sbi->boot_bh->b_data; + +- /* check the validity of PBR */ +- if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { ++ /* check the validity of BOOT */ ++ if (le16_to_cpu((p_boot->signature)) != BOOT_SIGNATURE) { + exfat_err(sb, "invalid boot record signature"); + ret = -EINVAL; + goto free_bh; +@@ -437,8 +437,8 @@ static int __exfat_fill_super(struct super_block *sb) + + + /* check logical sector size */ +- p_pbr = exfat_read_pbr_with_logical_sector(sb); +- if (!p_pbr) { ++ p_boot = exfat_read_boot_with_logical_sector(sb); ++ if (!p_boot) { + ret = -EIO; + goto free_bh; + } +@@ -447,43 +447,43 @@ static int __exfat_fill_super(struct super_block *sb) + * res_zero field must be filled with zero to prevent mounting + * from FAT volume. + */ +- if (memchr_inv(p_pbr->bpb.f64.res_zero, 0, +- sizeof(p_pbr->bpb.f64.res_zero))) { ++ if (memchr_inv(p_boot->must_be_zero, 0, ++ sizeof(p_boot->must_be_zero))) { + ret = -EINVAL; + goto free_bh; + } + +- p_bpb = (struct pbr64 *)p_pbr; +- if (!p_bpb->bsx.num_fats) { ++ p_boot = (struct boot_sector *)p_boot; ++ if (!p_boot->num_fats) { + exfat_err(sb, "bogus number of FAT structure"); + ret = -EINVAL; + goto free_bh; + } + +- sbi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits; +- sbi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits; ++ 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 = sbi->sect_per_clus_bits + sb->s_blocksize_bits; + sbi->cluster_size = 1 << sbi->cluster_size_bits; +- sbi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length); +- sbi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset); +- sbi->FAT2_start_sector = p_bpb->bsx.num_fats == 1 ? ++ sbi->num_FAT_sectors = le32_to_cpu(p_boot->fat_length); ++ sbi->FAT1_start_sector = le32_to_cpu(p_boot->fat_offset); ++ sbi->FAT2_start_sector = p_boot->num_fats == 1 ? + sbi->FAT1_start_sector : + sbi->FAT1_start_sector + sbi->num_FAT_sectors; +- sbi->data_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset); +- sbi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length); ++ sbi->data_start_sector = le32_to_cpu(p_boot->clu_offset); ++ sbi->num_sectors = le64_to_cpu(p_boot->vol_length); + /* because the cluster index starts with 2 */ +- sbi->num_clusters = le32_to_cpu(p_bpb->bsx.clu_count) + ++ sbi->num_clusters = le32_to_cpu(p_boot->clu_count) + + EXFAT_RESERVED_CLUSTERS; + +- sbi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster); ++ sbi->root_dir = le32_to_cpu(p_boot->root_cluster); + sbi->dentries_per_clu = 1 << + (sbi->cluster_size_bits - DENTRY_SIZE_BITS); + +- sbi->vol_flag = le16_to_cpu(p_bpb->bsx.vol_flags); ++ sbi->vol_flag = le16_to_cpu(p_boot->vol_flags); + sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; + sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; + +- if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) { ++ if (le16_to_cpu(p_boot->vol_flags) & VOL_DIRTY) { + sbi->vol_flag |= VOL_DIRTY; + exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); + } +@@ -517,7 +517,7 @@ static int __exfat_fill_super(struct super_block *sb) + free_upcase_table: + exfat_free_upcase_table(sbi); + free_bh: +- brelse(sbi->pbr_bh); ++ brelse(sbi->boot_bh); + return ret; + } + +@@ -608,7 +608,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc) + free_table: + exfat_free_upcase_table(sbi); + exfat_free_bitmap(sbi); +- brelse(sbi->pbr_bh); ++ brelse(sbi->boot_bh); + + check_nls_io: + unload_nls(sbi->nls_io); +-- +2.31.1 + diff --git a/SOURCES/0031-exfat-separate-the-boot-sector-analysis.patch b/SOURCES/0031-exfat-separate-the-boot-sector-analysis.patch new file mode 100644 index 0000000..8664ae2 --- /dev/null +++ b/SOURCES/0031-exfat-separate-the-boot-sector-analysis.patch @@ -0,0 +1,216 @@ +From 33404a159828a97fefada0d8f4cf910873b8e9f1 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Fri, 29 May 2020 19:14:57 +0900 +Subject: [Backport 33404a159828] exfat: separate the boot sector analysis + +Separate the boot sector analysis to read_boot_sector(). +And add a check for the fs_name field. +Furthermore, add a strict consistency check, because overlapping areas +can cause serious corruption. + +Signed-off-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/exfat_raw.h | 2 + + src/super.c | 97 ++++++++++++++++++++++++-------------------- + 2 files changed, 56 insertions(+), 43 deletions(-) + +diff --git a/src/exfat_raw.h b/src/exfat_raw.h +index 07f74190df44b215128d4dcf2a8ee33d32a7cf21..350ce59cc32478919d9ffb9823726b8b9015de48 100644 +--- a/src/exfat_raw.h ++++ b/src/exfat_raw.h +@@ -10,11 +10,13 @@ + + #define BOOT_SIGNATURE 0xAA55 + #define EXBOOT_SIGNATURE 0xAA550000 ++#define STR_EXFAT "EXFAT " /* size should be 8 */ + + #define EXFAT_MAX_FILE_LEN 255 + + #define VOL_CLEAN 0x0000 + #define VOL_DIRTY 0x0002 ++#define ERR_MEDIUM 0x0004 + + #define EXFAT_EOF_CLUSTER 0xFFFFFFFFu + #define EXFAT_BAD_CLUSTER 0xFFFFFFF7u +diff --git a/src/super.c b/src/super.c +index e60d28e73ff007b85eafc751693d332911fbcfd3..6a1330be5a9a4b58e2a39b197788f139bfbaba4f 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -366,25 +366,20 @@ static int exfat_read_root(struct inode *inode) + return 0; + } + +-static struct boot_sector *exfat_read_boot_with_logical_sector( +- struct super_block *sb) ++static int exfat_calibrate_blocksize(struct super_block *sb, int logical_sect) + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); +- struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; +- unsigned short logical_sect = 0; +- +- logical_sect = 1 << p_boot->sect_size_bits; + + if (!is_power_of_2(logical_sect) || + logical_sect < 512 || logical_sect > 4096) { + exfat_err(sb, "bogus logical sector size %u", logical_sect); +- return NULL; ++ return -EIO; + } + + if (logical_sect < sb->s_blocksize) { + exfat_err(sb, "logical sector size too small for device (logical sector size = %u)", + logical_sect); +- return NULL; ++ return -EIO; + } + + if (logical_sect > sb->s_blocksize) { +@@ -394,24 +389,20 @@ static struct boot_sector *exfat_read_boot_with_logical_sector( + if (!sb_set_blocksize(sb, logical_sect)) { + exfat_err(sb, "unable to set blocksize %u", + logical_sect); +- return NULL; ++ return -EIO; + } + sbi->boot_bh = sb_bread(sb, 0); + if (!sbi->boot_bh) { + exfat_err(sb, "unable to read boot sector (logical sector size = %lu)", + sb->s_blocksize); +- return NULL; ++ return -EIO; + } +- +- p_boot = (struct boot_sector *)sbi->boot_bh->b_data; + } +- return p_boot; ++ return 0; + } + +-/* mount the file system volume */ +-static int __exfat_fill_super(struct super_block *sb) ++static int exfat_read_boot_sector(struct super_block *sb) + { +- int ret; + struct boot_sector *p_boot; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + +@@ -424,51 +415,41 @@ static int __exfat_fill_super(struct super_block *sb) + exfat_err(sb, "unable to read boot sector"); + return -EIO; + } +- +- /* PRB is read */ + p_boot = (struct boot_sector *)sbi->boot_bh->b_data; + + /* check the validity of BOOT */ + if (le16_to_cpu((p_boot->signature)) != BOOT_SIGNATURE) { + exfat_err(sb, "invalid boot record signature"); +- ret = -EINVAL; +- goto free_bh; ++ return -EINVAL; + } + +- +- /* check logical sector size */ +- p_boot = exfat_read_boot_with_logical_sector(sb); +- if (!p_boot) { +- ret = -EIO; +- goto free_bh; ++ if (memcmp(p_boot->fs_name, STR_EXFAT, BOOTSEC_FS_NAME_LEN)) { ++ exfat_err(sb, "invalid fs_name"); /* fs_name may unprintable */ ++ return -EINVAL; + } + + /* +- * res_zero field must be filled with zero to prevent mounting ++ * must_be_zero field must be filled with zero to prevent mounting + * from FAT volume. + */ +- if (memchr_inv(p_boot->must_be_zero, 0, +- sizeof(p_boot->must_be_zero))) { +- ret = -EINVAL; +- goto free_bh; +- } ++ if (memchr_inv(p_boot->must_be_zero, 0, sizeof(p_boot->must_be_zero))) ++ return -EINVAL; + +- p_boot = (struct boot_sector *)p_boot; +- if (!p_boot->num_fats) { ++ if (p_boot->num_fats != 1 && p_boot->num_fats != 2) { + exfat_err(sb, "bogus number of FAT structure"); +- ret = -EINVAL; +- goto free_bh; ++ 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 = sbi->sect_per_clus_bits + sb->s_blocksize_bits; ++ sbi->cluster_size_bits = p_boot->sect_per_clus_bits + ++ p_boot->sect_size_bits; + sbi->cluster_size = 1 << sbi->cluster_size_bits; + sbi->num_FAT_sectors = le32_to_cpu(p_boot->fat_length); + sbi->FAT1_start_sector = le32_to_cpu(p_boot->fat_offset); +- sbi->FAT2_start_sector = p_boot->num_fats == 1 ? +- sbi->FAT1_start_sector : +- sbi->FAT1_start_sector + sbi->num_FAT_sectors; ++ sbi->FAT2_start_sector = le32_to_cpu(p_boot->fat_offset); ++ if (p_boot->num_fats == 2) ++ sbi->FAT2_start_sector += sbi->num_FAT_sectors; + sbi->data_start_sector = le32_to_cpu(p_boot->clu_offset); + sbi->num_sectors = le64_to_cpu(p_boot->vol_length); + /* because the cluster index starts with 2 */ +@@ -483,15 +464,45 @@ static int __exfat_fill_super(struct super_block *sb) + sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; + sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; + +- if (le16_to_cpu(p_boot->vol_flags) & VOL_DIRTY) { +- sbi->vol_flag |= VOL_DIRTY; +- exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); ++ /* check consistencies */ ++ if (sbi->num_FAT_sectors << p_boot->sect_size_bits < ++ 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) { ++ exfat_err(sb, "bogus data start sector"); ++ return -EINVAL; + } ++ if (sbi->vol_flag & VOL_DIRTY) ++ exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); ++ if (sbi->vol_flag & ERR_MEDIUM) ++ exfat_warn(sb, "Medium has reported failures. Some data may be lost."); + + /* exFAT file size is limited by a disk volume size */ + sb->s_maxbytes = (u64)(sbi->num_clusters - EXFAT_RESERVED_CLUSTERS) << + sbi->cluster_size_bits; + ++ /* check logical sector size */ ++ if (exfat_calibrate_blocksize(sb, 1 << p_boot->sect_size_bits)) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* mount the file system volume */ ++static int __exfat_fill_super(struct super_block *sb) ++{ ++ int ret; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ ret = exfat_read_boot_sector(sb); ++ if (ret) { ++ exfat_err(sb, "failed to read boot sector"); ++ goto free_bh; ++ } ++ + ret = exfat_create_upcase_table(sb); + if (ret) { + exfat_err(sb, "failed to load upcase table"); +-- +2.31.1 + diff --git a/SOURCES/0032-exfat-add-boot-region-verification.patch b/SOURCES/0032-exfat-add-boot-region-verification.patch new file mode 100644 index 0000000..bf58c52 --- /dev/null +++ b/SOURCES/0032-exfat-add-boot-region-verification.patch @@ -0,0 +1,126 @@ +From 476189c0ef3b658de3f6b89fd0fdeb6dc451b564 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Sun, 31 May 2020 18:30:17 +0900 +Subject: [Backport 476189c0ef3b] exfat: add boot region verification + +Add Boot-Regions verification specified in exFAT specification. +Note that the checksum type is strongly related to the raw structure, +so the'u32 'type is used to clarify the number of bits. + +Signed-off-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 1 + + src/misc.c | 14 +++++++++++++ + src/super.c | 50 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 65 insertions(+) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 911f58b93f3d257bce04b645a2ada62d0f51a3c0..8c2a70bfaa10d4743c46aad74254e363d4079c74 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -514,6 +514,7 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); + unsigned short exfat_calc_chksum_2byte(void *data, int len, + unsigned short chksum, int type); ++u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type); + void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync); + void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, + unsigned int size, unsigned char flags); +diff --git a/src/misc.c b/src/misc.c +index ab7f88b1f6d3065cd3fca409526d50e833bbbfb0..b82d2dd5bd7cb291ffe5ed5091c9214593c32a69 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -151,6 +151,20 @@ unsigned short exfat_calc_chksum_2byte(void *data, int len, + return chksum; + } + ++u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type) ++{ ++ int i; ++ u8 *c = (u8 *)data; ++ ++ for (i = 0; i < len; i++, c++) { ++ if (unlikely(type == CS_BOOT_SECTOR && ++ (i == 106 || i == 107 || i == 112))) ++ continue; ++ chksum = ((chksum << 31) | (chksum >> 1)) + *c; ++ } ++ return chksum; ++} ++ + void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync) + { + set_bit(EXFAT_SB_DIRTY, &EXFAT_SB(sb)->s_state); +diff --git a/src/super.c b/src/super.c +index 6a1330be5a9a4b58e2a39b197788f139bfbaba4f..405717e4e3eac6b3233e1e57b0bcc70f56f1c42f 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -491,6 +491,50 @@ static int exfat_read_boot_sector(struct super_block *sb) + return 0; + } + ++static int exfat_verify_boot_region(struct super_block *sb) ++{ ++ struct buffer_head *bh = NULL; ++ u32 chksum = 0; ++ __le32 *p_sig, *p_chksum; ++ int sn, i; ++ ++ /* read boot sector sub-regions */ ++ for (sn = 0; sn < 11; sn++) { ++ bh = sb_bread(sb, sn); ++ if (!bh) ++ return -EIO; ++ ++ if (sn != 0 && sn <= 8) { ++ /* extended boot sector sub-regions */ ++ p_sig = (__le32 *)&bh->b_data[sb->s_blocksize - 4]; ++ if (le32_to_cpu(*p_sig) != EXBOOT_SIGNATURE) ++ exfat_warn(sb, "Invalid exboot-signature(sector = %d): 0x%08x", ++ sn, le32_to_cpu(*p_sig)); ++ } ++ ++ chksum = exfat_calc_chksum32(bh->b_data, sb->s_blocksize, ++ chksum, sn ? CS_DEFAULT : CS_BOOT_SECTOR); ++ brelse(bh); ++ } ++ ++ /* boot checksum sub-regions */ ++ bh = sb_bread(sb, sn); ++ if (!bh) ++ return -EIO; ++ ++ for (i = 0; i < sb->s_blocksize; i += sizeof(u32)) { ++ p_chksum = (__le32 *)&bh->b_data[i]; ++ if (le32_to_cpu(*p_chksum) != chksum) { ++ exfat_err(sb, "Invalid boot checksum (boot checksum : 0x%08x, checksum : 0x%08x)", ++ le32_to_cpu(*p_chksum), chksum); ++ brelse(bh); ++ return -EINVAL; ++ } ++ } ++ brelse(bh); ++ return 0; ++} ++ + /* mount the file system volume */ + static int __exfat_fill_super(struct super_block *sb) + { +@@ -503,6 +547,12 @@ static int __exfat_fill_super(struct super_block *sb) + goto free_bh; + } + ++ ret = exfat_verify_boot_region(sb); ++ if (ret) { ++ exfat_err(sb, "invalid boot region"); ++ goto free_bh; ++ } ++ + ret = exfat_create_upcase_table(sb); + if (ret) { + exfat_err(sb, "failed to load upcase table"); +-- +2.31.1 + diff --git a/SOURCES/0033-exfat-standardize-checksum-calculation.patch b/SOURCES/0033-exfat-standardize-checksum-calculation.patch new file mode 100644 index 0000000..1cbe3d8 --- /dev/null +++ b/SOURCES/0033-exfat-standardize-checksum-calculation.patch @@ -0,0 +1,186 @@ +From 5875bf287d95314c58add01184f361cc5aa38429 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Fri, 29 May 2020 19:14:59 +0900 +Subject: [Backport 5875bf287d95] exfat: standardize checksum calculation + +To clarify that it is a 16-bit checksum, the parts related to the 16-bit +checksum are renamed and change type to u16. +Furthermore, replace checksum calculation in exfat_load_upcase_table() +with exfat_calc_checksum32(). + +Signed-off-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 12 ++++++------ + src/exfat_fs.h | 5 ++--- + src/misc.c | 10 ++++------ + src/nls.c | 19 +++++++------------ + 4 files changed, 19 insertions(+), 27 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 2902d285bf20412af3a57b0e96a6768e49d6c5ac..de43534aa2997aa87994a9447f95578de9931258 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -491,7 +491,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, + int ret = 0; + int i, num_entries; + sector_t sector; +- unsigned short chksum; ++ u16 chksum; + struct exfat_dentry *ep, *fep; + struct buffer_head *fbh, *bh; + +@@ -500,7 +500,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, + return -EIO; + + num_entries = fep->dentry.file.num_ext + 1; +- chksum = exfat_calc_chksum_2byte(fep, DENTRY_SIZE, 0, CS_DIR_ENTRY); ++ 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); +@@ -508,7 +508,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, + ret = -EIO; + goto release_fbh; + } +- chksum = exfat_calc_chksum_2byte(ep, DENTRY_SIZE, chksum, ++ chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, + CS_DEFAULT); + brelse(bh); + } +@@ -593,8 +593,8 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) + + for (i = 0; i < es->num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); +- chksum = exfat_calc_chksum_2byte(ep, DENTRY_SIZE, chksum, +- chksum_type); ++ chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, ++ chksum_type); + chksum_type = CS_DEFAULT; + } + ep = exfat_get_dentry_cached(es, 0); +@@ -1000,7 +1000,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, + } + + if (entry_type == TYPE_STREAM) { +- unsigned short name_hash; ++ u16 name_hash; + + if (step != DIRENT_STEP_STRM) { + step = DIRENT_STEP_FILE; +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 8c2a70bfaa10d4743c46aad74254e363d4079c74..595f3117f4924893a07eba48b55ef3d7d6edbee6 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -137,7 +137,7 @@ struct exfat_dentry_namebuf { + struct exfat_uni_name { + /* +3 for null and for converting */ + unsigned short name[MAX_NAME_LENGTH + 3]; +- unsigned short name_hash; ++ u16 name_hash; + unsigned char name_len; + }; + +@@ -512,8 +512,7 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + 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); +-unsigned short exfat_calc_chksum_2byte(void *data, int len, +- unsigned short chksum, int type); ++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 super_block *sb, struct buffer_head *bh, int sync); + void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, +diff --git a/src/misc.c b/src/misc.c +index b82d2dd5bd7cb291ffe5ed5091c9214593c32a69..17d41f3d3709287f6654dd52bd540e3f60fee285 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -136,17 +136,15 @@ void exfat_truncate_atime(struct timespec64 *ts) + ts->tv_nsec = 0; + } + +-unsigned short exfat_calc_chksum_2byte(void *data, int len, +- unsigned short chksum, int type) ++u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type) + { + int i; +- unsigned char *c = (unsigned char *)data; ++ u8 *c = (u8 *)data; + + for (i = 0; i < len; i++, c++) { +- if (((i == 2) || (i == 3)) && (type == CS_DIR_ENTRY)) ++ if (unlikely(type == CS_DIR_ENTRY && (i == 2 || i == 3))) + continue; +- chksum = (((chksum & 1) << 15) | ((chksum & 0xFFFE) >> 1)) + +- (unsigned short)*c; ++ chksum = ((chksum << 15) | (chksum >> 1)) + *c; + } + return chksum; + } +diff --git a/src/nls.c b/src/nls.c +index 1ebda90cbdd7c81c507f662332a53eb52bf92606..19321773dd078a42f8c9659e15032502b33bddcd 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -527,7 +527,7 @@ static int exfat_utf8_to_utf16(struct super_block *sb, + + *uniname = '\0'; + p_uniname->name_len = unilen; +- p_uniname->name_hash = exfat_calc_chksum_2byte(upname, unilen << 1, 0, ++ p_uniname->name_hash = exfat_calc_chksum16(upname, unilen << 1, 0, + CS_DEFAULT); + + if (p_lossy) +@@ -623,7 +623,7 @@ static int exfat_nls_to_ucs2(struct super_block *sb, + + *uniname = '\0'; + p_uniname->name_len = unilen; +- p_uniname->name_hash = exfat_calc_chksum_2byte(upname, unilen << 1, 0, ++ p_uniname->name_hash = exfat_calc_chksum16(upname, unilen << 1, 0, + CS_DEFAULT); + + if (p_lossy) +@@ -655,7 +655,8 @@ static int exfat_load_upcase_table(struct super_block *sb, + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); + unsigned int sect_size = sb->s_blocksize; +- unsigned int i, index = 0, checksum = 0; ++ unsigned int i, index = 0; ++ u32 chksum = 0; + int ret; + unsigned char skip = false; + unsigned short *upcase_table; +@@ -681,13 +682,6 @@ static int exfat_load_upcase_table(struct super_block *sb, + for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) { + unsigned short uni = get_unaligned_le16(bh->b_data + i); + +- checksum = ((checksum & 1) ? 0x80000000 : 0) + +- (checksum >> 1) + +- *(((unsigned char *)bh->b_data) + i); +- checksum = ((checksum & 1) ? 0x80000000 : 0) + +- (checksum >> 1) + +- *(((unsigned char *)bh->b_data) + (i + 1)); +- + if (skip) { + index += uni; + skip = false; +@@ -701,13 +695,14 @@ static int exfat_load_upcase_table(struct super_block *sb, + } + } + brelse(bh); ++ chksum = exfat_calc_chksum32(bh->b_data, i, chksum, CS_DEFAULT); + } + +- if (index >= 0xFFFF && utbl_checksum == checksum) ++ if (index >= 0xFFFF && utbl_checksum == chksum) + return 0; + + exfat_err(sb, "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)", +- index, checksum, utbl_checksum); ++ index, chksum, utbl_checksum); + ret = -EINVAL; + free_table: + exfat_free_upcase_table(sbi); +-- +2.31.1 + diff --git a/SOURCES/0034-exfat-remove-unnecessary-reassignment-of-p_uniname-n.patch b/SOURCES/0034-exfat-remove-unnecessary-reassignment-of-p_uniname-n.patch new file mode 100644 index 0000000..7fc761f --- /dev/null +++ b/SOURCES/0034-exfat-remove-unnecessary-reassignment-of-p_uniname-n.patch @@ -0,0 +1,35 @@ +From f78059805fb9fc5c343e89f39cf11856d047dd60 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 1 Jun 2020 09:43:49 +0900 +Subject: [Backport f78059805fb9] exfat: remove unnecessary reassignment of + p_uniname->name_len + +kbuild test robot reported : + + src/nls.c:531:22: warning: Variable 'p_uniname->name_len' + is reassigned a value before the old one has been used. + +The reassignment of p_uniname->name_len is not needed and remove it. + +Reported-by: kbuild test robot +Signed-off-by: Namjae Jeon +--- + src/nls.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/nls.c b/src/nls.c +index 19321773dd078a42f8c9659e15032502b33bddcd..c1ec05695497445934ac8f2d537ccd131e5d7f5c 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -514,8 +514,6 @@ static int exfat_utf8_to_utf16(struct super_block *sb, + return -ENAMETOOLONG; + } + +- p_uniname->name_len = unilen & 0xFF; +- + for (i = 0; i < unilen; i++) { + if (*uniname < 0x0020 || + exfat_wstrchr(bad_uni_chars, *uniname)) +-- +2.31.1 + diff --git a/SOURCES/0035-exfat-fix-memory-leak-in-exfat_parse_param.patch b/SOURCES/0035-exfat-fix-memory-leak-in-exfat_parse_param.patch new file mode 100644 index 0000000..177e099 --- /dev/null +++ b/SOURCES/0035-exfat-fix-memory-leak-in-exfat_parse_param.patch @@ -0,0 +1,85 @@ +From f341a7d8dcc4e3d01544d7bc145633f062ef6249 Mon Sep 17 00:00:00 2001 +From: Al Viro +Date: Wed, 3 Jun 2020 09:48:36 +0900 +Subject: [Backport f341a7d8dcc4] exfat: fix memory leak in exfat_parse_param() + +butt3rflyh4ck reported memory leak found by syzkaller. + +A param->string held by exfat_mount_options. + +BUG: memory leak + +unreferenced object 0xffff88801972e090 (size 8): + comm "syz-executor.2", pid 16298, jiffies 4295172466 (age 14.060s) + hex dump (first 8 bytes): + 6b 6f 69 38 2d 75 00 00 koi8-u.. + backtrace: + [<000000005bfe35d6>] kstrdup+0x36/0x70 mm/util.c:60 + [<0000000018ed3277>] exfat_parse_param+0x160/0x5e0 +src/super.c:276 + [<000000007680462b>] vfs_parse_fs_param+0x2b4/0x610 +fs/fs_context.c:147 + [<0000000097c027f2>] vfs_parse_fs_string+0xe6/0x150 +fs/fs_context.c:191 + [<00000000371bf78f>] generic_parse_monolithic+0x16f/0x1f0 +fs/fs_context.c:231 + [<000000005ce5eb1b>] do_new_mount fs/namespace.c:2812 [inline] + [<000000005ce5eb1b>] do_mount+0x12bb/0x1b30 fs/namespace.c:3141 + [<00000000b642040c>] __do_sys_mount fs/namespace.c:3350 [inline] + [<00000000b642040c>] __se_sys_mount fs/namespace.c:3327 [inline] + [<00000000b642040c>] __x64_sys_mount+0x18f/0x230 fs/namespace.c:3327 + [<000000003b024e98>] do_syscall_64+0xf6/0x7d0 +arch/x86/entry/common.c:295 + [<00000000ce2b698c>] entry_SYSCALL_64_after_hwframe+0x49/0xb3 + +exfat_free() should call exfat_free_iocharset(), to prevent a leak +in case we fail after parsing iocharset= but before calling +get_tree_bdev(). + +Additionally, there's no point copying param->string in +exfat_parse_param() - just steal it, leaving NULL in param->string. +That's independent from the leak or fix thereof - it's simply +avoiding an extra copy. + +Fixes: 719c1e182916 ("exfat: add super block operations") +Cc: stable@vger.kernel.org # v5.7 +Reported-by: butt3rflyh4ck +Signed-off-by: Al Viro +Signed-off-by: Namjae Jeon +--- + src/super.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/src/super.c b/src/super.c +index 405717e4e3eac6b3233e1e57b0bcc70f56f1c42f..e650e65536f848c544b731f273b5f8839298e850 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -273,9 +273,8 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) + break; + case Opt_charset: + exfat_free_iocharset(sbi); +- opts->iocharset = kstrdup(param->string, GFP_KERNEL); +- if (!opts->iocharset) +- return -ENOMEM; ++ opts->iocharset = param->string; ++ param->string = NULL; + break; + case Opt_errors: + opts->errors = result.uint_32; +@@ -686,7 +685,12 @@ static int exfat_get_tree(struct fs_context *fc) + + static void exfat_free(struct fs_context *fc) + { +- kfree(fc->s_fs_info); ++ struct exfat_sb_info *sbi = fc->s_fs_info; ++ ++ if (sbi) { ++ exfat_free_iocharset(sbi); ++ kfree(sbi); ++ } + } + + static const struct fs_context_operations exfat_context_ops = { +-- +2.31.1 + diff --git a/SOURCES/0036-exfat-fix-incorrect-update-of-stream-entry-in-__exfa.patch b/SOURCES/0036-exfat-fix-incorrect-update-of-stream-entry-in-__exfa.patch new file mode 100644 index 0000000..0ee904b --- /dev/null +++ b/SOURCES/0036-exfat-fix-incorrect-update-of-stream-entry-in-__exfa.patch @@ -0,0 +1,41 @@ +From 29bbb14bfc80dd760b07d2be0a27e610562982e3 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Thu, 4 Jun 2020 08:05:31 +0900 +Subject: [Backport 29bbb14bfc80] exfat: fix incorrect update of stream entry + in __exfat_truncate() + +At truncate, there is a problem of incorrect updating in the file entry +pointer instead of stream entry. This will cause the problem of +overwriting the time field of the file entry to new_size. Fix it to +update stream entry. + +Fixes: 98d917047e8b ("exfat: add file operations") +Cc: stable@vger.kernel.org # v5.7 +Signed-off-by: Namjae Jeon +--- + src/file.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/file.c b/src/file.c +index 8e3f0eef45d77345e6f57ef58f3e86c51c69e905..fce03f31878735e1fa389231abc1052f24447146 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -171,11 +171,11 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + + /* File size should be zero if there is no cluster allocated */ + if (ei->start_clu == EXFAT_EOF_CLUSTER) { +- ep->dentry.stream.valid_size = 0; +- ep->dentry.stream.size = 0; ++ ep2->dentry.stream.valid_size = 0; ++ ep2->dentry.stream.size = 0; + } else { +- ep->dentry.stream.valid_size = cpu_to_le64(new_size); +- ep->dentry.stream.size = ep->dentry.stream.valid_size; ++ ep2->dentry.stream.valid_size = cpu_to_le64(new_size); ++ ep2->dentry.stream.size = ep->dentry.stream.valid_size; + } + + if (new_size == 0) { +-- +2.31.1 + diff --git a/SOURCES/0037-exfat-fix-range-validation-error-in-alloc-and-free-c.patch b/SOURCES/0037-exfat-fix-range-validation-error-in-alloc-and-free-c.patch new file mode 100644 index 0000000..d3aefcd --- /dev/null +++ b/SOURCES/0037-exfat-fix-range-validation-error-in-alloc-and-free-c.patch @@ -0,0 +1,42 @@ +From a949824f01f3b39f737d77aed6cba47aced09319 Mon Sep 17 00:00:00 2001 +From: "hyeongseok.kim" +Date: Thu, 4 Jun 2020 13:54:28 +0900 +Subject: [Backport a949824f01f3] exfat: fix range validation error in alloc + and free cluster + +There is check error in range condition that can never be entered +even with invalid input. +Replace incorrent checking code with already existing valid checker. + +Signed-off-by: hyeongseok.kim +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/fatent.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/fatent.c b/src/fatent.c +index 267e5e09eb134c34990ac2d5ce17e2692c955be8..4e5c5c9c0f2df4457670dfeb14f30bfbcbd32a5d 100644 +--- a/src/fatent.c ++++ b/src/fatent.c +@@ -169,7 +169,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) + return 0; + + /* check cluster validation */ +- if (p_chain->dir < 2 && p_chain->dir >= sbi->num_clusters) { ++ if (!is_valid_cluster(sbi, p_chain->dir)) { + exfat_err(sb, "invalid start cluster (%u)", p_chain->dir); + return -EIO; + } +@@ -346,7 +346,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, + } + + /* check cluster validation */ +- if (hint_clu < EXFAT_FIRST_CLUSTER && hint_clu >= sbi->num_clusters) { ++ if (!is_valid_cluster(sbi, hint_clu)) { + exfat_err(sb, "hint_cluster is invalid (%u)", + hint_clu); + hint_clu = EXFAT_FIRST_CLUSTER; +-- +2.31.1 + diff --git a/SOURCES/0038-exfat-Fix-potential-use-after-free-in-exfat_load_upc.patch b/SOURCES/0038-exfat-Fix-potential-use-after-free-in-exfat_load_upc.patch new file mode 100644 index 0000000..e33954a --- /dev/null +++ b/SOURCES/0038-exfat-Fix-potential-use-after-free-in-exfat_load_upc.patch @@ -0,0 +1,34 @@ +From fc961522ddbdf00254dd03b677627139cc1f68bc Mon Sep 17 00:00:00 2001 +From: Dan Carpenter +Date: Mon, 8 Jun 2020 17:16:29 +0300 +Subject: [Backport fc961522ddbd] exfat: Fix potential use after free in + exfat_load_upcase_table() + +This code calls brelse(bh) and then dereferences "bh" on the next line +resulting in a possible use after free. The brelse() should just be +moved down a line. + +Fixes: b676fdbcf4c8 ("exfat: standardize checksum calculation") +Signed-off-by: Dan Carpenter +Signed-off-by: Namjae Jeon +--- + src/nls.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/nls.c b/src/nls.c +index c1ec05695497445934ac8f2d537ccd131e5d7f5c..57b5a7a4d1f7a18b1854348bd6841ea7bc9c466a 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -692,8 +692,8 @@ static int exfat_load_upcase_table(struct super_block *sb, + index++; + } + } +- brelse(bh); + chksum = exfat_calc_chksum32(bh->b_data, i, chksum, CS_DEFAULT); ++ brelse(bh); + } + + if (index >= 0xFFFF && utbl_checksum == chksum) +-- +2.31.1 + diff --git a/SOURCES/0039-exfat-Set-the-unused-characters-of-FileName-field-to.patch b/SOURCES/0039-exfat-Set-the-unused-characters-of-FileName-field-to.patch new file mode 100644 index 0000000..9c12fef --- /dev/null +++ b/SOURCES/0039-exfat-Set-the-unused-characters-of-FileName-field-to.patch @@ -0,0 +1,41 @@ +From 4ba6ccd695f5ed3ae851e59b443b757bbe4557fe Mon Sep 17 00:00:00 2001 +From: "Hyeongseok.Kim" +Date: Tue, 9 Jun 2020 14:30:44 +0900 +Subject: [Backport 4ba6ccd695f5] exfat: Set the unused characters of FileName + field to the value 0000h + +Some fsck tool complain that padding part of the FileName field +is not set to the value 0000h. So let's maintain filesystem cleaner, +as exfat's spec. recommendation. + +Signed-off-by: Hyeongseok.Kim +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index de43534aa2997aa87994a9447f95578de9931258..8e775bd5d523046530b9c82a58264a358bf857e3 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -425,10 +425,12 @@ static void exfat_init_name_entry(struct exfat_dentry *ep, + ep->dentry.name.flags = 0x0; + + for (i = 0; i < EXFAT_FILE_NAME_LEN; i++) { +- ep->dentry.name.unicode_0_14[i] = cpu_to_le16(*uniname); +- if (*uniname == 0x0) +- break; +- uniname++; ++ if (*uniname != 0x0) { ++ ep->dentry.name.unicode_0_14[i] = cpu_to_le16(*uniname); ++ uniname++; ++ } else { ++ ep->dentry.name.unicode_0_14[i] = 0x0; ++ } + } + } + +-- +2.31.1 + diff --git a/SOURCES/0040-exfat-add-missing-brelse-calls-on-error-paths.patch b/SOURCES/0040-exfat-add-missing-brelse-calls-on-error-paths.patch new file mode 100644 index 0000000..e6f0ddc --- /dev/null +++ b/SOURCES/0040-exfat-add-missing-brelse-calls-on-error-paths.patch @@ -0,0 +1,56 @@ +From e8dd3cda8667118b70d9fe527f61fe22623de04d Mon Sep 17 00:00:00 2001 +From: Dan Carpenter +Date: Wed, 10 Jun 2020 20:22:13 +0300 +Subject: [Backport e8dd3cda8667] exfat: add missing brelse() calls on error + paths + +If the second exfat_get_dentry() call fails then we need to release +"old_bh" before returning. There is a similar bug in exfat_move_file(). + +Fixes: 5f2aa075070c ("exfat: add inode operations") +Reported-by: Markus Elfring +Signed-off-by: Dan Carpenter +Signed-off-by: Namjae Jeon +--- + src/namei.c | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/src/namei.c b/src/namei.c +index 5b0f35329d63e0b9ab52162e4fbad129601c458d..edd8023865a0e50d0854856ab57afc75ce56dd46 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -1077,10 +1077,14 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, + + epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh, + §or_old); ++ if (!epold) ++ return -EIO; + epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh, + §or_new); +- if (!epold || !epnew) ++ if (!epnew) { ++ brelse(old_bh); + return -EIO; ++ } + + memcpy(epnew, epold, DENTRY_SIZE); + exfat_update_bh(sb, new_bh, sync); +@@ -1161,10 +1165,14 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, + + epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh, + §or_mov); ++ if (!epmov) ++ return -EIO; + epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh, + §or_new); +- if (!epmov || !epnew) ++ if (!epnew) { ++ brelse(mov_bh); + return -EIO; ++ } + + memcpy(epnew, epmov, DENTRY_SIZE); + exfat_update_bh(sb, new_bh, IS_DIRSYNC(inode)); +-- +2.31.1 + diff --git a/SOURCES/0041-exfat-call-sync_filesystem-for-read-only-remount.patch b/SOURCES/0041-exfat-call-sync_filesystem-for-read-only-remount.patch new file mode 100644 index 0000000..405e56e --- /dev/null +++ b/SOURCES/0041-exfat-call-sync_filesystem-for-read-only-remount.patch @@ -0,0 +1,55 @@ +From a0271a15cf2cf907ea5b0f2ba611123f1b7935ec Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Tue, 16 Jun 2020 14:34:45 +0900 +Subject: [Backport a0271a15cf2c] exfat: call sync_filesystem for read-only + remount + +We need to commit dirty metadata and pages to disk +before remounting exfat as read-only. + +This fixes a failure in xfstests generic/452 + +generic/452 does the following: +cp something / +mount -o remount,ro + +the /something is corrupted. because while +exfat is remounted as read-only, exfat doesn't +have a chance to commit metadata and +vfs invalidates page caches in a block device. + +Signed-off-by: Hyunchul Lee +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/super.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/src/super.c b/src/super.c +index e650e65536f848c544b731f273b5f8839298e850..253a92460d5222f4f3aa75e52fc0b3eab084bc06 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -693,10 +693,20 @@ static void exfat_free(struct fs_context *fc) + } + } + ++static int exfat_reconfigure(struct fs_context *fc) ++{ ++ fc->sb_flags |= SB_NODIRATIME; ++ ++ /* volume flag will be updated in exfat_sync_fs */ ++ sync_filesystem(fc->root->d_sb); ++ return 0; ++} ++ + static const struct fs_context_operations exfat_context_ops = { + .parse_param = exfat_parse_param, + .get_tree = exfat_get_tree, + .free = exfat_free, ++ .reconfigure = exfat_reconfigure, + }; + + static int exfat_init_fs_context(struct fs_context *fc) +-- +2.31.1 + diff --git a/SOURCES/0042-exfat-move-setting-VOL_DIRTY-over-exfat_remove_entri.patch b/SOURCES/0042-exfat-move-setting-VOL_DIRTY-over-exfat_remove_entri.patch new file mode 100644 index 0000000..9c60ae9 --- /dev/null +++ b/SOURCES/0042-exfat-move-setting-VOL_DIRTY-over-exfat_remove_entri.patch @@ -0,0 +1,41 @@ +From 3bcfb701099acf96b0e883bf5544f96af473aa1d Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Wed, 17 Jun 2020 12:17:18 +0900 +Subject: [Backport 3bcfb701099a] exfat: move setting VOL_DIRTY over + exfat_remove_entries() + +Move setting VOL_DIRTY over exfat_remove_entries() to avoid unneeded +leaving VOL_DIRTY on -ENOTEMPTY. + +Fixes: 5f2aa075070c ("exfat: add inode operations") +Cc: stable@vger.kernel.org # v5.7 +Reported-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/namei.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/namei.c b/src/namei.c +index edd8023865a0e50d0854856ab57afc75ce56dd46..2b9e21094a96dbb252f38e34db272e567d665665 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -975,7 +975,6 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + goto unlock; + } + +- exfat_set_vol_flags(sb, VOL_DIRTY); + exfat_chain_set(&clu_to_free, ei->start_clu, + EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi), ei->flags); + +@@ -1002,6 +1001,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + num_entries++; + brelse(bh); + ++ exfat_set_vol_flags(sb, VOL_DIRTY); + err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries); + if (err) { + exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err); +-- +2.31.1 + diff --git a/SOURCES/0043-exfat-flush-dirty-metadata-in-fsync.patch b/SOURCES/0043-exfat-flush-dirty-metadata-in-fsync.patch new file mode 100644 index 0000000..6f7743d --- /dev/null +++ b/SOURCES/0043-exfat-flush-dirty-metadata-in-fsync.patch @@ -0,0 +1,90 @@ +From 5267456e953fd8c5abd8e278b1cc6a9f9027ac0a Mon Sep 17 00:00:00 2001 +From: Sungjong Seo +Date: Thu, 18 Jun 2020 20:43:26 +0900 +Subject: [Backport 5267456e953f] exfat: flush dirty metadata in fsync + +generic_file_fsync() exfat used could not guarantee the consistency of +a file because it has flushed not dirty metadata but only dirty data pages +for a file. + +Instead of that, use exfat_file_fsync() for files and directories so that +it guarantees to commit both the metadata and data pages for a file. + +Signed-off-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 2 +- + src/exfat_fs.h | 1 + + src/file.c | 19 ++++++++++++++++++- + 3 files changed, 20 insertions(+), 2 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 8e775bd5d523046530b9c82a58264a358bf857e3..91ece649285d2824ff99ee5b996adeb8ff973a93 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -309,7 +309,7 @@ const struct file_operations exfat_dir_operations = { + .llseek = generic_file_llseek, + .read = generic_read_dir, + .iterate = exfat_iterate, +- .fsync = generic_file_fsync, ++ .fsync = exfat_file_fsync, + }; + + int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu) +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 595f3117f4924893a07eba48b55ef3d7d6edbee6..7579cd3bbadba87a8bfd56c0699c8186902a6763 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -420,6 +420,7 @@ void exfat_truncate(struct inode *inode, loff_t size); + int exfat_setattr(struct dentry *dentry, struct iattr *attr); + int exfat_getattr(const struct path *path, struct kstat *stat, + unsigned int request_mask, unsigned int query_flags); ++int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); + + /* namei.c */ + extern const struct dentry_operations exfat_dentry_ops; +diff --git a/src/file.c b/src/file.c +index fce03f31878735e1fa389231abc1052f24447146..3b7fea465fd41e2859a94afac61dd9768a9f3119 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + + #include "exfat_raw.h" + #include "exfat_fs.h" +@@ -346,12 +347,28 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) + return error; + } + ++int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) ++{ ++ struct inode *inode = filp->f_mapping->host; ++ int err; ++ ++ err = __generic_file_fsync(filp, start, end, datasync); ++ if (err) ++ return err; ++ ++ err = sync_blockdev(inode->i_sb->s_bdev); ++ if (err) ++ return err; ++ ++ return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL); ++} ++ + const struct file_operations exfat_file_operations = { + .llseek = generic_file_llseek, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, + .mmap = generic_file_mmap, +- .fsync = generic_file_fsync, ++ .fsync = exfat_file_fsync, + .splice_read = generic_file_splice_read, + .splice_write = iter_file_splice_write, + }; +-- +2.31.1 + diff --git a/SOURCES/0044-exfat-fix-overflow-issue-in-exfat_cluster_to_sector.patch b/SOURCES/0044-exfat-fix-overflow-issue-in-exfat_cluster_to_sector.patch new file mode 100644 index 0000000..cf1425d --- /dev/null +++ b/SOURCES/0044-exfat-fix-overflow-issue-in-exfat_cluster_to_sector.patch @@ -0,0 +1,34 @@ +From 43946b70494beefe40ec1b2ba4744c0f294d7736 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Fri, 3 Jul 2020 11:16:32 +0900 +Subject: [Backport 43946b70494b] exfat: fix overflow issue in + exfat_cluster_to_sector() + +An overflow issue can occur while calculating sector in +exfat_cluster_to_sector(). It needs to cast clus's type to sector_t +before left shifting. + +Fixes: 1acf1a564b60 ("exfat: add in-memory and on-disk structures and headers") +Cc: stable@vger.kernel.org # v5.7 +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 7579cd3bbadba87a8bfd56c0699c8186902a6763..75c7bdbeba6d3e9316c7b795cd525d1323e47b83 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -371,7 +371,7 @@ static inline bool exfat_is_last_sector_in_cluster(struct exfat_sb_info *sbi, + static inline sector_t exfat_cluster_to_sector(struct exfat_sb_info *sbi, + unsigned int clus) + { +- return ((clus - EXFAT_RESERVED_CLUSTERS) << sbi->sect_per_clus_bits) + ++ return ((sector_t)(clus - EXFAT_RESERVED_CLUSTERS) << sbi->sect_per_clus_bits) + + sbi->data_start_sector; + } + +-- +2.31.1 + diff --git a/SOURCES/0045-exfat-fix-wrong-hint_stat-initialization-in-exfat_fi.patch b/SOURCES/0045-exfat-fix-wrong-hint_stat-initialization-in-exfat_fi.patch new file mode 100644 index 0000000..6bd7e96 --- /dev/null +++ b/SOURCES/0045-exfat-fix-wrong-hint_stat-initialization-in-exfat_fi.patch @@ -0,0 +1,33 @@ +From d2fa0c337d97a5490190b9f3b9c73c8f9f3602a1 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Fri, 3 Jul 2020 11:19:46 +0900 +Subject: [Backport d2fa0c337d97] exfat: fix wrong hint_stat initialization in + exfat_find_dir_entry() + +We found the wrong hint_stat initialization in exfat_find_dir_entry(). +It should be initialized when cluster is EXFAT_EOF_CLUSTER. + +Fixes: ca06197382bd ("exfat: add directory operations") +Cc: stable@vger.kernel.org # v5.7 +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/dir.c b/src/dir.c +index 91ece649285d2824ff99ee5b996adeb8ff973a93..119abf0d8dd6fe5b23e1537341068f3f3060f035 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -1112,7 +1112,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, + ret = exfat_get_next_cluster(sb, &clu.dir); + } + +- if (ret || clu.dir != EXFAT_EOF_CLUSTER) { ++ if (ret || clu.dir == EXFAT_EOF_CLUSTER) { + /* just initialized hint_stat */ + hint_stat->clu = p_dir->dir; + hint_stat->eidx = 0; +-- +2.31.1 + diff --git a/SOURCES/0046-exfat-fix-wrong-size-update-of-stream-entry-by-typo.patch b/SOURCES/0046-exfat-fix-wrong-size-update-of-stream-entry-by-typo.patch new file mode 100644 index 0000000..024dfec --- /dev/null +++ b/SOURCES/0046-exfat-fix-wrong-size-update-of-stream-entry-by-typo.patch @@ -0,0 +1,32 @@ +From 41e3928f8c58184fcf0bb22e822af39a436370c7 Mon Sep 17 00:00:00 2001 +From: Hyeongseok Kim +Date: Wed, 8 Jul 2020 18:52:33 +0900 +Subject: [Backport 41e3928f8c58] exfat: fix wrong size update of stream entry + by typo + +The stream.size field is updated to the value of create timestamp +of the file entry. Fix this to use correct stream entry pointer. + +Fixes: 29bbb14bfc80 ("exfat: fix incorrect update of stream entry in __exfat_truncate()") +Signed-off-by: Hyeongseok Kim +Signed-off-by: Namjae Jeon +--- + src/file.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/file.c b/src/file.c +index 3b7fea465fd41e2859a94afac61dd9768a9f3119..a6a063830edcb68944e55fe7308dee1312463b15 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -176,7 +176,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + ep2->dentry.stream.size = 0; + } else { + ep2->dentry.stream.valid_size = cpu_to_le64(new_size); +- ep2->dentry.stream.size = ep->dentry.stream.valid_size; ++ ep2->dentry.stream.size = ep2->dentry.stream.valid_size; + } + + if (new_size == 0) { +-- +2.31.1 + diff --git a/SOURCES/0047-exfat-fix-name_hash-computation-on-big-endian-system.patch b/SOURCES/0047-exfat-fix-name_hash-computation-on-big-endian-system.patch new file mode 100644 index 0000000..a8d8cba --- /dev/null +++ b/SOURCES/0047-exfat-fix-name_hash-computation-on-big-endian-system.patch @@ -0,0 +1,62 @@ +From db415f7aae07cadcabd5d2a659f8ad825c905299 Mon Sep 17 00:00:00 2001 +From: Ilya Ponetayev +Date: Thu, 16 Jul 2020 17:27:53 +0900 +Subject: [Backport db415f7aae07] exfat: fix name_hash computation on big + endian systems + +On-disk format for name_hash field is LE, so it must be explicitly +transformed on BE system for proper result. + +Fixes: 370e812b3ec1 ("exfat: add nls operations") +Cc: stable@vger.kernel.org # v5.7 +Signed-off-by: Chen Minqiang +Signed-off-by: Ilya Ponetayev +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/nls.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/nls.c b/src/nls.c +index 57b5a7a4d1f7a18b1854348bd6841ea7bc9c466a..a3c927501e676f3b240eca17106b07a296d99e6f 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -495,7 +495,7 @@ static int exfat_utf8_to_utf16(struct super_block *sb, + struct exfat_uni_name *p_uniname, int *p_lossy) + { + int i, unilen, lossy = NLS_NAME_NO_LOSSY; +- unsigned short upname[MAX_NAME_LENGTH + 1]; ++ __le16 upname[MAX_NAME_LENGTH + 1]; + unsigned short *uniname = p_uniname->name; + + WARN_ON(!len); +@@ -519,7 +519,7 @@ static int exfat_utf8_to_utf16(struct super_block *sb, + exfat_wstrchr(bad_uni_chars, *uniname)) + lossy |= NLS_NAME_LOSSY; + +- upname[i] = exfat_toupper(sb, *uniname); ++ upname[i] = cpu_to_le16(exfat_toupper(sb, *uniname)); + uniname++; + } + +@@ -597,7 +597,7 @@ static int exfat_nls_to_ucs2(struct super_block *sb, + struct exfat_uni_name *p_uniname, int *p_lossy) + { + int i = 0, unilen = 0, lossy = NLS_NAME_NO_LOSSY; +- unsigned short upname[MAX_NAME_LENGTH + 1]; ++ __le16 upname[MAX_NAME_LENGTH + 1]; + unsigned short *uniname = p_uniname->name; + struct nls_table *nls = EXFAT_SB(sb)->nls_io; + +@@ -611,7 +611,7 @@ static int exfat_nls_to_ucs2(struct super_block *sb, + exfat_wstrchr(bad_uni_chars, *uniname)) + lossy |= NLS_NAME_LOSSY; + +- upname[unilen] = exfat_toupper(sb, *uniname); ++ upname[unilen] = cpu_to_le16(exfat_toupper(sb, *uniname)); + uniname++; + unilen++; + } +-- +2.31.1 + diff --git a/SOURCES/0048-exfat-remove-EXFAT_SB_DIRTY-flag.patch b/SOURCES/0048-exfat-remove-EXFAT_SB_DIRTY-flag.patch new file mode 100644 index 0000000..520e8a6 --- /dev/null +++ b/SOURCES/0048-exfat-remove-EXFAT_SB_DIRTY-flag.patch @@ -0,0 +1,307 @@ +From 2c7f8937ef91520a8a4bd700d5817b5e9c99803c Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Tue, 16 Jun 2020 11:18:07 +0900 +Subject: [Backport 2c7f8937ef91] exfat: remove EXFAT_SB_DIRTY flag + +This flag is set/reset in exfat_put_super()/exfat_sync_fs() +to avoid sync_blockdev(). +- exfat_put_super(): +Before calling this, the VFS has already called sync_filesystem(), +so sync is never performed here. +- exfat_sync_fs(): +After calling this, the VFS calls sync_blockdev(), so, it is meaningless +to check EXFAT_SB_DIRTY or to bypass sync_blockdev() here. + +Remove the EXFAT_SB_DIRTY check to ensure synchronization. +And remove the code related to the flag. + +Signed-off-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/balloc.c | 4 ++-- + src/dir.c | 16 ++++++++-------- + src/exfat_fs.h | 5 +---- + src/fatent.c | 7 ++----- + src/misc.c | 3 +-- + src/namei.c | 12 ++++++------ + src/super.c | 14 ++++++-------- + 7 files changed, 26 insertions(+), 35 deletions(-) + +diff --git a/src/balloc.c b/src/balloc.c +index 4055eb00ea9b84a3550dea0f831fafa2df3b3121..a987919686c0d4d339ce71d11887d9510280df77 100644 +--- a/src/balloc.c ++++ b/src/balloc.c +@@ -158,7 +158,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu) + b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); + + set_bit_le(b, sbi->vol_amap[i]->b_data); +- exfat_update_bh(sb, sbi->vol_amap[i], IS_DIRSYNC(inode)); ++ exfat_update_bh(sbi->vol_amap[i], IS_DIRSYNC(inode)); + return 0; + } + +@@ -180,7 +180,7 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu) + b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); + + clear_bit_le(b, sbi->vol_amap[i]->b_data); +- exfat_update_bh(sb, sbi->vol_amap[i], IS_DIRSYNC(inode)); ++ exfat_update_bh(sbi->vol_amap[i], IS_DIRSYNC(inode)); + + if (opts->discard) { + int ret_discard; +diff --git a/src/dir.c b/src/dir.c +index 119abf0d8dd6fe5b23e1537341068f3f3060f035..97296bc2b241cbe40b9a87e1f11ef8c203881680 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -470,7 +470,7 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, + &ep->dentry.file.access_date, + NULL); + +- exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(bh, IS_DIRSYNC(inode)); + brelse(bh); + + ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh, §or); +@@ -480,7 +480,7 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, + exfat_init_stream_entry(ep, + (type == TYPE_FILE) ? ALLOC_FAT_CHAIN : ALLOC_NO_FAT_CHAIN, + start_clu, size); +- exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(bh, IS_DIRSYNC(inode)); + brelse(bh); + + return 0; +@@ -516,7 +516,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, + } + + fep->dentry.file.checksum = cpu_to_le16(chksum); +- exfat_update_bh(sb, fbh, IS_DIRSYNC(inode)); ++ exfat_update_bh(fbh, IS_DIRSYNC(inode)); + release_fbh: + brelse(fbh); + return ret; +@@ -538,7 +538,7 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, + return -EIO; + + ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); +- exfat_update_bh(sb, bh, sync); ++ exfat_update_bh(bh, sync); + brelse(bh); + + ep = exfat_get_dentry(sb, p_dir, entry + 1, &bh, §or); +@@ -547,7 +547,7 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, + + ep->dentry.stream.name_len = p_uniname->name_len; + ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash); +- exfat_update_bh(sb, bh, sync); ++ exfat_update_bh(bh, sync); + brelse(bh); + + for (i = EXFAT_FIRST_CLUSTER; i < num_entries; i++) { +@@ -556,7 +556,7 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, + return -EIO; + + exfat_init_name_entry(ep, uniname); +- exfat_update_bh(sb, bh, sync); ++ exfat_update_bh(bh, sync); + brelse(bh); + uniname += EXFAT_FILE_NAME_LEN; + } +@@ -580,7 +580,7 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, + return -EIO; + + exfat_set_entry_type(ep, TYPE_DELETED); +- exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(bh, IS_DIRSYNC(inode)); + brelse(bh); + } + +@@ -610,7 +610,7 @@ void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) + + for (i = 0; i < es->num_bh; i++) { + if (es->modified) +- exfat_update_bh(es->sb, es->bh[i], sync); ++ exfat_update_bh(es->bh[i], sync); + brelse(es->bh[i]); + } + kfree(es); +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 75c7bdbeba6d3e9316c7b795cd525d1323e47b83..05553b2924ee313d9341bc8585f2290a10e8ff56 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -13,8 +13,6 @@ + #define EXFAT_SUPER_MAGIC 0x2011BAB0UL + #define EXFAT_ROOT_INO 1 + +-#define EXFAT_SB_DIRTY 0 +- + #define EXFAT_CLUSTERS_UNTRACKED (~0u) + + /* +@@ -238,7 +236,6 @@ struct exfat_sb_info { + unsigned int clu_srch_ptr; /* cluster search pointer */ + unsigned int used_clusters; /* number of used clusters */ + +- unsigned long s_state; + struct mutex s_lock; /* superblock lock */ + struct exfat_mount_options options; + struct nls_table *nls_io; /* Charset used for input and display */ +@@ -515,7 +512,7 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); + 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 super_block *sb, struct buffer_head *bh, int sync); ++void exfat_update_bh(struct buffer_head *bh, int sync); + 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); +diff --git a/src/fatent.c b/src/fatent.c +index 4e5c5c9c0f2df4457670dfeb14f30bfbcbd32a5d..82ee8246c080f38bc2cc0f8074c72aebbff03fd8 100644 +--- a/src/fatent.c ++++ b/src/fatent.c +@@ -75,7 +75,7 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, + + fat_entry = (__le32 *)&(bh->b_data[off]); + *fat_entry = cpu_to_le32(content); +- exfat_update_bh(sb, bh, sb->s_flags & SB_SYNCHRONOUS); ++ exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); + exfat_mirror_bh(sb, sec, bh); + brelse(bh); + return 0; +@@ -174,7 +174,6 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) + return -EIO; + } + +- set_bit(EXFAT_SB_DIRTY, &sbi->s_state); + clu = p_chain->dir; + + if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { +@@ -274,7 +273,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) + goto release_bhs; + } + memset(bhs[n]->b_data, 0, sb->s_blocksize); +- exfat_update_bh(sb, bhs[n], 0); ++ exfat_update_bh(bhs[n], 0); + + n++; + blknr++; +@@ -358,8 +357,6 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, + } + } + +- set_bit(EXFAT_SB_DIRTY, &sbi->s_state); +- + p_chain->dir = EXFAT_EOF_CLUSTER; + + while ((new_clu = exfat_find_free_bitmap(sb, hint_clu)) != +diff --git a/src/misc.c b/src/misc.c +index 17d41f3d3709287f6654dd52bd540e3f60fee285..8a3dde59052bf04a4de6f65554df14ed6f185147 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -163,9 +163,8 @@ u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type) + return chksum; + } + +-void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync) ++void exfat_update_bh(struct buffer_head *bh, int sync) + { +- set_bit(EXFAT_SB_DIRTY, &EXFAT_SB(sb)->s_state); + set_buffer_uptodate(bh); + mark_buffer_dirty(bh); + +diff --git a/src/namei.c b/src/namei.c +index 2b9e21094a96dbb252f38e34db272e567d665665..126ed3ba8f4791859a11bdfac05a1b71847b2e22 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -387,7 +387,7 @@ static int exfat_find_empty_entry(struct inode *inode, + ep->dentry.stream.valid_size = cpu_to_le64(size); + ep->dentry.stream.size = ep->dentry.stream.valid_size; + ep->dentry.stream.flags = p_dir->flags; +- exfat_update_bh(sb, bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(bh, IS_DIRSYNC(inode)); + brelse(bh); + if (exfat_update_dir_chksum(inode, &(ei->dir), + ei->entry)) +@@ -1071,7 +1071,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, + epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); + ei->attr |= ATTR_ARCHIVE; + } +- exfat_update_bh(sb, new_bh, sync); ++ exfat_update_bh(new_bh, sync); + brelse(old_bh); + brelse(new_bh); + +@@ -1087,7 +1087,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, + } + + memcpy(epnew, epold, DENTRY_SIZE); +- exfat_update_bh(sb, new_bh, sync); ++ exfat_update_bh(new_bh, sync); + brelse(old_bh); + brelse(new_bh); + +@@ -1104,7 +1104,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, + epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); + ei->attr |= ATTR_ARCHIVE; + } +- exfat_update_bh(sb, old_bh, sync); ++ exfat_update_bh(old_bh, sync); + brelse(old_bh); + ret = exfat_init_ext_entry(inode, p_dir, oldentry, + num_new_entries, p_uniname); +@@ -1159,7 +1159,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, + epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); + ei->attr |= ATTR_ARCHIVE; + } +- exfat_update_bh(sb, new_bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(new_bh, IS_DIRSYNC(inode)); + brelse(mov_bh); + brelse(new_bh); + +@@ -1175,7 +1175,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, + } + + memcpy(epnew, epmov, DENTRY_SIZE); +- exfat_update_bh(sb, new_bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(new_bh, IS_DIRSYNC(inode)); + brelse(mov_bh); + brelse(new_bh); + +diff --git a/src/super.c b/src/super.c +index 253a92460d5222f4f3aa75e52fc0b3eab084bc06..b5bf6dedbe1159fb4aa0b501ab7e5b8f2051a063 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -45,9 +45,6 @@ static void exfat_put_super(struct super_block *sb) + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + mutex_lock(&sbi->s_lock); +- if (test_and_clear_bit(EXFAT_SB_DIRTY, &sbi->s_state)) +- sync_blockdev(sb->s_bdev); +- exfat_set_vol_flags(sb, VOL_CLEAN); + exfat_free_bitmap(sbi); + brelse(sbi->boot_bh); + mutex_unlock(&sbi->s_lock); +@@ -60,13 +57,14 @@ static int exfat_sync_fs(struct super_block *sb, int wait) + struct exfat_sb_info *sbi = EXFAT_SB(sb); + int err = 0; + ++ if (!wait) ++ return 0; ++ + /* If there are some dirty buffers in the bdev inode */ + mutex_lock(&sbi->s_lock); +- if (test_and_clear_bit(EXFAT_SB_DIRTY, &sbi->s_state)) { +- sync_blockdev(sb->s_bdev); +- if (exfat_set_vol_flags(sb, VOL_CLEAN)) +- err = -EIO; +- } ++ sync_blockdev(sb->s_bdev); ++ if (exfat_set_vol_flags(sb, VOL_CLEAN)) ++ err = -EIO; + mutex_unlock(&sbi->s_lock); + return err; + } +-- +2.31.1 + diff --git a/SOURCES/0049-exfat-write-multiple-sectors-at-once.patch b/SOURCES/0049-exfat-write-multiple-sectors-at-once.patch new file mode 100644 index 0000000..4d02e3f --- /dev/null +++ b/SOURCES/0049-exfat-write-multiple-sectors-at-once.patch @@ -0,0 +1,90 @@ +From 3db3c3fb840ed4a6c7666d1464959edd40fe54f1 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Tue, 23 Jun 2020 15:22:19 +0900 +Subject: [Backport 3db3c3fb840e] exfat: write multiple sectors at once + +Write multiple sectors at once when updating dir-entries. +Add exfat_update_bhs() for that. It wait for write completion once +instead of sector by sector. +It's only effective if sync enabled. + +Signed-off-by: Tetsuhiro Kohada +Signed-off-by: Namjae Jeon +--- + src/dir.c | 15 +++++++++------ + src/exfat_fs.h | 1 + + src/misc.c | 19 +++++++++++++++++++ + 3 files changed, 29 insertions(+), 6 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 97296bc2b241cbe40b9a87e1f11ef8c203881680..542f1a5ab56fd60a2498f4437c85875cb07e9ef2 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -606,13 +606,16 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) + + void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) + { +- int i; ++ int i, err = 0; + +- for (i = 0; i < es->num_bh; i++) { +- if (es->modified) +- exfat_update_bh(es->bh[i], sync); +- brelse(es->bh[i]); +- } ++ if (es->modified) ++ err = exfat_update_bhs(es->bh, es->num_bh, sync); ++ ++ for (i = 0; i < es->num_bh; i++) ++ if (err) ++ bforget(es->bh[i]); ++ else ++ brelse(es->bh[i]); + kfree(es); + } + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 05553b2924ee313d9341bc8585f2290a10e8ff56..af0f526eece7bd05caa6a1b7fae28701776df5ed 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -513,6 +513,7 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, + 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); ++int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync); + 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); +diff --git a/src/misc.c b/src/misc.c +index 8a3dde59052bf04a4de6f65554df14ed6f185147..d34e6193258dd37117dba5fe27b5323b82dfedef 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -172,6 +172,25 @@ void exfat_update_bh(struct buffer_head *bh, int sync) + sync_dirty_buffer(bh); + } + ++int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync) ++{ ++ int i, err = 0; ++ ++ for (i = 0; i < nr_bhs; i++) { ++ set_buffer_uptodate(bhs[i]); ++ mark_buffer_dirty(bhs[i]); ++ if (sync) ++ write_dirty_buffer(bhs[i], 0); ++ } ++ ++ for (i = 0; i < nr_bhs && sync; i++) { ++ wait_on_buffer(bhs[i]); ++ if (!err && !buffer_uptodate(bhs[i])) ++ err = -EIO; ++ } ++ return err; ++} ++ + void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, + unsigned int size, unsigned char flags) + { +-- +2.31.1 + diff --git a/SOURCES/0050-exfat-add-error-check-when-updating-dir-entries.patch b/SOURCES/0050-exfat-add-error-check-when-updating-dir-entries.patch new file mode 100644 index 0000000..2ddf5be --- /dev/null +++ b/SOURCES/0050-exfat-add-error-check-when-updating-dir-entries.patch @@ -0,0 +1,112 @@ +From 8b0c471773819c8201dc0b0123e1580639ee1570 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Wed, 24 Jun 2020 09:54:54 +0900 +Subject: [Backport 8b0c47177381] exfat: add error check when updating + dir-entries + +Add error check when synchronously updating dir-entries. + +Suggested-by: Sungjong Seo +Signed-off-by: Tetsuhiro Kohada +Signed-off-by: Namjae Jeon +--- + src/dir.c | 3 ++- + src/exfat_fs.h | 2 +- + src/file.c | 5 ++++- + src/inode.c | 9 +++++---- + 4 files changed, 12 insertions(+), 7 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 542f1a5ab56fd60a2498f4437c85875cb07e9ef2..573659bfbc5542f20d1181e95ef292f341aab0d6 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -604,7 +604,7 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) + es->modified = true; + } + +-void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) ++int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) + { + int i, err = 0; + +@@ -617,6 +617,7 @@ void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) + else + brelse(es->bh[i]); + kfree(es); ++ return err; + } + + static int exfat_walk_fat_chain(struct super_block *sb, +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index af0f526eece7bd05caa6a1b7fae28701776df5ed..cb51d6e831990f13413267f4fa0a04c3a6176380 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -460,7 +460,7 @@ struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, + int num); + struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, + struct exfat_chain *p_dir, int entry, unsigned int type); +-void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); ++int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); + int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); + + /* inode.c */ +diff --git a/src/file.c b/src/file.c +index a6a063830edcb68944e55fe7308dee1312463b15..6bdabfd4b13426a1965a0e90598a3f5a2821bbe9 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -154,6 +154,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + struct timespec64 ts; + struct exfat_dentry *ep, *ep2; + struct exfat_entry_set_cache *es; ++ int err; + + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, + ES_ALL_ENTRIES); +@@ -188,7 +189,9 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + } + + exfat_update_dir_chksum_with_entry_set(es); +- exfat_free_dentry_set(es, inode_needs_sync(inode)); ++ err = exfat_free_dentry_set(es, inode_needs_sync(inode)); ++ if (err) ++ return err; + } + + /* cut off from the FAT chain */ +diff --git a/src/inode.c b/src/inode.c +index cf9ca6c4d046eed63e8658d7216e2fedbd89cf22..f0160a7892a815cf22a0e495db104a9ebea35615 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -77,8 +77,7 @@ static int __exfat_write_inode(struct inode *inode, int sync) + ep2->dentry.stream.size = ep2->dentry.stream.valid_size; + + exfat_update_dir_chksum_with_entry_set(es); +- exfat_free_dentry_set(es, sync); +- return 0; ++ return exfat_free_dentry_set(es, sync); + } + + int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) +@@ -222,6 +221,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, + if (ei->dir.dir != DIR_DELETED && modified) { + struct exfat_dentry *ep; + struct exfat_entry_set_cache *es; ++ int err; + + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, + ES_ALL_ENTRIES); +@@ -240,8 +240,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, + ep->dentry.stream.valid_size; + + exfat_update_dir_chksum_with_entry_set(es); +- exfat_free_dentry_set(es, inode_needs_sync(inode)); +- ++ err = exfat_free_dentry_set(es, inode_needs_sync(inode)); ++ if (err) ++ return err; + } /* end of if != DIR_DELETED */ + + inode->i_blocks += +-- +2.31.1 + diff --git a/SOURCES/0051-exfat-optimize-exfat_zeroed_cluster.patch b/SOURCES/0051-exfat-optimize-exfat_zeroed_cluster.patch new file mode 100644 index 0000000..2dbb480 --- /dev/null +++ b/SOURCES/0051-exfat-optimize-exfat_zeroed_cluster.patch @@ -0,0 +1,96 @@ +From 4dc7d35e09ba78aa0a3bcaa9fad1c19952e018a7 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Wed, 24 Jun 2020 11:30:40 +0900 +Subject: [Backport 4dc7d35e09ba] exfat: optimize exfat_zeroed_cluster() + +Replace part of exfat_zeroed_cluster() with exfat_update_bhs(). +And remove exfat_sync_bhs(). + +Signed-off-by: Tetsuhiro Kohada +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/fatent.c | 53 +++++++++-------------------------------------- + 1 file changed, 10 insertions(+), 43 deletions(-) + +diff --git a/src/fatent.c b/src/fatent.c +index 82ee8246c080f38bc2cc0f8074c72aebbff03fd8..c3c9afee7418f1871a65757d79f8d4a13fe650c2 100644 +--- a/src/fatent.c ++++ b/src/fatent.c +@@ -229,21 +229,6 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, + return 0; + } + +-static inline int exfat_sync_bhs(struct buffer_head **bhs, int nr_bhs) +-{ +- int i, err = 0; +- +- for (i = 0; i < nr_bhs; i++) +- write_dirty_buffer(bhs[i], 0); +- +- for (i = 0; i < nr_bhs; i++) { +- wait_on_buffer(bhs[i]); +- if (!err && !buffer_uptodate(bhs[i])) +- err = -EIO; +- } +- return err; +-} +- + int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) + { + struct super_block *sb = dir->i_sb; +@@ -265,41 +250,23 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) + } + + /* Zeroing the unused blocks on this cluster */ +- n = 0; + while (blknr < last_blknr) { +- bhs[n] = sb_getblk(sb, blknr); +- if (!bhs[n]) { +- err = -ENOMEM; +- goto release_bhs; +- } +- memset(bhs[n]->b_data, 0, sb->s_blocksize); +- exfat_update_bh(bhs[n], 0); +- +- n++; +- blknr++; +- +- if (n == nr_bhs) { +- if (IS_DIRSYNC(dir)) { +- err = exfat_sync_bhs(bhs, n); +- if (err) +- goto release_bhs; ++ 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; + } +- +- for (i = 0; i < n; i++) +- brelse(bhs[i]); +- n = 0; ++ memset(bhs[n]->b_data, 0, sb->s_blocksize); + } +- } + +- if (IS_DIRSYNC(dir)) { +- err = exfat_sync_bhs(bhs, n); ++ err = exfat_update_bhs(bhs, n, IS_DIRSYNC(dir)); + if (err) + goto release_bhs; +- } +- +- for (i = 0; i < n; i++) +- brelse(bhs[i]); + ++ for (i = 0; i < n; i++) ++ brelse(bhs[i]); ++ } + return 0; + + release_bhs: +-- +2.31.1 + diff --git a/SOURCES/0052-exfat-retain-VolumeFlags-properly.patch b/SOURCES/0052-exfat-retain-VolumeFlags-properly.patch new file mode 100644 index 0000000..4ccffd2 --- /dev/null +++ b/SOURCES/0052-exfat-retain-VolumeFlags-properly.patch @@ -0,0 +1,298 @@ +From 7018ec68f08249de17cb131b324d5a48e89ed898 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Fri, 31 Jul 2020 14:58:26 +0900 +Subject: [Backport 7018ec68f082] exfat: retain 'VolumeFlags' properly + +MediaFailure and VolumeDirty should be retained if these are set before +mounting. + +In '3.1.13.3 Media Failure Field' of exfat specification describe: + + If, upon mounting a volume, the value of this field is 1, + implementations which scan the entire volume for media failures and + record all failures as "bad" clusters in the FAT (or otherwise resolve + media failures) may clear the value of this field to 0. + +Therefore, We should not clear MediaFailure without scanning volume. + +In '8.1 Recommended Write Ordering' of exfat specification describe: + + Clear the value of the VolumeDirty field to 0, if its value prior to + the first step was 0. + +Therefore, We should not clear VolumeDirty after mounting. +Also rename ERR_MEDIUM to MEDIA_FAILURE. + +Signed-off-by: Tetsuhiro Kohada +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 6 ++++-- + src/exfat_raw.h | 5 ++--- + src/file.c | 4 ++-- + src/inode.c | 4 ++-- + src/namei.c | 20 ++++++++++---------- + src/super.c | 36 +++++++++++++++++++++++++++--------- + 6 files changed, 47 insertions(+), 28 deletions(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index cb51d6e831990f13413267f4fa0a04c3a6176380..95d717f8620cd06cd29909ca07a17208dfd12325 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -224,7 +224,8 @@ struct exfat_sb_info { + unsigned int num_FAT_sectors; /* num of FAT sectors */ + unsigned int root_dir; /* root dir cluster */ + unsigned int dentries_per_clu; /* num of dentries per cluster */ +- unsigned int vol_flag; /* volume dirty flag */ ++ unsigned int vol_flags; /* volume flags */ ++ unsigned int vol_flags_persistent; /* volume flags to retain */ + struct buffer_head *boot_bh; /* buffer_head of BOOT sector */ + + unsigned int map_clu; /* allocation bitmap start cluster */ +@@ -380,7 +381,8 @@ static inline int exfat_sector_to_cluster(struct exfat_sb_info *sbi, + } + + /* super.c */ +-int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag); ++int exfat_set_volume_dirty(struct super_block *sb); ++int exfat_clear_volume_dirty(struct super_block *sb); + + /* fatent.c */ + #define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu) +diff --git a/src/exfat_raw.h b/src/exfat_raw.h +index 350ce59cc32478919d9ffb9823726b8b9015de48..6aec6288e1f21981920f802083d591034c3fb6a2 100644 +--- a/src/exfat_raw.h ++++ b/src/exfat_raw.h +@@ -14,9 +14,8 @@ + + #define EXFAT_MAX_FILE_LEN 255 + +-#define VOL_CLEAN 0x0000 +-#define VOL_DIRTY 0x0002 +-#define ERR_MEDIUM 0x0004 ++#define VOLUME_DIRTY 0x0002 ++#define MEDIA_FAILURE 0x0004 + + #define EXFAT_EOF_CLUSTER 0xFFFFFFFFu + #define EXFAT_BAD_CLUSTER 0xFFFFFFF7u +diff --git a/src/file.c b/src/file.c +index 6bdabfd4b13426a1965a0e90598a3f5a2821bbe9..f41f523a58ad4dab6f7c4d3edef6efce21348ce5 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -106,7 +106,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + if (ei->type != TYPE_FILE && ei->type != TYPE_DIR) + return -EPERM; + +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + + num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi); + num_clusters_phys = +@@ -220,7 +220,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + if (exfat_free_cluster(inode, &clu)) + return -EIO; + +- exfat_set_vol_flags(sb, VOL_CLEAN); ++ exfat_clear_volume_dirty(sb); + + return 0; + } +diff --git a/src/inode.c b/src/inode.c +index f0160a7892a815cf22a0e495db104a9ebea35615..7f90204adef53027dd1eb4348141394af5548b1b 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -39,7 +39,7 @@ static int __exfat_write_inode(struct inode *inode, int sync) + if (is_dir && ei->dir.dir == sbi->root_dir && ei->entry == -1) + return 0; + +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + + /* get the directory entry of given file or directory */ + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES); +@@ -167,7 +167,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, + } + + if (*clu == EXFAT_EOF_CLUSTER) { +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + + new_clu.dir = (last_clu == EXFAT_EOF_CLUSTER) ? + EXFAT_EOF_CLUSTER : last_clu + 1; +diff --git a/src/namei.c b/src/namei.c +index 126ed3ba8f4791859a11bdfac05a1b71847b2e22..e73f20f66cb2e1c1c3c20b4a62bad8210fda6e8b 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -562,10 +562,10 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, + int err; + + mutex_lock(&EXFAT_SB(sb)->s_lock); +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE, + &info); +- exfat_set_vol_flags(sb, VOL_CLEAN); ++ exfat_clear_volume_dirty(sb); + if (err) + goto unlock; + +@@ -834,7 +834,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) + num_entries++; + brelse(bh); + +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + /* update the directory entry */ + if (exfat_remove_entries(dir, &cdir, entry, 0, num_entries)) { + err = -EIO; +@@ -843,7 +843,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) + + /* This doesn't modify ei */ + ei->dir.dir = DIR_DELETED; +- exfat_set_vol_flags(sb, VOL_CLEAN); ++ exfat_clear_volume_dirty(sb); + + inode_inc_iversion(dir); + dir->i_mtime = dir->i_atime = current_time(dir); +@@ -873,10 +873,10 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) + int err; + + mutex_lock(&EXFAT_SB(sb)->s_lock); +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR, + &info); +- exfat_set_vol_flags(sb, VOL_CLEAN); ++ exfat_clear_volume_dirty(sb); + if (err) + goto unlock; + +@@ -1001,14 +1001,14 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + num_entries++; + brelse(bh); + +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries); + if (err) { + exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err); + goto unlock; + } + ei->dir.dir = DIR_DELETED; +- exfat_set_vol_flags(sb, VOL_CLEAN); ++ exfat_clear_volume_dirty(sb); + + inode_inc_iversion(dir); + dir->i_mtime = dir->i_atime = current_time(dir); +@@ -1300,7 +1300,7 @@ static int __exfat_rename(struct inode *old_parent_inode, + if (ret) + goto out; + +- exfat_set_vol_flags(sb, VOL_DIRTY); ++ exfat_set_volume_dirty(sb); + + if (olddir.dir == newdir.dir) + ret = exfat_rename_file(new_parent_inode, &olddir, dentry, +@@ -1355,7 +1355,7 @@ static int __exfat_rename(struct inode *old_parent_inode, + */ + new_ei->dir.dir = DIR_DELETED; + } +- exfat_set_vol_flags(sb, VOL_CLEAN); ++ exfat_clear_volume_dirty(sb); + out: + return ret; + } +diff --git a/src/super.c b/src/super.c +index b5bf6dedbe1159fb4aa0b501ab7e5b8f2051a063..3b6a1659892ffd6ee3a94f6aa0f8bfca65fba9ae 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -63,7 +63,7 @@ static int exfat_sync_fs(struct super_block *sb, int wait) + /* If there are some dirty buffers in the bdev inode */ + mutex_lock(&sbi->s_lock); + sync_blockdev(sb->s_bdev); +- if (exfat_set_vol_flags(sb, VOL_CLEAN)) ++ if (exfat_clear_volume_dirty(sb)) + err = -EIO; + mutex_unlock(&sbi->s_lock); + return err; +@@ -96,17 +96,20 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) + return 0; + } + +-int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) ++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; ++ + /* flags are not changed */ +- if (sbi->vol_flag == new_flag) ++ if (sbi->vol_flags == new_flags) + return 0; + +- sbi->vol_flag = new_flag; ++ sbi->vol_flags = new_flags; + + /* skip updating volume dirty flag, + * if this volume has been mounted with read-only +@@ -114,9 +117,9 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) + if (sb_rdonly(sb)) + return 0; + +- p_boot->vol_flags = cpu_to_le16(new_flag); ++ p_boot->vol_flags = cpu_to_le16(new_flags); + +- if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->boot_bh)) ++ if ((new_flags & VOLUME_DIRTY) && !buffer_dirty(sbi->boot_bh)) + sync = true; + else + sync = false; +@@ -129,6 +132,20 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) + return 0; + } + ++int exfat_set_volume_dirty(struct super_block *sb) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ return exfat_set_vol_flags(sb, sbi->vol_flags | VOLUME_DIRTY); ++} ++ ++int exfat_clear_volume_dirty(struct super_block *sb) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ ++ return exfat_set_vol_flags(sb, sbi->vol_flags & ~VOLUME_DIRTY); ++} ++ + static int exfat_show_options(struct seq_file *m, struct dentry *root) + { + struct super_block *sb = root->d_sb; +@@ -457,7 +474,8 @@ static int exfat_read_boot_sector(struct super_block *sb) + sbi->dentries_per_clu = 1 << + (sbi->cluster_size_bits - DENTRY_SIZE_BITS); + +- sbi->vol_flag = le16_to_cpu(p_boot->vol_flags); ++ sbi->vol_flags = le16_to_cpu(p_boot->vol_flags); ++ sbi->vol_flags_persistent = sbi->vol_flags & (VOLUME_DIRTY | MEDIA_FAILURE); + sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; + sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; + +@@ -472,9 +490,9 @@ static int exfat_read_boot_sector(struct super_block *sb) + exfat_err(sb, "bogus data start sector"); + return -EINVAL; + } +- if (sbi->vol_flag & VOL_DIRTY) ++ 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_flag & ERR_MEDIUM) ++ if (sbi->vol_flags & MEDIA_FAILURE) + exfat_warn(sb, "Medium has reported failures. Some data may be lost."); + + /* exFAT file size is limited by a disk volume size */ +-- +2.31.1 + diff --git a/SOURCES/0053-exfat-fix-pointer-error-checking.patch b/SOURCES/0053-exfat-fix-pointer-error-checking.patch new file mode 100644 index 0000000..ee202ab --- /dev/null +++ b/SOURCES/0053-exfat-fix-pointer-error-checking.patch @@ -0,0 +1,57 @@ +From d6c9efd92443b23307995f34246c2374056ebbd8 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Wed, 26 Aug 2020 10:18:29 +0900 +Subject: [Backport d6c9efd92443] exfat: fix pointer error checking + +Fix missing result check of exfat_build_inode(). +And use PTR_ERR_OR_ZERO instead of PTR_ERR. + +Signed-off-by: Tetsuhiro Kohada +Signed-off-by: Namjae Jeon +--- + src/namei.c | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/src/namei.c b/src/namei.c +index e73f20f66cb2e1c1c3c20b4a62bad8210fda6e8b..c94ac239f740b0bbce1d9a51b9ea6b820c6b7f33 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -578,7 +578,8 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, + + i_pos = exfat_make_i_pos(&info); + inode = exfat_build_inode(sb, &info, i_pos); +- if (IS_ERR(inode)) ++ err = PTR_ERR_OR_ZERO(inode); ++ if (err) + goto unlock; + + inode_inc_iversion(inode); +@@ -745,10 +746,9 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, + + i_pos = exfat_make_i_pos(&info); + inode = exfat_build_inode(sb, &info, i_pos); +- if (IS_ERR(inode)) { +- err = PTR_ERR(inode); ++ err = PTR_ERR_OR_ZERO(inode); ++ if (err) + goto unlock; +- } + + i_mode = inode->i_mode; + alias = d_find_alias(inode); +@@ -890,10 +890,9 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) + + i_pos = exfat_make_i_pos(&info); + inode = exfat_build_inode(sb, &info, i_pos); +- if (IS_ERR(inode)) { +- err = PTR_ERR(inode); ++ err = PTR_ERR_OR_ZERO(inode); ++ if (err) + goto unlock; +- } + + inode_inc_iversion(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = +-- +2.31.1 + diff --git a/SOURCES/0054-exfat-fix-use-of-uninitialized-spinlock-on-error-pat.patch b/SOURCES/0054-exfat-fix-use-of-uninitialized-spinlock-on-error-pat.patch new file mode 100644 index 0000000..84e15a0 --- /dev/null +++ b/SOURCES/0054-exfat-fix-use-of-uninitialized-spinlock-on-error-pat.patch @@ -0,0 +1,136 @@ +From 8ff006e57ad3a25f909c456d053aa498b6673a39 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Tue, 29 Sep 2020 09:09:49 +0900 +Subject: [Backport 8ff006e57ad3] exfat: fix use of uninitialized spinlock on + error path + +syzbot reported warning message: + +Call Trace: + __dump_stack lib/dump_stack.c:77 [inline] + dump_stack+0x1d6/0x29e lib/dump_stack.c:118 + register_lock_class+0xf06/0x1520 kernel/locking/lockdep.c:893 + __lock_acquire+0xfd/0x2ae0 kernel/locking/lockdep.c:4320 + lock_acquire+0x148/0x720 kernel/locking/lockdep.c:5029 + __raw_spin_lock include/linux/spinlock_api_smp.h:142 [inline] + _raw_spin_lock+0x2a/0x40 kernel/locking/spinlock.c:151 + spin_lock include/linux/spinlock.h:354 [inline] + exfat_cache_inval_inode+0x30/0x280 src/cache.c:226 + exfat_evict_inode+0x124/0x270 src/inode.c:660 + evict+0x2bb/0x6d0 fs/inode.c:576 + exfat_fill_super+0x1e07/0x27d0 src/super.c:681 + get_tree_bdev+0x3e9/0x5f0 fs/super.c:1342 + vfs_get_tree+0x88/0x270 fs/super.c:1547 + do_new_mount fs/namespace.c:2875 [inline] + path_mount+0x179d/0x29e0 fs/namespace.c:3192 + do_mount fs/namespace.c:3205 [inline] + __do_sys_mount fs/namespace.c:3413 [inline] + __se_sys_mount+0x126/0x180 fs/namespace.c:3390 + do_syscall_64+0x31/0x70 arch/x86/entry/common.c:46 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 + +If exfat_read_root() returns an error, spinlock is used in +exfat_evict_inode() without initialization. This patch combines +exfat_cache_init_inode() with exfat_inode_init_once() to initialize +spinlock by slab constructor. + +Fixes: c35b6810c495 ("exfat: add exfat cache") +Cc: stable@vger.kernel.org # v5.7+ +Reported-by: syzbot +Signed-off-by: Namjae Jeon +--- + src/cache.c | 11 ----------- + src/exfat_fs.h | 3 ++- + src/inode.c | 2 -- + src/super.c | 5 ++++- + 4 files changed, 6 insertions(+), 15 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 03d0824fc368a5b10241082e271dd4bfefd26ada..5a2f119b7e8c79c6e6c917dd66f12b10acef00b5 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -17,7 +17,6 @@ + #include "exfat_raw.h" + #include "exfat_fs.h" + +-#define EXFAT_CACHE_VALID 0 + #define EXFAT_MAX_CACHE 16 + + struct exfat_cache { +@@ -61,16 +60,6 @@ void exfat_cache_shutdown(void) + kmem_cache_destroy(exfat_cachep); + } + +-void exfat_cache_init_inode(struct inode *inode) +-{ +- struct exfat_inode_info *ei = EXFAT_I(inode); +- +- spin_lock_init(&ei->cache_lru_lock); +- ei->nr_caches = 0; +- ei->cache_valid_id = EXFAT_CACHE_VALID + 1; +- INIT_LIST_HEAD(&ei->cache_lru); +-} +- + static inline struct exfat_cache *exfat_cache_alloc(void) + { + return kmem_cache_alloc(exfat_cachep, GFP_NOFS); +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 95d717f8620cd06cd29909ca07a17208dfd12325..c013fe931d9c17aaed61c75d0d9c41e67cd67907 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -248,6 +248,8 @@ struct exfat_sb_info { + struct rcu_head rcu; + }; + ++#define EXFAT_CACHE_VALID 0 ++ + /* + * EXFAT file system inode in-memory data + */ +@@ -428,7 +430,6 @@ extern const struct dentry_operations exfat_utf8_dentry_ops; + /* cache.c */ + int exfat_cache_init(void); + void exfat_cache_shutdown(void); +-void exfat_cache_init_inode(struct inode *inode); + void exfat_cache_inval_inode(struct inode *inode); + int exfat_get_cluster(struct inode *inode, unsigned int cluster, + unsigned int *fclus, unsigned int *dclus, +diff --git a/src/inode.c b/src/inode.c +index 7f90204adef53027dd1eb4348141394af5548b1b..a6de17cac3dfd5566832da7cbabb5635bb186a1a 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -611,8 +611,6 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) + ei->i_crtime = info->crtime; + inode->i_atime = info->atime; + +- exfat_cache_init_inode(inode); +- + return 0; + } + +diff --git a/src/super.c b/src/super.c +index 3b6a1659892ffd6ee3a94f6aa0f8bfca65fba9ae..60b941ba557b41fae3ea6b16b3041c3aecdc2b93 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -376,7 +376,6 @@ static int exfat_read_root(struct inode *inode) + inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = + current_time(inode); + exfat_truncate_atime(&inode->i_atime); +- exfat_cache_init_inode(inode); + return 0; + } + +@@ -763,6 +762,10 @@ static void exfat_inode_init_once(void *foo) + { + struct exfat_inode_info *ei = (struct exfat_inode_info *)foo; + ++ spin_lock_init(&ei->cache_lru_lock); ++ ei->nr_caches = 0; ++ ei->cache_valid_id = EXFAT_CACHE_VALID + 1; ++ INIT_LIST_HEAD(&ei->cache_lru); + INIT_HLIST_NODE(&ei->i_hash_fat); + inode_init_once(&ei->vfs_inode); + } +-- +2.31.1 + diff --git a/SOURCES/0055-exfat-fix-misspellings-using-codespell-tool.patch b/SOURCES/0055-exfat-fix-misspellings-using-codespell-tool.patch new file mode 100644 index 0000000..0251d11 --- /dev/null +++ b/SOURCES/0055-exfat-fix-misspellings-using-codespell-tool.patch @@ -0,0 +1,66 @@ +From 9e456aeaacb6b44c5d8f858800d53c1958daad3d Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Fri, 14 Aug 2020 11:03:46 +0900 +Subject: [Backport 9e456aeaacb6] exfat: fix misspellings using codespell tool + +Sedat reported typos using codespell tool. + + $ codespell src/*.c | grep -v iput + src/namei.c:293: upto ==> up to + src/nls.c:14: tabel ==> table + + $ codespell src/*.h + src/exfat_fs.h:133: usally ==> usually + +Fix typos found by codespell. + +Reported-by: Sedat Dilek +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 2 +- + src/namei.c | 2 +- + src/nls.c | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index c013fe931d9c17aaed61c75d0d9c41e67cd67907..49af03660d6c31423864e866db70c34df943c691 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -128,7 +128,7 @@ enum { + + struct exfat_dentry_namebuf { + char *lfn; +- int lfnbuf_len; /* usally MAX_UNINAME_BUF_SIZE */ ++ int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ + }; + + /* unicode name structure */ +diff --git a/src/namei.c b/src/namei.c +index c94ac239f740b0bbce1d9a51b9ea6b820c6b7f33..0b12033e15777966a05bdec5492eacd86b262e11 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -290,7 +290,7 @@ static int exfat_check_max_dentries(struct inode *inode) + { + if (EXFAT_B_TO_DEN(i_size_read(inode)) >= MAX_EXFAT_DENTRIES) { + /* +- * exFAT spec allows a dir to grow upto 8388608(256MB) ++ * exFAT spec allows a dir to grow up to 8388608(256MB) + * dentries + */ + return -ENOSPC; +diff --git a/src/nls.c b/src/nls.c +index a3c927501e676f3b240eca17106b07a296d99e6f..675d0e7058c5a01afe9bdcba4c915e983184c639 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -11,7 +11,7 @@ + #include "exfat_raw.h" + #include "exfat_fs.h" + +-/* Upcase tabel macro */ ++/* Upcase table macro */ + #define EXFAT_NUM_UPCASE (2918) + #define UTBL_COUNT (0x10000) + +-- +2.31.1 + diff --git a/SOURCES/0056-exfat-use-i_blocksize-to-get-blocksize.patch b/SOURCES/0056-exfat-use-i_blocksize-to-get-blocksize.patch new file mode 100644 index 0000000..8f3476f --- /dev/null +++ b/SOURCES/0056-exfat-use-i_blocksize-to-get-blocksize.patch @@ -0,0 +1,30 @@ +From 45882a6a0dbd1189defae1a449152f8a8168c274 Mon Sep 17 00:00:00 2001 +From: Xianting Tian +Date: Sat, 15 Aug 2020 18:57:07 +0800 +Subject: [Backport 45882a6a0dbd] exfat: use i_blocksize() to get blocksize + +We alreday has the interface i_blocksize() to get blocksize, +so use it. + +Signed-off-by: Xianting Tian +Signed-off-by: Namjae Jeon +--- + src/file.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/file.c b/src/file.c +index f41f523a58ad4dab6f7c4d3edef6efce21348ce5..4831a39632a18d65ef4901d355f2dc58700b8e45 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -229,7 +229,7 @@ void exfat_truncate(struct inode *inode, loff_t size) + { + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); +- unsigned int blocksize = 1 << inode->i_blkbits; ++ unsigned int blocksize = i_blocksize(inode); + loff_t aligned_size; + int err; + +-- +2.31.1 + diff --git a/SOURCES/0057-exfat-eliminate-dead-code-in-exfat_find.patch b/SOURCES/0057-exfat-eliminate-dead-code-in-exfat_find.patch new file mode 100644 index 0000000..572fade --- /dev/null +++ b/SOURCES/0057-exfat-eliminate-dead-code-in-exfat_find.patch @@ -0,0 +1,181 @@ +From 188df41f212c9282f6cb05b832383ffca3c66893 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Wed, 2 Sep 2020 16:53:06 +0900 +Subject: [Backport 188df41f212c] exfat: eliminate dead code in exfat_find() + +The exfat_find_dir_entry() called by exfat_find() doesn't return -EEXIST. +Therefore, the root-dir information setting is never executed. + +Signed-off-by: Tetsuhiro Kohada +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 1 - + src/namei.c | 120 +++++++++++++++++++---------------------------- + 2 files changed, 47 insertions(+), 74 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 573659bfbc5542f20d1181e95ef292f341aab0d6..a9b13ae3f325fcbf628657981055b91b5079d5f6 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -911,7 +911,6 @@ enum { + /* + * return values: + * >= 0 : return dir entiry position with the name in dir +- * -EEXIST : (root dir, ".") it is the root dir itself + * -ENOENT : entry with the name does not exist + * -EIO : I/O error + */ +diff --git a/src/namei.c b/src/namei.c +index 0b12033e15777966a05bdec5492eacd86b262e11..b966b9120c9ca25e8519b05bdacc5e17ced0f9da 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -604,6 +604,8 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + struct super_block *sb = dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *ei = EXFAT_I(dir); ++ struct exfat_dentry *ep, *ep2; ++ struct exfat_entry_set_cache *es; + + if (qname->len == 0) + return -ENOENT; +@@ -629,91 +631,63 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, + num_entries, TYPE_ALL); + +- if ((dentry < 0) && (dentry != -EEXIST)) ++ if (dentry < 0) + return dentry; /* -error value */ + + memcpy(&info->dir, &cdir.dir, sizeof(struct exfat_chain)); + info->entry = dentry; + info->num_subdirs = 0; + +- /* root directory itself */ +- if (unlikely(dentry == -EEXIST)) { +- int num_clu = 0; ++ es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); ++ if (!es) ++ return -EIO; ++ ep = exfat_get_dentry_cached(es, 0); ++ ep2 = exfat_get_dentry_cached(es, 1); ++ ++ info->type = exfat_get_entry_type(ep); ++ info->attr = le16_to_cpu(ep->dentry.file.attr); ++ info->size = le64_to_cpu(ep2->dentry.stream.valid_size); ++ if ((info->type == TYPE_FILE) && (info->size == 0)) { ++ info->flags = ALLOC_NO_FAT_CHAIN; ++ info->start_clu = EXFAT_EOF_CLUSTER; ++ } else { ++ info->flags = ep2->dentry.stream.flags; ++ info->start_clu = ++ le32_to_cpu(ep2->dentry.stream.start_clu); ++ } + +- info->type = TYPE_DIR; +- info->attr = ATTR_SUBDIR; +- info->flags = ALLOC_FAT_CHAIN; +- info->start_clu = sbi->root_dir; +- memset(&info->crtime, 0, sizeof(info->crtime)); +- memset(&info->mtime, 0, sizeof(info->mtime)); +- memset(&info->atime, 0, sizeof(info->atime)); +- +- exfat_chain_set(&cdir, sbi->root_dir, 0, ALLOC_FAT_CHAIN); +- if (exfat_count_num_clusters(sb, &cdir, &num_clu)) +- return -EIO; +- info->size = num_clu << sbi->cluster_size_bits; ++ exfat_get_entry_time(sbi, &info->crtime, ++ ep->dentry.file.create_tz, ++ ep->dentry.file.create_time, ++ ep->dentry.file.create_date, ++ ep->dentry.file.create_time_cs); ++ exfat_get_entry_time(sbi, &info->mtime, ++ ep->dentry.file.modify_tz, ++ ep->dentry.file.modify_time, ++ ep->dentry.file.modify_date, ++ ep->dentry.file.modify_time_cs); ++ exfat_get_entry_time(sbi, &info->atime, ++ ep->dentry.file.access_tz, ++ ep->dentry.file.access_time, ++ ep->dentry.file.access_date, ++ 0); ++ exfat_free_dentry_set(es, false); ++ ++ if (ei->start_clu == EXFAT_FREE_CLUSTER) { ++ exfat_fs_error(sb, ++ "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", ++ i_size_read(dir), ei->dir.dir, ei->entry); ++ return -EIO; ++ } + ++ if (info->type == TYPE_DIR) { ++ exfat_chain_set(&cdir, info->start_clu, ++ EXFAT_B_TO_CLU(info->size, sbi), info->flags); + count = exfat_count_dir_entries(sb, &cdir); + if (count < 0) + return -EIO; + +- info->num_subdirs = count; +- } else { +- struct exfat_dentry *ep, *ep2; +- struct exfat_entry_set_cache *es; +- +- es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); +- if (!es) +- return -EIO; +- ep = exfat_get_dentry_cached(es, 0); +- ep2 = exfat_get_dentry_cached(es, 1); +- +- info->type = exfat_get_entry_type(ep); +- info->attr = le16_to_cpu(ep->dentry.file.attr); +- info->size = le64_to_cpu(ep2->dentry.stream.valid_size); +- if ((info->type == TYPE_FILE) && (info->size == 0)) { +- info->flags = ALLOC_NO_FAT_CHAIN; +- info->start_clu = EXFAT_EOF_CLUSTER; +- } else { +- info->flags = ep2->dentry.stream.flags; +- info->start_clu = +- le32_to_cpu(ep2->dentry.stream.start_clu); +- } +- +- if (ei->start_clu == EXFAT_FREE_CLUSTER) { +- exfat_fs_error(sb, +- "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", +- i_size_read(dir), ei->dir.dir, ei->entry); +- exfat_free_dentry_set(es, false); +- return -EIO; +- } +- +- exfat_get_entry_time(sbi, &info->crtime, +- ep->dentry.file.create_tz, +- ep->dentry.file.create_time, +- ep->dentry.file.create_date, +- ep->dentry.file.create_time_cs); +- exfat_get_entry_time(sbi, &info->mtime, +- ep->dentry.file.modify_tz, +- ep->dentry.file.modify_time, +- ep->dentry.file.modify_date, +- ep->dentry.file.modify_time_cs); +- exfat_get_entry_time(sbi, &info->atime, +- ep->dentry.file.access_tz, +- ep->dentry.file.access_time, +- ep->dentry.file.access_date, +- 0); +- exfat_free_dentry_set(es, false); +- +- if (info->type == TYPE_DIR) { +- exfat_chain_set(&cdir, info->start_clu, +- EXFAT_B_TO_CLU(info->size, sbi), info->flags); +- count = exfat_count_dir_entries(sb, &cdir); +- if (count < 0) +- return -EIO; +- +- info->num_subdirs = count + EXFAT_MIN_SUBDIR; +- } ++ info->num_subdirs = count + EXFAT_MIN_SUBDIR; + } + return 0; + } +-- +2.31.1 + diff --git a/SOURCES/0058-exfat-remove-useless-directory-scan-in-exfat_add_ent.patch b/SOURCES/0058-exfat-remove-useless-directory-scan-in-exfat_add_ent.patch new file mode 100644 index 0000000..3919ef8 --- /dev/null +++ b/SOURCES/0058-exfat-remove-useless-directory-scan-in-exfat_add_ent.patch @@ -0,0 +1,43 @@ +From 6c958a09555515684947d94bfcfa8e8a414f0572 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Fri, 11 Sep 2020 13:44:39 +0900 +Subject: [Backport 6c958a095555] exfat: remove useless directory scan in + exfat_add_entry() + +There is nothing in directory just created, so there is no need to scan. + +Signed-off-by: Tetsuhiro Kohada +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/namei.c | 11 +---------- + 1 file changed, 1 insertion(+), 10 deletions(-) + +diff --git a/src/namei.c b/src/namei.c +index b966b9120c9ca25e8519b05bdacc5e17ced0f9da..803748946ddbf409693ba0016628cb8d0d1dcf8c 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -530,19 +530,10 @@ static int exfat_add_entry(struct inode *inode, const char *path, + info->size = 0; + info->num_subdirs = 0; + } else { +- int count; +- struct exfat_chain cdir; +- + info->attr = ATTR_SUBDIR; + info->start_clu = start_clu; + info->size = clu_size; +- +- exfat_chain_set(&cdir, info->start_clu, +- EXFAT_B_TO_CLU(info->size, sbi), info->flags); +- count = exfat_count_dir_entries(sb, &cdir); +- if (count < 0) +- return -EIO; +- info->num_subdirs = count + EXFAT_MIN_SUBDIR; ++ info->num_subdirs = EXFAT_MIN_SUBDIR; + } + memset(&info->crtime, 0, sizeof(info->crtime)); + memset(&info->mtime, 0, sizeof(info->mtime)); +-- +2.31.1 + diff --git a/SOURCES/0059-exfat-replace-memcpy-with-structure-assignment.patch b/SOURCES/0059-exfat-replace-memcpy-with-structure-assignment.patch new file mode 100644 index 0000000..d5d60f2 --- /dev/null +++ b/SOURCES/0059-exfat-replace-memcpy-with-structure-assignment.patch @@ -0,0 +1,119 @@ +From a7a241686c8f8142afafbd5fa5b9b9b6ea1aa173 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Fri, 11 Sep 2020 13:45:19 +0900 +Subject: [Backport a7a241686c8f] exfat: replace memcpy with structure + assignment + +Use structure assignment instead of memcpy. + +Signed-off-by: Tetsuhiro Kohada +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 7 ++----- + src/inode.c | 2 +- + src/namei.c | 15 +++++++-------- + 3 files changed, 10 insertions(+), 14 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index a9b13ae3f325fcbf628657981055b91b5079d5f6..5fa751937932f589c3f633c99ba0efd1aadad57e 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -978,11 +978,8 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, + if (ei->hint_femp.eidx == + EXFAT_HINT_NONE || + candi_empty.eidx <= +- ei->hint_femp.eidx) { +- memcpy(&ei->hint_femp, +- &candi_empty, +- sizeof(candi_empty)); +- } ++ ei->hint_femp.eidx) ++ ei->hint_femp = candi_empty; + } + + brelse(bh); +diff --git a/src/inode.c b/src/inode.c +index a6de17cac3dfd5566832da7cbabb5635bb186a1a..109f9b4c40d3119351695e5f84063882b41907f3 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -556,7 +556,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) + struct exfat_inode_info *ei = EXFAT_I(inode); + loff_t size = info->size; + +- memcpy(&ei->dir, &info->dir, sizeof(struct exfat_chain)); ++ ei->dir = info->dir; + ei->entry = info->entry; + ei->attr = info->attr; + ei->start_clu = info->start_clu; +diff --git a/src/namei.c b/src/namei.c +index 803748946ddbf409693ba0016628cb8d0d1dcf8c..676094f2abe2d13573c7e970cbf595586f46a059 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -318,8 +318,7 @@ static int exfat_find_empty_entry(struct inode *inode, + hint_femp.eidx = EXFAT_HINT_NONE; + + if (ei->hint_femp.eidx != EXFAT_HINT_NONE) { +- memcpy(&hint_femp, &ei->hint_femp, +- sizeof(struct exfat_hint_femp)); ++ hint_femp = ei->hint_femp; + ei->hint_femp.eidx = EXFAT_HINT_NONE; + } + +@@ -519,7 +518,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, + if (ret) + goto out; + +- memcpy(&info->dir, p_dir, sizeof(struct exfat_chain)); ++ info->dir = *p_dir; + info->entry = dentry; + info->flags = ALLOC_NO_FAT_CHAIN; + info->type = type; +@@ -625,7 +624,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + if (dentry < 0) + return dentry; /* -error value */ + +- memcpy(&info->dir, &cdir.dir, sizeof(struct exfat_chain)); ++ info->dir = cdir; + info->entry = dentry; + info->num_subdirs = 0; + +@@ -1030,7 +1029,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, + if (!epnew) + return -EIO; + +- memcpy(epnew, epold, DENTRY_SIZE); ++ *epnew = *epold; + if (exfat_get_entry_type(epnew) == TYPE_FILE) { + epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); + ei->attr |= ATTR_ARCHIVE; +@@ -1050,7 +1049,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, + return -EIO; + } + +- memcpy(epnew, epold, DENTRY_SIZE); ++ *epnew = *epold; + exfat_update_bh(new_bh, sync); + brelse(old_bh); + brelse(new_bh); +@@ -1118,7 +1117,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, + if (!epnew) + return -EIO; + +- memcpy(epnew, epmov, DENTRY_SIZE); ++ *epnew = *epmov; + if (exfat_get_entry_type(epnew) == TYPE_FILE) { + epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); + ei->attr |= ATTR_ARCHIVE; +@@ -1138,7 +1137,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, + return -EIO; + } + +- memcpy(epnew, epmov, DENTRY_SIZE); ++ *epnew = *epmov; + exfat_update_bh(new_bh, IS_DIRSYNC(inode)); + brelse(mov_bh); + brelse(new_bh); +-- +2.31.1 + diff --git a/SOURCES/0060-exfat-remove-rwoffset-in-exfat_inode_info.patch b/SOURCES/0060-exfat-remove-rwoffset-in-exfat_inode_info.patch new file mode 100644 index 0000000..67b58a0 --- /dev/null +++ b/SOURCES/0060-exfat-remove-rwoffset-in-exfat_inode_info.patch @@ -0,0 +1,171 @@ +From 04cee52fb8bdbdb0506460f191ed6fd9e6faf00b Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Thu, 17 Sep 2020 10:39:16 +0900 +Subject: [Backport 04cee52fb8bd] exfat: remove 'rwoffset' in exfat_inode_info + +Remove 'rwoffset' in exfat_inode_info and replace it with the parameter of +exfat_readdir(). +Since rwoffset is referenced only by exfat_readdir(), it is not necessary +a exfat_inode_info's member. +Also, change cpos to point to the next of entry-set, and return the index +of dir-entry via dir_entry->entry. + +Signed-off-by: Tetsuhiro Kohada +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 21 +++++++++------------ + src/exfat_fs.h | 2 -- + src/file.c | 2 -- + src/inode.c | 3 --- + src/super.c | 1 - + 5 files changed, 9 insertions(+), 20 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 5fa751937932f589c3f633c99ba0efd1aadad57e..916797077aad4ab45cd73c36a5012fd2653fef33 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -59,9 +59,9 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, + } + + /* read a directory entry from the opened directory */ +-static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) ++static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_entry *dir_entry) + { +- int i, dentries_per_clu, dentries_per_clu_bits = 0; ++ int i, dentries_per_clu, dentries_per_clu_bits = 0, num_ext; + unsigned int type, clu_offset; + sector_t sector; + struct exfat_chain dir, clu; +@@ -70,7 +70,7 @@ static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *ei = EXFAT_I(inode); +- unsigned int dentry = ei->rwoffset & 0xFFFFFFFF; ++ unsigned int dentry = EXFAT_B_TO_DEN(*cpos) & 0xFFFFFFFF; + struct buffer_head *bh; + + /* check if the given file ID is opened */ +@@ -127,6 +127,7 @@ static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) + continue; + } + ++ num_ext = ep->dentry.file.num_ext; + dir_entry->attr = le16_to_cpu(ep->dentry.file.attr); + exfat_get_entry_time(sbi, &dir_entry->crtime, + ep->dentry.file.create_tz, +@@ -157,12 +158,13 @@ static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) + return -EIO; + dir_entry->size = + le64_to_cpu(ep->dentry.stream.valid_size); ++ dir_entry->entry = dentry; + brelse(bh); + + ei->hint_bmap.off = dentry >> dentries_per_clu_bits; + ei->hint_bmap.clu = clu.dir; + +- ei->rwoffset = ++dentry; ++ *cpos = EXFAT_DEN_TO_B(dentry + 1 + num_ext); + return 0; + } + +@@ -178,7 +180,7 @@ static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) + } + + dir_entry->namebuf.lfn[0] = '\0'; +- ei->rwoffset = dentry; ++ *cpos = EXFAT_DEN_TO_B(dentry); + return 0; + } + +@@ -242,12 +244,10 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) + if (err) + goto unlock; + get_new: +- ei->rwoffset = EXFAT_B_TO_DEN(cpos); +- + if (cpos >= i_size_read(inode)) + goto end_of_dir; + +- err = exfat_readdir(inode, &de); ++ err = exfat_readdir(inode, &cpos, &de); + if (err) { + /* + * At least we tried to read a sector. Move cpos to next sector +@@ -262,13 +262,10 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) + goto end_of_dir; + } + +- cpos = EXFAT_DEN_TO_B(ei->rwoffset); +- + if (!nb->lfn[0]) + goto end_of_dir; + +- i_pos = ((loff_t)ei->start_clu << 32) | +- ((ei->rwoffset - 1) & 0xffffffff); ++ i_pos = ((loff_t)ei->start_clu << 32) | (de.entry & 0xffffffff); + tmp = exfat_iget(sb, i_pos); + if (tmp) { + inum = tmp->i_ino; +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 49af03660d6c31423864e866db70c34df943c691..b8f0e829ecbd26c14788899de066e533b6dbaf1e 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -265,8 +265,6 @@ struct exfat_inode_info { + * the validation of hint_stat. + */ + unsigned int version; +- /* file offset or dentry index for readdir */ +- loff_t rwoffset; + + /* hint for cluster last accessed */ + struct exfat_hint hint_bmap; +diff --git a/src/file.c b/src/file.c +index 4831a39632a18d65ef4901d355f2dc58700b8e45..a92478eabfa4e43f2198ccf9b62b8632b697c8dc 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -208,8 +208,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) + /* hint information */ + ei->hint_bmap.off = EXFAT_EOF_CLUSTER; + ei->hint_bmap.clu = EXFAT_EOF_CLUSTER; +- if (ei->rwoffset > new_size) +- ei->rwoffset = new_size; + + /* hint_stat will be used if this is directory. */ + ei->hint_stat.eidx = 0; +diff --git a/src/inode.c b/src/inode.c +index 109f9b4c40d3119351695e5f84063882b41907f3..730373e0965aff2a8c366b34482c61afeec9cd56 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -114,8 +114,6 @@ 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; + +- ei->rwoffset = EXFAT_CLU_TO_B(clu_offset, sbi); +- + if (EXFAT_I(inode)->i_size_ondisk > 0) + num_clusters = + EXFAT_B_TO_CLU_ROUND_UP(EXFAT_I(inode)->i_size_ondisk, +@@ -567,7 +565,6 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) + ei->hint_stat.eidx = 0; + ei->hint_stat.clu = info->start_clu; + ei->hint_femp.eidx = EXFAT_HINT_NONE; +- ei->rwoffset = 0; + ei->hint_bmap.off = EXFAT_EOF_CLUSTER; + ei->i_pos = 0; + +diff --git a/src/super.c b/src/super.c +index 60b941ba557b41fae3ea6b16b3041c3aecdc2b93..3ffdce5c7384dd4ad416068d78675a40d0d01e64 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -342,7 +342,6 @@ static int exfat_read_root(struct inode *inode) + ei->flags = ALLOC_FAT_CHAIN; + ei->type = TYPE_DIR; + ei->version = 0; +- ei->rwoffset = 0; + ei->hint_bmap.off = EXFAT_EOF_CLUSTER; + ei->hint_stat.eidx = 0; + ei->hint_stat.clu = sbi->root_dir; +-- +2.31.1 + diff --git a/SOURCES/0061-exfat-remove-useless-check-in-exfat_move_file.patch b/SOURCES/0061-exfat-remove-useless-check-in-exfat_move_file.patch new file mode 100644 index 0000000..7ffd98d --- /dev/null +++ b/SOURCES/0061-exfat-remove-useless-check-in-exfat_move_file.patch @@ -0,0 +1,37 @@ +From eae503f7eb0509594076a951e422e29082385c96 Mon Sep 17 00:00:00 2001 +From: Tetsuhiro Kohada +Date: Fri, 11 Sep 2020 13:45:06 +0900 +Subject: [Backport eae503f7eb05] exfat: remove useless check in + exfat_move_file() + +In exfat_move_file(), the identity of source and target directory has been +checked by the caller. +Also, it gets stream.start_clu from file dir-entry, which is an invalid +determination. + +Signed-off-by: Tetsuhiro Kohada +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/namei.c | 5 ----- + 1 file changed, 5 deletions(-) + +diff --git a/src/namei.c b/src/namei.c +index 676094f2abe2d13573c7e970cbf595586f46a059..2932b23a3b6c36ebfb82e5a5e7621c07fbb2822d 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -1094,11 +1094,6 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, + if (!epmov) + return -EIO; + +- /* check if the source and target directory is the same */ +- if (exfat_get_entry_type(epmov) == TYPE_DIR && +- le32_to_cpu(epmov->dentry.stream.start_clu) == p_newdir->dir) +- return -EINVAL; +- + num_old_entries = exfat_count_ext_entries(sb, p_olddir, oldentry, + epmov); + if (num_old_entries < 0) +-- +2.31.1 + diff --git a/SOURCES/0062-PATCH-reduce-boilerplate-in-fsid-handling.patch b/SOURCES/0062-PATCH-reduce-boilerplate-in-fsid-handling.patch new file mode 100644 index 0000000..ee638d1 --- /dev/null +++ b/SOURCES/0062-PATCH-reduce-boilerplate-in-fsid-handling.patch @@ -0,0 +1,30 @@ +From 6d1349c769ea28543bdde20a658cbc93c3bc936d Mon Sep 17 00:00:00 2001 +From: Al Viro +Date: Fri, 18 Sep 2020 16:45:50 -0400 +Subject: [Backport 6d1349c769ea] [PATCH] reduce boilerplate in fsid handling + +Get rid of boilerplate in most of ->statfs() +instances... + +Signed-off-by: Al Viro +--- + src/super.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/super.c b/src/super.c +index 3b6a1659892ffd6ee3a94f6aa0f8bfca65fba9ae..ea17e8ed50674dc3d659fd4475649001d105c279 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -89,8 +89,7 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) + buf->f_blocks = sbi->num_clusters - 2; /* clu 0 & 1 */ + buf->f_bfree = buf->f_blocks - sbi->used_clusters; + buf->f_bavail = buf->f_bfree; +- buf->f_fsid.val[0] = (unsigned int)id; +- buf->f_fsid.val[1] = (unsigned int)(id >> 32); ++ buf->f_fsid = u64_to_fsid(id); + /* Unicode utf16 255 characters */ + buf->f_namelen = EXFAT_MAX_FILE_LEN * NLS_MAX_CHARSET_SIZE; + return 0; +-- +2.31.1 + diff --git a/SOURCES/0063-exfat-Avoid-allocating-upcase-table-using-kcalloc.patch b/SOURCES/0063-exfat-Avoid-allocating-upcase-table-using-kcalloc.patch new file mode 100644 index 0000000..a62c0f1 --- /dev/null +++ b/SOURCES/0063-exfat-Avoid-allocating-upcase-table-using-kcalloc.patch @@ -0,0 +1,80 @@ +From 9eb78c25327548b905598975aa3ded4ef244b94a Mon Sep 17 00:00:00 2001 +From: Artem Labazov <123321artyom@gmail.com> +Date: Mon, 7 Dec 2020 09:04:36 +0900 +Subject: [Backport 9eb78c253275] exfat: Avoid allocating upcase table using + kcalloc() + +The table for Unicode upcase conversion requires an order-5 allocation, +which may fail on a highly-fragmented system: + + pool-udisksd: page allocation failure: order:5, + mode:0x40dc0(GFP_KERNEL|__GFP_COMP|__GFP_ZERO), nodemask=(null), + cpuset=/,mems_allowed=0 + CPU: 4 PID: 3756880 Comm: pool-udisksd Tainted: G U + 5.8.10-200.fc32.x86_64 #1 + Hardware name: Dell Inc. XPS 13 9360/0PVG6D, BIOS 2.13.0 11/14/2019 + Call Trace: + dump_stack+0x6b/0x88 + warn_alloc.cold+0x75/0xd9 + ? _cond_resched+0x16/0x40 + ? __alloc_pages_direct_compact+0x144/0x150 + __alloc_pages_slowpath.constprop.0+0xcfa/0xd30 + ? __schedule+0x28a/0x840 + ? __wait_on_bit_lock+0x92/0xa0 + __alloc_pages_nodemask+0x2df/0x320 + kmalloc_order+0x1b/0x80 + kmalloc_order_trace+0x1d/0xa0 + exfat_create_upcase_table+0x115/0x390 [exfat] + exfat_fill_super+0x3ef/0x7f0 [exfat] + ? sget_fc+0x1d0/0x240 + ? exfat_init_fs_context+0x120/0x120 [exfat] + get_tree_bdev+0x15c/0x250 + vfs_get_tree+0x25/0xb0 + do_mount+0x7c3/0xaf0 + ? copy_mount_options+0xab/0x180 + __x64_sys_mount+0x8e/0xd0 + do_syscall_64+0x4d/0x90 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 + +Make the driver use kvcalloc() to eliminate the issue. + +Fixes: 370e812b3ec1 ("exfat: add nls operations") +Cc: stable@vger.kernel.org #v5.7+ +Signed-off-by: Artem Labazov <123321artyom@gmail.com> +Signed-off-by: Namjae Jeon +--- + src/nls.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/nls.c b/src/nls.c +index 675d0e7058c5a01afe9bdcba4c915e983184c639..314d5407a1be50ac2c789c8a58afda8e1baec413 100644 +--- a/src/nls.c ++++ b/src/nls.c +@@ -659,7 +659,7 @@ static int exfat_load_upcase_table(struct super_block *sb, + unsigned char skip = false; + unsigned short *upcase_table; + +- upcase_table = kcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); ++ upcase_table = kvcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); + if (!upcase_table) + return -ENOMEM; + +@@ -715,7 +715,7 @@ static int exfat_load_default_upcase_table(struct super_block *sb) + unsigned short uni = 0, *upcase_table; + unsigned int index = 0; + +- upcase_table = kcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); ++ upcase_table = kvcalloc(UTBL_COUNT, sizeof(unsigned short), GFP_KERNEL); + if (!upcase_table) + return -ENOMEM; + +@@ -803,5 +803,5 @@ int exfat_create_upcase_table(struct super_block *sb) + + void exfat_free_upcase_table(struct exfat_sb_info *sbi) + { +- kfree(sbi->vol_utbl); ++ kvfree(sbi->vol_utbl); + } +-- +2.31.1 + diff --git a/SOURCES/0064-block-use-an-on-stack-bio-in-blkdev_issue_flush.patch b/SOURCES/0064-block-use-an-on-stack-bio-in-blkdev_issue_flush.patch new file mode 100644 index 0000000..5441f92 --- /dev/null +++ b/SOURCES/0064-block-use-an-on-stack-bio-in-blkdev_issue_flush.patch @@ -0,0 +1,33 @@ +From c6bf3f0e25f4c0f0ecce6cf8d1c589bd9d74d3cf Mon Sep 17 00:00:00 2001 +From: Christoph Hellwig +Date: Tue, 26 Jan 2021 15:52:35 +0100 +Subject: [Backport c6bf3f0e25f4] block: use an on-stack bio in + blkdev_issue_flush + +There is no point in allocating memory for a synchronous flush. + +Signed-off-by: Christoph Hellwig +Reviewed-by: Johannes Thumshirn +Reviewed-by: Chaitanya Kulkarni +Acked-by: Damien Le Moal +Signed-off-by: Jens Axboe +--- + src/file.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/file.c b/src/file.c +index a92478eabfa4e43f2198ccf9b62b8632b697c8dc..183ffdf4d43c5fa781c8b7ddcde8dda4cdce2ac4 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -361,7 +361,7 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) + if (err) + return err; + +- return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL); ++ return blkdev_issue_flush(inode->i_sb->s_bdev); + } + + const struct file_operations exfat_file_operations = { +-- +2.31.1 + diff --git a/SOURCES/0065-exfat-fix-shift-out-of-bounds-in-exfat_fill_super.patch b/SOURCES/0065-exfat-fix-shift-out-of-bounds-in-exfat_fill_super.patch new file mode 100644 index 0000000..cc7314b --- /dev/null +++ b/SOURCES/0065-exfat-fix-shift-out-of-bounds-in-exfat_fill_super.patch @@ -0,0 +1,124 @@ +From 78c276f5495aa53a8beebb627e5bf6a54f0af34f Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 1 Feb 2021 09:23:37 +0900 +Subject: [Backport 78c276f5495a] exfat: fix shift-out-of-bounds in + exfat_fill_super() + +syzbot reported a warning which could cause shift-out-of-bounds issue. + +Call Trace: + __dump_stack lib/dump_stack.c:79 [inline] + dump_stack+0x183/0x22e lib/dump_stack.c:120 + ubsan_epilogue lib/ubsan.c:148 [inline] + __ubsan_handle_shift_out_of_bounds+0x432/0x4d0 lib/ubsan.c:395 + exfat_read_boot_sector src/super.c:471 [inline] + __exfat_fill_super src/super.c:556 [inline] + exfat_fill_super+0x2acb/0x2d00 src/super.c:624 + get_tree_bdev+0x406/0x630 fs/super.c:1291 + vfs_get_tree+0x86/0x270 fs/super.c:1496 + do_new_mount fs/namespace.c:2881 [inline] + path_mount+0x1937/0x2c50 fs/namespace.c:3211 + do_mount fs/namespace.c:3224 [inline] + __do_sys_mount fs/namespace.c:3432 [inline] + __se_sys_mount+0x2f9/0x3b0 fs/namespace.c:3409 + do_syscall_64+0x2d/0x70 arch/x86/entry/common.c:46 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 + +exfat specification describe sect_per_clus_bits field of boot sector +could be at most 25 - sect_size_bits and at least 0. And sect_size_bits +can also affect this calculation, It also needs validation. +This patch add validation for sect_per_clus_bits and sect_size_bits +field of boot sector. + +Fixes: 719c1e182916 ("exfat: add super block operations") +Cc: stable@vger.kernel.org # v5.9+ +Reported-by: syzbot+da4fe66aaadd3c2e2d1c@syzkaller.appspotmail.com +Reviewed-by: Sungjong Seo +Tested-by: Randy Dunlap +Signed-off-by: Namjae Jeon +--- + src/exfat_raw.h | 4 ++++ + src/super.c | 31 ++++++++++++++++++++++++++----- + 2 files changed, 30 insertions(+), 5 deletions(-) + +diff --git a/src/exfat_raw.h b/src/exfat_raw.h +index 6aec6288e1f21981920f802083d591034c3fb6a2..7f39b1c6469c4e6938fc855ee4cd7a51ede9ec57 100644 +--- a/src/exfat_raw.h ++++ b/src/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]; +diff --git a/src/super.c b/src/super.c +index 87be5bfc31eb41bce6dbbe4f9c5e5d9c01d7d829..c6d8d2e534865236d16386575627e0a442a8db52 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -381,8 +381,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; + } +@@ -451,6 +450,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 + +@@ -477,16 +495,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) +-- +2.31.1 + diff --git a/SOURCES/0066-exfat-improve-performance-of-exfat_free_cluster-when.patch b/SOURCES/0066-exfat-improve-performance-of-exfat_free_cluster-when.patch new file mode 100644 index 0000000..c790991 --- /dev/null +++ b/SOURCES/0066-exfat-improve-performance-of-exfat_free_cluster-when.patch @@ -0,0 +1,152 @@ +From f728760aa923f1dd3a4818368dbdbd2c7d63b370 Mon Sep 17 00:00:00 2001 +From: Hyeongseok Kim +Date: Mon, 1 Feb 2021 10:02:46 +0900 +Subject: [Backport f728760aa923] exfat: improve performance of + exfat_free_cluster when using dirsync mount option + +There are stressful update of cluster allocation bitmap when using +dirsync mount option which is doing sync buffer on every cluster bit +clearing. This could result in performance degradation when deleting +big size file. +Fix to update only when the bitmap buffer index is changed would make +less disk access, improving performance especially for truncate operation. + +Testing with Samsung 256GB sdcard, mounted with dirsync option +(mount -t exfat /dev/block/mmcblk0p1 /temp/mount -o dirsync) + +Remove 4GB file, blktrace result. +[Before] : 39 secs. +Total (blktrace): + Reads Queued: 0, 0KiB Writes Queued: 32775, 16387KiB + Read Dispatches: 0, 0KiB Write Dispatches: 32775, 16387KiB + Reads Requeued: 0 Writes Requeued: 0 + Reads Completed: 0, 0KiB Writes Completed: 32775, 16387KiB + Read Merges: 0, 0KiB Write Merges: 0, 0KiB + IO unplugs: 2 Timer unplugs: 0 + +[After] : 1 sec. +Total (blktrace): + Reads Queued: 0, 0KiB Writes Queued: 13, 6KiB + Read Dispatches: 0, 0KiB Write Dispatches: 13, 6KiB + Reads Requeued: 0 Writes Requeued: 0 + Reads Completed: 0, 0KiB Writes Completed: 13, 6KiB + Read Merges: 0, 0KiB Write Merges: 0, 0KiB + IO unplugs: 1 Timer unplugs: 0 + +Signed-off-by: Hyeongseok Kim +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/balloc.c | 4 ++-- + src/exfat_fs.h | 2 +- + src/fatent.c | 43 +++++++++++++++++++++++++++++++++++++------ + 3 files changed, 40 insertions(+), 9 deletions(-) + +diff --git a/src/balloc.c b/src/balloc.c +index a987919686c0d4d339ce71d11887d9510280df77..761c79c3a4ba262870686608f3619c98312f65cb 100644 +--- a/src/balloc.c ++++ b/src/balloc.c +@@ -166,7 +166,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu) + * 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; +@@ -180,7 +180,7 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu) + 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; +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index b8f0e829ecbd26c14788899de066e533b6dbaf1e..764bc645241ed8e401d34d73b28d347d29ceed56 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -408,7 +408,7 @@ int exfat_count_num_clusters(struct super_block *sb, + 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); ++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); + +diff --git a/src/fatent.c b/src/fatent.c +index c3c9afee7418f1871a65757d79f8d4a13fe650c2..7b2e8af17193bfffe00b4e0b8b88cccb34b1970a 100644 +--- a/src/fatent.c ++++ b/src/fatent.c +@@ -157,6 +157,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) + 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; + + /* invalid cluster number */ + if (p_chain->dir == EXFAT_FREE_CLUSTER || +@@ -176,21 +177,51 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) + + clu = p_chain->dir; + ++ 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 { +- exfat_clear_bitmap(inode, clu); +- clu++; ++ 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); +- +- if (exfat_get_next_cluster(sb, &clu)) +- goto dec_used_clus; ++ bool sync = false; ++ unsigned int n_clu = clu; ++ int err = exfat_get_next_cluster(sb, &n_clu); ++ ++ 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); + } + +-- +2.31.1 + diff --git a/SOURCES/0067-attr-handle-idmapped-mounts.patch b/SOURCES/0067-attr-handle-idmapped-mounts.patch new file mode 100644 index 0000000..75094e7 --- /dev/null +++ b/SOURCES/0067-attr-handle-idmapped-mounts.patch @@ -0,0 +1,55 @@ +From 2f221d6f7b881d95de1f356a3097d755ab1e47d4 Mon Sep 17 00:00:00 2001 +From: Christian Brauner +Date: Thu, 21 Jan 2021 14:19:26 +0100 +Subject: [Backport 2f221d6f7b88] attr: handle idmapped mounts + +When file attributes are changed most filesystems rely on the +setattr_prepare(), setattr_copy(), and notify_change() helpers for +initialization and permission checking. Let them handle idmapped mounts. +If the inode is accessed through an idmapped mount map it into the +mount's user namespace. Afterwards the checks are identical to +non-idmapped mounts. If the initial user namespace is passed nothing +changes so non-idmapped mounts will see identical behavior as before. + +Helpers that perform checks on the ia_uid and ia_gid fields in struct +iattr assume that ia_uid and ia_gid are intended values and have already +been mapped correctly at the userspace-kernelspace boundary as we +already do today. If the initial user namespace is passed nothing +changes so non-idmapped mounts will see identical behavior as before. + +Link: https://lore.kernel.org/r/20210121131959.646623-8-christian.brauner@ubuntu.com +Cc: Christoph Hellwig +Cc: David Howells +Cc: Al Viro +Cc: linux-fsdevel@vger.kernel.org +Reviewed-by: Christoph Hellwig +Signed-off-by: Christian Brauner +--- + src/file.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/file.c b/src/file.c +index a92478eabfa4e43f2198ccf9b62b8632b697c8dc..ace35aa8e64b7f416f26cfa194253d27389dfc7f 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -305,7 +305,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) + ATTR_TIMES_SET); + } + +- error = setattr_prepare(dentry, attr); ++ error = setattr_prepare(&init_user_ns, dentry, attr); + attr->ia_valid = ia_valid; + if (error) + goto out; +@@ -340,7 +340,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) + up_write(&EXFAT_I(inode)->truncate_lock); + } + +- setattr_copy(inode, attr); ++ setattr_copy(&init_user_ns, inode, attr); + exfat_truncate_atime(&inode->i_atime); + mark_inode_dirty(inode); + +-- +2.31.1 + diff --git a/SOURCES/0068-stat-handle-idmapped-mounts.patch b/SOURCES/0068-stat-handle-idmapped-mounts.patch new file mode 100644 index 0000000..468651e --- /dev/null +++ b/SOURCES/0068-stat-handle-idmapped-mounts.patch @@ -0,0 +1,40 @@ +From 0d56a4518d5eaf595a24ab2202e171330bb2ed72 Mon Sep 17 00:00:00 2001 +From: Christian Brauner +Date: Thu, 21 Jan 2021 14:19:30 +0100 +Subject: [Backport 0d56a4518d5e] stat: handle idmapped mounts + +The generic_fillattr() helper fills in the basic attributes associated +with an inode. Enable it to handle idmapped mounts. If the inode is +accessed through an idmapped mount map it into the mount's user +namespace before we store the uid and gid. If the initial user namespace +is passed nothing changes so non-idmapped mounts will see identical +behavior as before. + +Link: https://lore.kernel.org/r/20210121131959.646623-12-christian.brauner@ubuntu.com +Cc: Christoph Hellwig +Cc: David Howells +Cc: Al Viro +Cc: linux-fsdevel@vger.kernel.org +Reviewed-by: Christoph Hellwig +Reviewed-by: James Morris +Signed-off-by: Christian Brauner +--- + src/file.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/file.c b/src/file.c +index ace35aa8e64b7f416f26cfa194253d27389dfc7f..e9705b3295d31def9c56d86955da9ef359fd9567 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -273,7 +273,7 @@ int exfat_getattr(const struct path *path, struct kstat *stat, + struct inode *inode = d_backing_inode(path->dentry); + struct exfat_inode_info *ei = EXFAT_I(inode); + +- generic_fillattr(inode, stat); ++ generic_fillattr(&init_user_ns, inode, stat); + exfat_truncate_atime(&stat->atime); + stat->result_mask |= STATX_BTIME; + stat->btime.tv_sec = ei->i_crtime.tv_sec; +-- +2.31.1 + diff --git a/SOURCES/0069-fs-make-helpers-idmap-mount-aware.patch b/SOURCES/0069-fs-make-helpers-idmap-mount-aware.patch new file mode 100644 index 0000000..17e4d07 --- /dev/null +++ b/SOURCES/0069-fs-make-helpers-idmap-mount-aware.patch @@ -0,0 +1,116 @@ +From 549c7297717c32ee53f156cd949e055e601f67bb Mon Sep 17 00:00:00 2001 +From: Christian Brauner +Date: Thu, 21 Jan 2021 14:19:43 +0100 +Subject: [Backport 549c7297717c] fs: make helpers idmap mount aware + +Extend some inode methods with an additional user namespace argument. A +filesystem that is aware of idmapped mounts will receive the user +namespace the mount has been marked with. This can be used for +additional permission checking and also to enable filesystems to +translate between uids and gids if they need to. We have implemented all +relevant helpers in earlier patches. + +As requested we simply extend the exisiting inode method instead of +introducing new ones. This is a little more code churn but it's mostly +mechanical and doesnt't leave us with additional inode methods. + +Link: https://lore.kernel.org/r/20210121131959.646623-25-christian.brauner@ubuntu.com +Cc: Christoph Hellwig +Cc: David Howells +Cc: Al Viro +Cc: linux-fsdevel@vger.kernel.org +Reviewed-by: Christoph Hellwig +Signed-off-by: Christian Brauner +--- + src/exfat_fs.h | 8 +++++--- + src/file.c | 8 +++++--- + src/namei.c | 14 ++++++++------ + 3 files changed, 18 insertions(+), 12 deletions(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index b8f0e829ecbd26c14788899de066e533b6dbaf1e..d905bb9cd2ca61f9f25c0a338e441342af9778d8 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -416,9 +416,11 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count); + 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); +-int exfat_setattr(struct dentry *dentry, struct iattr *attr); +-int exfat_getattr(const struct path *path, struct kstat *stat, +- unsigned int request_mask, unsigned int query_flags); ++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); + int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); + + /* namei.c */ +diff --git a/src/file.c b/src/file.c +index e9705b3295d31def9c56d86955da9ef359fd9567..3aa6eb4de5e3d40b7dd3693435ac5c4dbdda5aa3 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -267,8 +267,9 @@ void exfat_truncate(struct inode *inode, loff_t size) + mutex_unlock(&sbi->s_lock); + } + +-int exfat_getattr(const struct path *path, struct kstat *stat, +- unsigned int request_mask, unsigned int query_flags) ++int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path, ++ struct kstat *stat, unsigned int request_mask, ++ unsigned int query_flags) + { + struct inode *inode = d_backing_inode(path->dentry); + struct exfat_inode_info *ei = EXFAT_I(inode); +@@ -282,7 +283,8 @@ int exfat_getattr(const struct path *path, struct kstat *stat, + return 0; + } + +-int exfat_setattr(struct dentry *dentry, struct iattr *attr) ++int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, ++ struct iattr *attr) + { + struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb); + struct inode *inode = dentry->d_inode; +diff --git a/src/namei.c b/src/namei.c +index 2932b23a3b6c36ebfb82e5a5e7621c07fbb2822d..d9e8ec689c55ca3e8511177de0c01dfe1d3c090a 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -541,8 +541,8 @@ static int exfat_add_entry(struct inode *inode, const char *path, + return ret; + } + +-static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, +- bool excl) ++static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, ++ struct dentry *dentry, umode_t mode, bool excl) + { + struct super_block *sb = dir->i_sb; + struct inode *inode; +@@ -827,7 +827,8 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) + return err; + } + +-static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) ++static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, ++ struct dentry *dentry, umode_t mode) + { + struct super_block *sb = dir->i_sb; + struct inode *inode; +@@ -1318,9 +1319,10 @@ static int __exfat_rename(struct inode *old_parent_inode, + return ret; + } + +-static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, +- struct inode *new_dir, struct dentry *new_dentry, +- unsigned int flags) ++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) + { + struct inode *old_inode, *new_inode; + struct super_block *sb = old_dir->i_sb; +-- +2.31.1 + diff --git a/SOURCES/0070-exfat-fix-erroneous-discard-when-clear-cluster-bit.patch b/SOURCES/0070-exfat-fix-erroneous-discard-when-clear-cluster-bit.patch new file mode 100644 index 0000000..1a1f9d4 --- /dev/null +++ b/SOURCES/0070-exfat-fix-erroneous-discard-when-clear-cluster-bit.patch @@ -0,0 +1,61 @@ +From 77edfc6e51055b61cae2f54c8e6c3bb7c762e4fe Mon Sep 17 00:00:00 2001 +From: Hyeongseok Kim +Date: Thu, 4 Mar 2021 09:15:34 +0900 +Subject: [Backport 77edfc6e5105] exfat: fix erroneous discard when clear + cluster bit + +If mounted with discard option, exFAT issues discard command when clear +cluster bit to remove file. But the input parameter of cluster-to-sector +calculation is abnormally added by reserved cluster size which is 2, +leading to discard unrelated sectors included in target+2 cluster. +With fixing this, remove the wrong comments in set/clear/find bitmap +functions. + +Fixes: 1e49a94cf707 ("exfat: add bitmap operations") +Cc: stable@vger.kernel.org # v5.7+ +Signed-off-by: Hyeongseok Kim +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/balloc.c | 11 +---------- + 1 file changed, 1 insertion(+), 10 deletions(-) + +diff --git a/src/balloc.c b/src/balloc.c +index 761c79c3a4ba262870686608f3619c98312f65cb..411fb0a8da10d40be5610f55f72280d44eb03ee7 100644 +--- a/src/balloc.c ++++ b/src/balloc.c +@@ -141,10 +141,6 @@ 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 i, b; +@@ -162,10 +158,6 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu) + 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, bool sync) + { + int i, b; +@@ -186,8 +178,7 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync) + 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) { +-- +2.31.1 + diff --git a/SOURCES/0071-exfat-introduce-bitmap_lock-for-cluster-bitmap-acces.patch b/SOURCES/0071-exfat-introduce-bitmap_lock-for-cluster-bitmap-acces.patch new file mode 100644 index 0000000..dc7d018 --- /dev/null +++ b/SOURCES/0071-exfat-introduce-bitmap_lock-for-cluster-bitmap-acces.patch @@ -0,0 +1,140 @@ +From 5c2d728507299f84631ab8020d6f0f98f2cb8fc2 Mon Sep 17 00:00:00 2001 +From: Hyeongseok Kim +Date: Tue, 2 Mar 2021 14:05:20 +0900 +Subject: [Backport 5c2d72850729] exfat: introduce bitmap_lock for cluster + bitmap access + +s_lock which is for protecting concurrent access of file operations is +too huge for cluster bitmap protection, so introduce a new bitmap_lock +to narrow the lock range if only need to access cluster bitmap. + +Signed-off-by: Hyeongseok Kim +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/exfat_fs.h | 1 + + src/fatent.c | 37 +++++++++++++++++++++++++++++-------- + src/super.c | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index fa21421a14d9e047add5b9b03c0970b4fd4d7522..e05e0a3152c0e5c415bd042e40d680194f02b49e 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -238,6 +238,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; +diff --git a/src/fatent.c b/src/fatent.c +index 7b2e8af17193bfffe00b4e0b8b88cccb34b1970a..fd6c7fd127620fc405cff2b5ae2d6fae5be25422 100644 +--- a/src/fatent.c ++++ b/src/fatent.c +@@ -151,13 +151,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 || +@@ -230,6 +231,17 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) + 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) + { +@@ -328,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) { +@@ -338,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 */ +@@ -349,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; + } + } +@@ -400,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; + } + +@@ -419,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; + } + +diff --git a/src/super.c b/src/super.c +index c6d8d2e534865236d16386575627e0a442a8db52..d38d17a77e76c45a006abeae532a6ae3170529d7 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -752,6 +752,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); + +-- +2.31.1 + diff --git a/SOURCES/0072-exfat-add-support-ioctl-and-FITRIM-function.patch b/SOURCES/0072-exfat-add-support-ioctl-and-FITRIM-function.patch new file mode 100644 index 0000000..4eb97cc --- /dev/null +++ b/SOURCES/0072-exfat-add-support-ioctl-and-FITRIM-function.patch @@ -0,0 +1,234 @@ +From 654762df2ec7d61b05acc788afbffaba52d658fe Mon Sep 17 00:00:00 2001 +From: Hyeongseok Kim +Date: Thu, 4 Mar 2021 09:20:35 +0900 +Subject: [Backport 654762df2ec7] exfat: add support ioctl and FITRIM function + +Add FITRIM ioctl to enable discarding unused blocks while mounted. +As current exFAT doesn't have generic ioctl handler, add empty ioctl +function first, and add FITRIM handler. + +Signed-off-by: Hyeongseok Kim +Reviewed-by: Chaitanya Kulkarni +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/balloc.c | 80 +++++++++++++++++++++++++++++++++++++++++++++ + src/dir.c | 5 +++ + src/exfat_fs.h | 4 +++ + src/file.c | 53 ++++++++++++++++++++++++++++++ + 4 files changed, 142 insertions(+) + +diff --git a/src/balloc.c b/src/balloc.c +index 411fb0a8da10d40be5610f55f72280d44eb03ee7..78bc87d5a11bf9688dbef42ad9c3b4f0c232c0f1 100644 +--- a/src/balloc.c ++++ b/src/balloc.c +@@ -264,3 +264,83 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count) + *ret_count = count; + return 0; + } ++ ++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/src/dir.c b/src/dir.c +index 916797077aad4ab45cd73c36a5012fd2653fef33..e1d5536de948b3dcd4667cf3074b51462ddb0697 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + +@@ -306,6 +307,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, + }; + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index e05e0a3152c0e5c415bd042e40d680194f02b49e..169d0b602f5e7c06c8889b38d83abcfad2e8382c 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -412,6 +412,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu); + 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; +@@ -421,6 +422,9 @@ + int exfat_getattr(const struct path *path, struct kstat *stat, + unsigned int request_mask, unsigned int query_flags); + 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; +diff --git a/src/file.c b/src/file.c +index f783cf38dd8e885aa1cf33c91ffeaa3b87a120eb..6af0191b648f1601890f32e3df0f5db81cce8ea4 100644 +--- a/src/file.c ++++ b/src/file.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -350,6 +351,54 @@ int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, + 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; +@@ -370,6 +419,10 @@ 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, +-- +2.31.1 + diff --git a/SOURCES/0073-exfat-improve-write-performance-when-dirsync-enabled.patch b/SOURCES/0073-exfat-improve-write-performance-when-dirsync-enabled.patch new file mode 100644 index 0000000..5295aeb --- /dev/null +++ b/SOURCES/0073-exfat-improve-write-performance-when-dirsync-enabled.patch @@ -0,0 +1,132 @@ +From 23befe490ba885bdf757d40b2489134315fef690 Mon Sep 17 00:00:00 2001 +From: Hyeongseok Kim +Date: Mon, 15 Mar 2021 13:12:55 +0900 +Subject: [Backport 23befe490ba8] exfat: improve write performance when dirsync + enabled + +Degradation of write speed caused by frequent disk access for cluster +bitmap update on every cluster allocation could be improved by +selective syncing bitmap buffer. Change to flush bitmap buffer only +for the directory related operations. + +Signed-off-by: Hyeongseok Kim +Acked-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/balloc.c | 4 ++-- + src/dir.c | 2 +- + src/exfat_fs.h | 4 ++-- + src/fatent.c | 4 ++-- + src/inode.c | 3 ++- + src/namei.c | 2 +- + 6 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/src/balloc.c b/src/balloc.c +index 78bc87d5a11bf9688dbef42ad9c3b4f0c232c0f1..cc5cffc4a76910cb2ed3c36f1fcadd4ccb9b1679 100644 +--- a/src/balloc.c ++++ b/src/balloc.c +@@ -141,7 +141,7 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) + kfree(sbi->vol_amap); + } + +-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; +@@ -154,7 +154,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu) + 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; + } + +diff --git a/src/dir.c b/src/dir.c +index e1d5536de948b3dcd4667cf3074b51462ddb0697..7efb1c6d480846937da5436277b6220dd26f2f7c 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -320,7 +320,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; + +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index 169d0b602f5e7c06c8889b38d83abcfad2e8382c..e77fe2f45cf28fe8533276426a73c657446863f6 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -389,7 +389,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); +@@ -408,7 +408,7 @@ 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); ++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); +diff --git a/src/fatent.c b/src/fatent.c +index fd6c7fd127620fc405cff2b5ae2d6fae5be25422..e949e563443c937bc80b13b6cd502a0e0a32c68d 100644 +--- a/src/fatent.c ++++ b/src/fatent.c +@@ -320,7 +320,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) + } + + 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; +@@ -388,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; + } +diff --git a/src/inode.c b/src/inode.c +index 730373e0965aff2a8c366b34482c61afeec9cd56..1803ef3220fdbd6ca51470b736ca3ab596588fc3 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -179,7 +179,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; + +diff --git a/src/namei.c b/src/namei.c +index d9e8ec689c55ca3e8511177de0c01dfe1d3c090a..1f7b3dc66fcd6fd5507f9d52d73d01cb69433afd 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -340,7 +340,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; + +-- +2.31.1 + diff --git a/SOURCES/0074-exfat-speed-up-iterate-lookup-by-fixing-start-point-.patch b/SOURCES/0074-exfat-speed-up-iterate-lookup-by-fixing-start-point-.patch new file mode 100644 index 0000000..dc00a2d --- /dev/null +++ b/SOURCES/0074-exfat-speed-up-iterate-lookup-by-fixing-start-point-.patch @@ -0,0 +1,125 @@ +From c6e2f52e3051e8d898d38840104638ca8bbcdec2 Mon Sep 17 00:00:00 2001 +From: Hyeongseok Kim +Date: Mon, 22 Mar 2021 12:53:36 +0900 +Subject: [Backport c6e2f52e3051] exfat: speed up iterate/lookup by fixing + start point of traversing cluster chain + +When directory iterate and lookup is called, there's a buggy rewinding +of start point for traversing cluster chain to the parent directory +entry's first cluster. This caused repeated cluster chain traversing +from the first entry of the parent directory that would show worse +performance if huge amounts of files exist under the parent directory. +Fix not to rewind, make continue from currently referenced cluster and +dir entry. + +Tested with 50,000 files under single directory / 256GB sdcard, +with command "time ls -l > /dev/null", +Before : 0m08.69s real 0m00.27s user 0m05.91s system +After : 0m07.01s real 0m00.25s user 0m04.34s system + +Signed-off-by: Hyeongseok Kim +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/dir.c | 19 +++++++++++++------ + src/exfat_fs.h | 2 +- + src/namei.c | 9 ++++++++- + 3 files changed, 22 insertions(+), 8 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index 7efb1c6d480846937da5436277b6220dd26f2f7c..c4523648472a088a2477ea9c3ffe7416c498b523 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -147,7 +147,7 @@ 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, +@@ -911,14 +911,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; +@@ -995,6 +1000,8 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, + + 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; +diff --git a/src/exfat_fs.h b/src/exfat_fs.h +index e77fe2f45cf28fe8533276426a73c657446863f6..1d6da61157c9364996da966537da92681cae5e31 100644 +--- a/src/exfat_fs.h ++++ b/src/exfat_fs.h +@@ -457,7 +457,7 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es); + int exfat_calc_num_entries(struct exfat_uni_name *p_uniname); + int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, + struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, +- int num_entries, unsigned int type); ++ 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); +diff --git a/src/namei.c b/src/namei.c +index 1f7b3dc66fcd6fd5507f9d52d73d01cb69433afd..24b41103d1cc08cbfc967cba9d60d14b579a932b 100644 +--- a/src/namei.c ++++ b/src/namei.c +@@ -596,6 +596,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; +@@ -619,7 +621,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, + + /* search the file name for directories */ + dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, +- num_entries, TYPE_ALL); ++ num_entries, TYPE_ALL, &hint_opt); + + if (dentry < 0) + return dentry; /* -error value */ +@@ -628,6 +630,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; +-- +2.31.1 + diff --git a/SOURCES/0075-mm-require-set_page_dirty-to-be-explicitly-wired-up.patch b/SOURCES/0075-mm-require-set_page_dirty-to-be-explicitly-wired-up.patch new file mode 100644 index 0000000..d1761a9 --- /dev/null +++ b/SOURCES/0075-mm-require-set_page_dirty-to-be-explicitly-wired-up.patch @@ -0,0 +1,40 @@ +From 0af573780b0b13fceb7fabd49dc1b073cee9a507 Mon Sep 17 00:00:00 2001 +From: Christoph Hellwig +Date: Mon, 28 Jun 2021 19:36:12 -0700 +Subject: [Backport 0af573780b0b] mm: require ->set_page_dirty to be explicitly + wired up + +Remove the CONFIG_BLOCK default to __set_page_dirty_buffers and just wire +that method up for the missing instances. + +[hch@lst.de: ecryptfs: add a ->set_page_dirty cludge] + Link: https://lkml.kernel.org/r/20210624125250.536369-1-hch@lst.de + +Link: https://lkml.kernel.org/r/20210614061512.3966143-4-hch@lst.de +Signed-off-by: Christoph Hellwig +Reviewed-by: Greg Kroah-Hartman +Reviewed-by: Jan Kara +Cc: Al Viro +Cc: Matthew Wilcox (Oracle) +Cc: Tyler Hicks +Signed-off-by: Andrew Morton +Signed-off-by: Linus Torvalds +--- + src/inode.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/inode.c b/src/inode.c +index 1803ef3220fdbd6ca51470b736ca3ab596588fc3..ca37d43443612c61200b07b69e40993ea0eff8d2 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -491,6 +491,7 @@ int exfat_block_truncate_page(struct inode *inode, loff_t from) + } + + static const struct address_space_operations exfat_aops = { ++ .set_page_dirty = __set_page_dirty_buffers, + .readpage = exfat_readpage, + .readpages = exfat_readpages, + .writepage = exfat_writepage, +-- +2.31.1 + diff --git a/SOURCES/0076-exfat-avoid-incorrectly-releasing-for-root-inode.patch b/SOURCES/0076-exfat-avoid-incorrectly-releasing-for-root-inode.patch new file mode 100644 index 0000000..e6f8200 --- /dev/null +++ b/SOURCES/0076-exfat-avoid-incorrectly-releasing-for-root-inode.patch @@ -0,0 +1,33 @@ +From 839a534f1e853f1aec100d06040c0037b89c2dc3 Mon Sep 17 00:00:00 2001 +From: Chen Li +Date: Wed, 9 Jun 2021 11:48:55 +0800 +Subject: [Backport 839a534f1e85] exfat: avoid incorrectly releasing for root + inode + +In d_make_root, when we fail to allocate dentry for root inode, +we will iput root inode and returned value is NULL in this function. + +So we do not need to release this inode again at d_make_root's caller. + +Signed-off-by: Chen Li +Signed-off-by: Namjae Jeon +--- + src/super.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/super.c b/src/super.c +index d38d17a77e76c45a006abeae532a6ae3170529d7..5539ffc20d16413a8e007aa8b00e8254887be66d 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -690,7 +690,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; +-- +2.31.1 + diff --git a/SOURCES/0077-exfat-handle-wrong-stream-entry-size-in-exfat_readdi.patch b/SOURCES/0077-exfat-handle-wrong-stream-entry-size-in-exfat_readdi.patch new file mode 100644 index 0000000..1b22df3 --- /dev/null +++ b/SOURCES/0077-exfat-handle-wrong-stream-entry-size-in-exfat_readdi.patch @@ -0,0 +1,69 @@ +From 1e5654de0f51890f88abd409ebf4867782431e81 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Fri, 11 Jun 2021 09:40:24 +0900 +Subject: [Backport 1e5654de0f51] exfat: handle wrong stream entry size in + exfat_readdir() + +The compatibility issue between linux exfat and exfat of some camera +company was reported from Florian. In their exfat, if the number of files +exceeds any limit, the DataLength in stream entry of the directory is +no longer updated. So some files created from camera does not show in +linux exfat. because linux exfat doesn't allow that cpos becomes larger +than DataLength of stream entry. This patch check DataLength in stream +entry only if the type is ALLOC_NO_FAT_CHAIN and add the check ensure +that dentry offset does not exceed max dentries size(256 MB) to avoid +the circular FAT chain issue. + +Fixes: ca06197382bd ("exfat: add directory operations") +Cc: stable@vger.kernel.org # v5.9 +Reported-by: Florian Cramer +Reviewed-by: Sungjong Seo +Tested-by: Chris Down +Signed-off-by: Namjae Jeon +--- + src/dir.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/dir.c b/src/dir.c +index c4523648472a088a2477ea9c3ffe7416c498b523..cb1c0d8c17141b1a0b1fdb70167cce3ec3c5446b 100644 +--- a/src/dir.c ++++ b/src/dir.c +@@ -63,7 +63,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; ++ unsigned int type, clu_offset, max_dentries; + sector_t sector; + struct exfat_chain dir, clu; + struct exfat_uni_name uni_name; +@@ -86,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); +@@ -109,7 +111,7 @@ 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++) { +@@ -245,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); +-- +2.31.1 + diff --git a/SOURCES/0078-exfat-fix-incorrect-loading-of-i_blocks-for-large-fi.patch b/SOURCES/0078-exfat-fix-incorrect-loading-of-i_blocks-for-large-fi.patch new file mode 100644 index 0000000..0d34466 --- /dev/null +++ b/SOURCES/0078-exfat-fix-incorrect-loading-of-i_blocks-for-large-fi.patch @@ -0,0 +1,35 @@ +From 0c336d6e33f4bedc443404c89f43c91c8bd9ee11 Mon Sep 17 00:00:00 2001 +From: Sungjong Seo +Date: Tue, 19 Oct 2021 15:14:21 +0900 +Subject: [Backport 0c336d6e33f4] exfat: fix incorrect loading of i_blocks for + large files + +When calculating i_blocks, there was a mistake that was masked with a +32-bit variable. So i_blocks for files larger than 4 GiB had incorrect +values. Mask with a 64-bit variable instead of 32-bit one. + +Fixes: 5f2aa075070c ("exfat: add inode operations") +Cc: stable@vger.kernel.org # v5.7+ +Reported-by: Ganapathi Kamath +Signed-off-by: Sungjong Seo +Signed-off-by: Namjae Jeon +--- + src/inode.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/inode.c b/src/inode.c +index ca37d43443612c61200b07b69e40993ea0eff8d2..1c7aa1ea4724cf03e49daef0e730dc620200088f 100644 +--- a/src/inode.c ++++ b/src/inode.c +@@ -604,7 +604,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; +-- +2.31.1 + diff --git a/SOURCES/9001-Compat-for-missing-inode-method-free_inode.patch b/SOURCES/9001-Compat-for-missing-inode-method-free_inode.patch new file mode 100644 index 0000000..f77e6a1 --- /dev/null +++ b/SOURCES/9001-Compat-for-missing-inode-method-free_inode.patch @@ -0,0 +1,40 @@ +From 8c3318adf46a7f2d3026ea08f66e8483a4971ca4 Mon Sep 17 00:00:00 2001 +From: Peter Georg +Date: Mon, 23 Aug 2021 14:27:25 +0200 +Subject: [PATCH] Compat for missing inode method ->free_inode() + +Introduced upstream in fdb0da89f4ba0c74d7d3b9e6f471e96a5766820b +--- + src/super.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/super.c b/src/super.c +index a3d3b39..af81722 100644 +--- a/src/super.c ++++ b/src/super.c +@@ -191,14 +191,20 @@ static struct inode *exfat_alloc_inode(struct super_block *sb) + return &ei->vfs_inode; + } + +-static void exfat_free_inode(struct inode *inode) ++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); ++} ++ + static const struct super_operations exfat_sops = { + .alloc_inode = exfat_alloc_inode, +- .free_inode = exfat_free_inode, ++ .destroy_inode = exfat_destroy_inode, + .write_inode = exfat_write_inode, + .evict_inode = exfat_evict_inode, + .put_super = exfat_put_super, +-- +2.31.1 + diff --git a/SOURCES/9999-enable-exfat.patch b/SOURCES/9999-enable-exfat.patch new file mode 100644 index 0000000..bc95133 --- /dev/null +++ b/SOURCES/9999-enable-exfat.patch @@ -0,0 +1,6 @@ +--- a/src/Makefile ++++ b/src/Makefile +@@ -5 +5,2 @@ +-obj-$(CONFIG_EXFAT_FS) += exfat.o ++obj-m += exfat.o ++ccflags-y += -DCONFIG_EXFAT_DEFAULT_IOCHARSET=\"utf8\" diff --git a/SPECS/kmod-exfat.spec b/SPECS/kmod-exfat.spec index efdc796..9d5f70f 100644 --- a/SPECS/kmod-exfat.spec +++ b/SPECS/kmod-exfat.spec @@ -1,5 +1,7 @@ %global pkg exfat +%global driver_version 4.18.0-0.el8 + %global kernel_version 4.18.0-348.el8 %global _use_internal_dependency_generator 0 @@ -18,14 +20,94 @@ Name: kmod-%{pkg} -Version: 5.16 +Version: 4.18.0.0 Release: 1%{?dist} Summary: exFAT filesystem support License: GPLv2 URL: https://www.kernel.org/ -Source0: %{pkg}-%{version}.tar.xz +Source0: %{pkg}-%{driver_version}.tar.xz +Patch1: 0001-exfat-add-in-memory-and-on-disk-structures-and-heade.patch +Patch2: 0002-exfat-add-super-block-operations.patch +Patch3: 0003-exfat-add-inode-operations.patch +Patch4: 0004-exfat-add-directory-operations.patch +Patch5: 0005-exfat-add-file-operations.patch +Patch6: 0006-exfat-add-fat-entry-operations.patch +Patch7: 0007-exfat-add-bitmap-operations.patch +Patch8: 0008-exfat-add-exfat-cache.patch +Patch9: 0009-exfat-add-misc-operations.patch +Patch10: 0010-exfat-add-nls-operations.patch +Patch11: 0011-exfat-add-Kconfig-and-Makefile.patch +Patch12: 0012-exfat-update-file-system-parameter-handling.patch +Patch13: 0013-exfat-Fix-discard-support.patch +Patch14: 0014-exfat-add-missing-MODULE_ALIAS_FS.patch +Patch15: 0015-exfat-Unify-access-to-the-boot-sector.patch +Patch16: 0016-exfat-remove-bps-mount-option.patch +Patch17: 0017-exfat-properly-set-s_time_gran.patch +Patch18: 0018-exfat-truncate-atimes-to-2s-granularity.patch +Patch19: 0019-exfat-use-iter_file_splice_write.patch +Patch20: 0020-exfat-fix-possible-memory-leak-in-exfat_find.patch +Patch21: 0021-exfat-add-the-dummy-mount-options-to-be-backward-com.patch +#Patch22: 0022-fs-convert-mpage_readpages-to-mpage_readahead.patch +Patch23: 0023-exfat-Simplify-exfat_utf8_d_cmp-for-code-points-abov.patch +Patch24: 0024-exfat-Use-a-more-common-logging-style.patch +Patch25: 0025-exfat-Simplify-exfat_utf8_d_hash-for-code-points-abo.patch +Patch26: 0026-exfat-Remove-unused-functions-exfat_high_surrogate-a.patch +Patch27: 0027-exfat-remove-the-assignment-of-0-to-bool-variable.patch +Patch28: 0028-exfat-replace-time_ms-with-time_cs.patch +Patch29: 0029-exfat-optimize-dir-cache.patch +Patch30: 0030-exfat-redefine-PBR-as-boot_sector.patch +Patch31: 0031-exfat-separate-the-boot-sector-analysis.patch +Patch32: 0032-exfat-add-boot-region-verification.patch +Patch33: 0033-exfat-standardize-checksum-calculation.patch +Patch34: 0034-exfat-remove-unnecessary-reassignment-of-p_uniname-n.patch +Patch35: 0035-exfat-fix-memory-leak-in-exfat_parse_param.patch +Patch36: 0036-exfat-fix-incorrect-update-of-stream-entry-in-__exfa.patch +Patch37: 0037-exfat-fix-range-validation-error-in-alloc-and-free-c.patch +Patch38: 0038-exfat-Fix-potential-use-after-free-in-exfat_load_upc.patch +Patch39: 0039-exfat-Set-the-unused-characters-of-FileName-field-to.patch +Patch40: 0040-exfat-add-missing-brelse-calls-on-error-paths.patch +Patch41: 0041-exfat-call-sync_filesystem-for-read-only-remount.patch +Patch42: 0042-exfat-move-setting-VOL_DIRTY-over-exfat_remove_entri.patch +Patch43: 0043-exfat-flush-dirty-metadata-in-fsync.patch +Patch44: 0044-exfat-fix-overflow-issue-in-exfat_cluster_to_sector.patch +Patch45: 0045-exfat-fix-wrong-hint_stat-initialization-in-exfat_fi.patch +Patch46: 0046-exfat-fix-wrong-size-update-of-stream-entry-by-typo.patch +Patch47: 0047-exfat-fix-name_hash-computation-on-big-endian-system.patch +Patch48: 0048-exfat-remove-EXFAT_SB_DIRTY-flag.patch +Patch49: 0049-exfat-write-multiple-sectors-at-once.patch +Patch50: 0050-exfat-add-error-check-when-updating-dir-entries.patch +Patch51: 0051-exfat-optimize-exfat_zeroed_cluster.patch +Patch52: 0052-exfat-retain-VolumeFlags-properly.patch +Patch53: 0053-exfat-fix-pointer-error-checking.patch +Patch54: 0054-exfat-fix-use-of-uninitialized-spinlock-on-error-pat.patch +Patch55: 0055-exfat-fix-misspellings-using-codespell-tool.patch +Patch56: 0056-exfat-use-i_blocksize-to-get-blocksize.patch +Patch57: 0057-exfat-eliminate-dead-code-in-exfat_find.patch +Patch58: 0058-exfat-remove-useless-directory-scan-in-exfat_add_ent.patch +Patch59: 0059-exfat-replace-memcpy-with-structure-assignment.patch +Patch60: 0060-exfat-remove-rwoffset-in-exfat_inode_info.patch +Patch61: 0061-exfat-remove-useless-check-in-exfat_move_file.patch +#Patch62: 0062-PATCH-reduce-boilerplate-in-fsid-handling.patch +Patch63: 0063-exfat-Avoid-allocating-upcase-table-using-kcalloc.patch +#Patch64: 0064-block-use-an-on-stack-bio-in-blkdev_issue_flush.patch +Patch65: 0065-exfat-fix-shift-out-of-bounds-in-exfat_fill_super.patch +Patch66: 0066-exfat-improve-performance-of-exfat_free_cluster-when.patch +#Patch67: 0067-attr-handle-idmapped-mounts.patch +#Patch68: 0068-stat-handle-idmapped-mounts.patch +#Patch69: 0069-fs-make-helpers-idmap-mount-aware.patch +Patch70: 0070-exfat-fix-erroneous-discard-when-clear-cluster-bit.patch +Patch71: 0071-exfat-introduce-bitmap_lock-for-cluster-bitmap-acces.patch +Patch72: 0072-exfat-add-support-ioctl-and-FITRIM-function.patch +Patch73: 0073-exfat-improve-write-performance-when-dirsync-enabled.patch +Patch74: 0074-exfat-speed-up-iterate-lookup-by-fixing-start-point-.patch +Patch75: 0075-mm-require-set_page_dirty-to-be-explicitly-wired-up.patch +Patch76: 0076-exfat-avoid-incorrectly-releasing-for-root-inode.patch +Patch77: 0077-exfat-handle-wrong-stream-entry-size-in-exfat_readdi.patch +Patch78: 0078-exfat-fix-incorrect-loading-of-i_blocks-for-large-fi.patch +Patch9001: 9001-Compat-for-missing-inode-method-free_inode.patch +Patch9999: 9999-enable-exfat.patch ExclusiveArch: x86_64 aarch64 @@ -65,7 +147,7 @@ formatted with the exFAT file system. %prep -%autosetup -p1 -n %{pkg}-%{version} +%autosetup -p1 -n %{pkg}-%{driver_version} %build @@ -127,11 +209,6 @@ fi %changelog -* Thu Dec 09 2021 Kmods SIG - 5.16-1 -- Update to v5.16 (8c3318adf46a) - -* Tue Nov 09 2021 Peter Georg - 5.14-5 -- Rebuild for 4.18.0-348.el8 - -* Tue Sep 28 2021 Peter Georg - 5.14-4 -- Convert to kABI tracking kmod package (kernel >= 4.18.0-305.7.1.el8_4) +* Thu Jan 13 2022 Kmods SIG - 4.18.0.0-1 +- Switch to EL kernel source and versioning +- kABI tracking kmod package (kernel >= 4.18.0-348.el8)