Merge branch 'for-kernel-version-from-4.1.0' of https://github.com/namjaejeon/linux-exfat-oot into android13-5.4-lahaina

* 'for-kernel-version-from-4.1.0' of https://github.com/namjaejeon/linux-exfat-oot:
  exfat: github action: run buiuld and tests on for-kernel-version-from-4.1.0 branch
  exfat: fix ALIGN_DOWN undefined error
  exfat: using ffs instead of internal logic
  exfat: using hweight instead of internal logic
  exfat: fix ctime is not updated
  exfat: fix setting uninitialized time to ctime/atime
  exfat: convert to new timestamp accessors
  exfat: convert to ctime accessor functions
  exfat: fs: pass the request_mask to generic_fillattr
  exfat: convert to simple_rename_timestamp
  exfat: ensure that ctime is updated whenever the mtime is
  exfat: fs: add CONFIG_BUFFER_HEAD
  exfat: use fat ioctls definitions from include/uapi/linux/msdos_fs.h
  exfat: github action: remove liunx-4.1 source to get more disk space
  exfat: support create zero-size directory
  exfat: support handle zero-size directory
  exfat: add ioctls for accessing attributes
  exfat: vfs: get rid of old '->iterate' directory operation

Change-Id: I7e8add708697faf95952cacdb24bf5f504190fe4
This commit is contained in:
Michael Bestas 2024-04-16 00:03:08 +03:00
commit d561bf243b
No known key found for this signature in database
GPG Key ID: CC95044519BE6669
11 changed files with 458 additions and 115 deletions

View File

@ -2,9 +2,9 @@ name: linux-exfat-oot CI
on: on:
push: push:
branches: [ "master" ] branches: [ "for-kernel-version-from-4.1.0" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "for-kernel-version-from-4.1.0" ]
jobs: jobs:
build: build:
@ -20,6 +20,7 @@ jobs:
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz
tar xf linux-4.1.36.tar.gz tar xf linux-4.1.36.tar.gz
mv linux-4.1.36 linux-stable mv linux-4.1.36 linux-stable
rm -rf linux-4.1.36.tar.gz
- name: Prerequisite for xfstests testing - name: Prerequisite for xfstests testing
run: | run: |
sudo apt-get install linux-headers-$(uname -r) sudo apt-get install linux-headers-$(uname -r)
@ -47,7 +48,9 @@ jobs:
make -j$((`nproc`+1)) fs/exfat/exfat.ko make -j$((`nproc`+1)) fs/exfat/exfat.ko
- name: Run xfstests testsuite - name: Run xfstests testsuite
run: | run: |
cd ../linux-exfat-oot cd ..
rm -rf linux-stable
cd linux-exfat-oot
make > /dev/null make > /dev/null
sudo make install > /dev/null sudo make install > /dev/null
sudo insmod exfat.ko sudo insmod exfat.ko

View File

@ -2,6 +2,7 @@
config EXFAT_FS config EXFAT_FS
tristate "exFAT filesystem support" tristate "exFAT filesystem support"
select BUFFER_HEAD
select NLS select NLS
select LEGACY_DIRECT_IO select LEGACY_DIRECT_IO
help help

View File

@ -6,6 +6,7 @@
#include <linux/version.h> #include <linux/version.h>
#include <linux/blkdev.h> #include <linux/blkdev.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/bitmap.h>
#include <linux/buffer_head.h> #include <linux/buffer_head.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
#include <linux/sched/signal.h> #include <linux/sched/signal.h>
@ -17,37 +18,21 @@
#include "exfat_raw.h" #include "exfat_raw.h"
#include "exfat_fs.h" #include "exfat_fs.h"
static const unsigned char free_bit[] = { #if BITS_PER_LONG == 32
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/* 0 ~ 19*/ #define __le_long __le32
0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~ 39*/ #define lel_to_cpu(A) le32_to_cpu(A)
0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/* 40 ~ 59*/ #define cpu_to_lel(A) cpu_to_le32(A)
0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/* 60 ~ 79*/ #elif BITS_PER_LONG == 64
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2,/* 80 ~ 99*/ #define __le_long __le64
0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3,/*100 ~ 119*/ #define lel_to_cpu(A) le64_to_cpu(A)
0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*120 ~ 139*/ #define cpu_to_lel(A) cpu_to_le64(A)
0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,/*140 ~ 159*/ #else
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/*160 ~ 179*/ #error "BITS_PER_LONG not 32 or 64"
0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3,/*180 ~ 199*/ #endif
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[] = { #ifndef ALIGN_DOWN
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,/* 0 ~ 19*/ #define ALIGN_DOWN(x, a) __ALIGN_KERNEL((x) - ((a) - 1), (a))
2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,/* 20 ~ 39*/ #endif
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 * Allocation Bitmap Management Functions
@ -211,32 +196,35 @@ unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu)
{ {
unsigned int i, map_i, map_b, ent_idx; unsigned int i, map_i, map_b, ent_idx;
unsigned int clu_base, clu_free; unsigned int clu_base, clu_free;
unsigned char k, clu_mask; unsigned long clu_bits, clu_mask;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
__le_long bitval;
WARN_ON(clu < EXFAT_FIRST_CLUSTER); WARN_ON(clu < EXFAT_FIRST_CLUSTER);
ent_idx = CLUSTER_TO_BITMAP_ENT(clu); ent_idx = ALIGN_DOWN(CLUSTER_TO_BITMAP_ENT(clu), BITS_PER_LONG);
clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx & ~(BITS_PER_BYTE_MASK)); clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx);
clu_mask = IGNORED_BITS_REMAINED(clu, clu_base); clu_mask = IGNORED_BITS_REMAINED(clu, clu_base);
map_i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); map_i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
map_b = BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent_idx); map_b = BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent_idx);
for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters; for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters;
i += BITS_PER_BYTE) { i += BITS_PER_LONG) {
k = *(sbi->vol_amap[map_i]->b_data + map_b); bitval = *(__le_long *)(sbi->vol_amap[map_i]->b_data + map_b);
if (clu_mask > 0) { if (clu_mask > 0) {
k |= clu_mask; bitval |= cpu_to_lel(clu_mask);
clu_mask = 0; clu_mask = 0;
} }
if (k < 0xFF) { if (lel_to_cpu(bitval) != ULONG_MAX) {
clu_free = clu_base + free_bit[k]; clu_bits = lel_to_cpu(bitval);
clu_free = clu_base + ffz(clu_bits);
if (clu_free < sbi->num_clusters) if (clu_free < sbi->num_clusters)
return clu_free; return clu_free;
} }
clu_base += BITS_PER_BYTE; clu_base += BITS_PER_LONG;
map_b += sizeof(long);
if (++map_b >= sb->s_blocksize || if (map_b >= sb->s_blocksize ||
clu_base >= sbi->num_clusters) { clu_base >= sbi->num_clusters) {
if (++map_i >= sbi->map_sectors) { if (++map_i >= sbi->map_sectors) {
clu_base = EXFAT_FIRST_CLUSTER; clu_base = EXFAT_FIRST_CLUSTER;
@ -255,25 +243,24 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count)
unsigned int count = 0; unsigned int count = 0;
unsigned int i, map_i = 0, map_b = 0; unsigned int i, map_i = 0, map_b = 0;
unsigned int total_clus = EXFAT_DATA_CLUSTER_COUNT(sbi); unsigned int total_clus = EXFAT_DATA_CLUSTER_COUNT(sbi);
unsigned int last_mask = total_clus & BITS_PER_BYTE_MASK; unsigned int last_mask = total_clus & (BITS_PER_LONG - 1);
unsigned char clu_bits; unsigned long *bitmap, clu_bits;
const unsigned char last_bit_mask[] = {0, 0b00000001, 0b00000011,
0b00000111, 0b00001111, 0b00011111, 0b00111111, 0b01111111};
total_clus &= ~last_mask; total_clus &= ~last_mask;
for (i = 0; i < total_clus; i += BITS_PER_BYTE) { for (i = 0; i < total_clus; i += BITS_PER_LONG) {
clu_bits = *(sbi->vol_amap[map_i]->b_data + map_b); bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b);
count += used_bit[clu_bits]; count += hweight_long(*bitmap);
if (++map_b >= (unsigned int)sb->s_blocksize) { map_b += sizeof(long);
if (map_b >= (unsigned int)sb->s_blocksize) {
map_i++; map_i++;
map_b = 0; map_b = 0;
} }
} }
if (last_mask) { if (last_mask) {
clu_bits = *(sbi->vol_amap[map_i]->b_data + map_b); bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b);
clu_bits &= last_bit_mask[last_mask]; clu_bits = lel_to_cpu(*(__le_long *)bitmap);
count += used_bit[clu_bits]; count += hweight_long(clu_bits & BITMAP_LAST_WORD_MASK(last_mask));
} }
*ret_count = count; *ret_count = count;

View File

@ -288,7 +288,7 @@ get_new:
mutex_unlock(&EXFAT_SB(sb)->s_lock); mutex_unlock(&EXFAT_SB(sb)->s_lock);
if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum,
(de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) (de.attr & EXFAT_ATTR_SUBDIR) ? DT_DIR : DT_REG))
goto out; goto out;
ctx->pos = cpos; ctx->pos = cpos;
goto get_new; goto get_new;
@ -307,10 +307,17 @@ out:
return err; return err;
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
WRAP_DIR_ITER(exfat_iterate) // FIXME!
#endif
const struct file_operations exfat_dir_operations = { const struct file_operations exfat_dir_operations = {
.llseek = generic_file_llseek, .llseek = generic_file_llseek,
.read = generic_read_dir, .read = generic_read_dir,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
.iterate_shared = shared_exfat_iterate,
#else
.iterate = exfat_iterate, .iterate = exfat_iterate,
#endif
.unlocked_ioctl = exfat_ioctl, .unlocked_ioctl = exfat_ioctl,
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
.compat_ioctl = exfat_compat_ioctl, .compat_ioctl = exfat_compat_ioctl,
@ -359,7 +366,7 @@ unsigned int exfat_get_entry_type(struct exfat_dentry *ep)
if (ep->type == EXFAT_VOLUME) if (ep->type == EXFAT_VOLUME)
return TYPE_VOLUME; return TYPE_VOLUME;
if (ep->type == EXFAT_FILE) { if (ep->type == EXFAT_FILE) {
if (le16_to_cpu(ep->dentry.file.attr) & ATTR_SUBDIR) if (le16_to_cpu(ep->dentry.file.attr) & EXFAT_ATTR_SUBDIR)
return TYPE_DIR; return TYPE_DIR;
return TYPE_FILE; return TYPE_FILE;
} }
@ -410,19 +417,21 @@ static void exfat_set_entry_type(struct exfat_dentry *ep, unsigned int type)
ep->type = EXFAT_VOLUME; ep->type = EXFAT_VOLUME;
} else if (type == TYPE_DIR) { } else if (type == TYPE_DIR) {
ep->type = EXFAT_FILE; ep->type = EXFAT_FILE;
ep->dentry.file.attr = cpu_to_le16(ATTR_SUBDIR); ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_SUBDIR);
} else if (type == TYPE_FILE) { } else if (type == TYPE_FILE) {
ep->type = EXFAT_FILE; ep->type = EXFAT_FILE;
ep->dentry.file.attr = cpu_to_le16(ATTR_ARCHIVE); ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_ARCHIVE);
} }
} }
static void exfat_init_stream_entry(struct exfat_dentry *ep, static void exfat_init_stream_entry(struct exfat_dentry *ep,
unsigned char flags, unsigned int start_clu, unsigned int start_clu, unsigned long long size)
unsigned long long size)
{ {
exfat_set_entry_type(ep, TYPE_STREAM); exfat_set_entry_type(ep, TYPE_STREAM);
ep->dentry.stream.flags = flags; if (size == 0)
ep->dentry.stream.flags = ALLOC_FAT_CHAIN;
else
ep->dentry.stream.flags = ALLOC_NO_FAT_CHAIN;
ep->dentry.stream.start_clu = cpu_to_le32(start_clu); ep->dentry.stream.start_clu = cpu_to_le32(start_clu);
ep->dentry.stream.valid_size = cpu_to_le64(size); ep->dentry.stream.valid_size = cpu_to_le64(size);
ep->dentry.stream.size = cpu_to_le64(size); ep->dentry.stream.size = cpu_to_le64(size);
@ -498,9 +507,7 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir,
if (!ep) if (!ep)
return -EIO; return -EIO;
exfat_init_stream_entry(ep, exfat_init_stream_entry(ep, start_clu, size);
(type == TYPE_FILE) ? ALLOC_FAT_CHAIN : ALLOC_NO_FAT_CHAIN,
start_clu, size);
exfat_update_bh(bh, IS_DIRSYNC(inode)); exfat_update_bh(bh, IS_DIRSYNC(inode));
brelse(bh); brelse(bh);

View File

@ -148,8 +148,7 @@ enum {
#define BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent) (ent & BITS_PER_SECTOR_MASK(sb)) #define BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent) (ent & BITS_PER_SECTOR_MASK(sb))
#define BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent) \ #define BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent) \
((ent / BITS_PER_BYTE) & ((sb)->s_blocksize - 1)) ((ent / BITS_PER_BYTE) & ((sb)->s_blocksize - 1))
#define BITS_PER_BYTE_MASK 0x7 #define IGNORED_BITS_REMAINED(clu, clu_base) ((1UL << ((clu) - (clu_base))) - 1)
#define IGNORED_BITS_REMAINED(clu, clu_base) ((1 << ((clu) - (clu_base))) - 1)
#define ES_ENTRY_NUM(name_len) (ES_IDX_LAST_FILENAME(name_len) + 1) #define ES_ENTRY_NUM(name_len) (ES_IDX_LAST_FILENAME(name_len) + 1)
/* 19 entries = 1 file entry + 1 stream entry + 17 filename entries */ /* 19 entries = 1 file entry + 1 stream entry + 17 filename entries */
@ -253,6 +252,8 @@ struct exfat_mount_options {
discard:1, /* Issue discard requests on deletions */ discard:1, /* Issue discard requests on deletions */
keep_last_dots:1; /* Keep trailing periods in paths */ keep_last_dots:1; /* Keep trailing periods in paths */
int time_offset; /* Offset of timestamps from UTC (in minutes) */ int time_offset; /* Offset of timestamps from UTC (in minutes) */
/* Support creating zero-size directory, default: false */
bool zero_size_dir;
}; };
/* /*
@ -382,10 +383,10 @@ static inline int exfat_mode_can_hold_ro(struct inode *inode)
static inline mode_t exfat_make_mode(struct exfat_sb_info *sbi, static inline mode_t exfat_make_mode(struct exfat_sb_info *sbi,
unsigned short attr, mode_t mode) unsigned short attr, mode_t mode)
{ {
if ((attr & ATTR_READONLY) && !(attr & ATTR_SUBDIR)) if ((attr & EXFAT_ATTR_READONLY) && !(attr & EXFAT_ATTR_SUBDIR))
mode &= ~0222; mode &= ~0222;
if (attr & ATTR_SUBDIR) if (attr & EXFAT_ATTR_SUBDIR)
return (mode & ~sbi->options.fs_dmask) | S_IFDIR; return (mode & ~sbi->options.fs_dmask) | S_IFDIR;
return (mode & ~sbi->options.fs_fmask) | S_IFREG; return (mode & ~sbi->options.fs_fmask) | S_IFREG;
@ -397,18 +398,18 @@ static inline unsigned short exfat_make_attr(struct inode *inode)
unsigned short attr = EXFAT_I(inode)->attr; unsigned short attr = EXFAT_I(inode)->attr;
if (S_ISDIR(inode->i_mode)) if (S_ISDIR(inode->i_mode))
attr |= ATTR_SUBDIR; attr |= EXFAT_ATTR_SUBDIR;
if (exfat_mode_can_hold_ro(inode) && !(inode->i_mode & 0222)) if (exfat_mode_can_hold_ro(inode) && !(inode->i_mode & 0222))
attr |= ATTR_READONLY; attr |= EXFAT_ATTR_READONLY;
return attr; return attr;
} }
static inline void exfat_save_attr(struct inode *inode, unsigned short attr) static inline void exfat_save_attr(struct inode *inode, unsigned short attr)
{ {
if (exfat_mode_can_hold_ro(inode)) if (exfat_mode_can_hold_ro(inode))
EXFAT_I(inode)->attr = attr & (ATTR_RWMASK | ATTR_READONLY); EXFAT_I(inode)->attr = attr & (EXFAT_ATTR_RWMASK | EXFAT_ATTR_READONLY);
else else
EXFAT_I(inode)->attr = attr & ATTR_RWMASK; EXFAT_I(inode)->attr = attr & EXFAT_ATTR_RWMASK;
} }
static inline bool exfat_is_last_sector_in_cluster(struct exfat_sb_info *sbi, static inline bool exfat_is_last_sector_in_cluster(struct exfat_sb_info *sbi,
@ -596,6 +597,9 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...)
void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts,
u8 tz, __le16 time, __le16 date, u8 time_cs); u8 tz, __le16 time, __le16 date, u8 time_cs);
void exfat_truncate_atime(struct timespec64 *ts); void exfat_truncate_atime(struct timespec64 *ts);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
void exfat_truncate_inode_atime(struct inode *inode);
#endif
void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts,
u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); u8 *tz, __le16 *time, __le16 *date, u8 *time_cs);
#else #else

View File

@ -64,15 +64,16 @@
#define CS_DEFAULT 2 #define CS_DEFAULT 2
/* file attributes */ /* file attributes */
#define ATTR_READONLY 0x0001 #define EXFAT_ATTR_READONLY 0x0001
#define ATTR_HIDDEN 0x0002 #define EXFAT_ATTR_HIDDEN 0x0002
#define ATTR_SYSTEM 0x0004 #define EXFAT_ATTR_SYSTEM 0x0004
#define ATTR_VOLUME 0x0008 #define EXFAT_ATTR_VOLUME 0x0008
#define ATTR_SUBDIR 0x0010 #define EXFAT_ATTR_SUBDIR 0x0010
#define ATTR_ARCHIVE 0x0020 #define EXFAT_ATTR_ARCHIVE 0x0020
#define ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ #define EXFAT_ATTR_RWMASK (EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM | \
ATTR_SUBDIR | ATTR_ARCHIVE) EXFAT_ATTR_VOLUME | EXFAT_ATTR_SUBDIR | \
EXFAT_ATTR_ARCHIVE)
#define BOOTSEC_JUMP_BOOT_LEN 3 #define BOOTSEC_JUMP_BOOT_LEN 3
#define BOOTSEC_FS_NAME_LEN 8 #define BOOTSEC_FS_NAME_LEN 8

View File

@ -9,8 +9,12 @@
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
#include <linux/cred.h> #include <linux/cred.h>
#endif #endif
#include <linux/mount.h>
#include <linux/compat.h> #include <linux/compat.h>
#include <linux/blkdev.h> #include <linux/blkdev.h>
#include <linux/fsnotify.h>
#include <linux/security.h>
#include <linux/msdos_fs.h>
#include "exfat_raw.h" #include "exfat_raw.h"
#include "exfat_fs.h" #include "exfat_fs.h"
@ -26,7 +30,15 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
return err; return err;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
#else
inode->i_mtime = inode_set_ctime_current(inode);
#endif
#else
inode->i_ctime = inode->i_mtime = current_time(inode); inode->i_ctime = inode->i_mtime = current_time(inode);
#endif
#else #else
inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
#endif #endif
@ -151,7 +163,7 @@ int __exfat_truncate(struct inode *inode)
} }
if (ei->type == TYPE_FILE) if (ei->type == TYPE_FILE)
ei->attr |= ATTR_ARCHIVE; ei->attr |= EXFAT_ATTR_ARCHIVE;
/* /*
* update the directory entry * update the directory entry
@ -265,7 +277,11 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
#else
generic_fillattr(&nop_mnt_idmap, inode, stat); generic_fillattr(&nop_mnt_idmap, inode, stat);
#endif
#else #else
generic_fillattr(&init_user_ns, inode, stat); generic_fillattr(&init_user_ns, inode, stat);
#endif #endif
@ -355,11 +371,23 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr)
if (attr->ia_valid & ATTR_SIZE) if (attr->ia_valid & ATTR_SIZE)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
#else
inode->i_mtime = inode_set_ctime_current(inode);
#endif
#else
inode->i_mtime = inode->i_ctime = current_time(inode); inode->i_mtime = inode->i_ctime = current_time(inode);
#endif
#else #else
inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
setattr_copy(&nop_mnt_idmap, inode, attr);
exfat_truncate_inode_atime(inode);
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
setattr_copy(&nop_mnt_idmap, inode, attr); setattr_copy(&nop_mnt_idmap, inode, attr);
@ -370,6 +398,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr)
setattr_copy(inode, attr); setattr_copy(inode, attr);
#endif #endif
exfat_truncate_atime(&inode->i_atime); exfat_truncate_atime(&inode->i_atime);
#endif
if (attr->ia_valid & ATTR_SIZE) { if (attr->ia_valid & ATTR_SIZE) {
error = exfat_block_truncate_page(inode, attr->ia_size); error = exfat_block_truncate_page(inode, attr->ia_size);
@ -390,6 +419,131 @@ out:
return error; return error;
} }
/*
* modified ioctls from fat/file.c by Welmer Almesberger
*/
static int exfat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr)
{
u32 attr;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
inode_lock_shared(inode);
#else
mutex_lock(&inode->i_mutex);
#endif
attr = exfat_make_attr(inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
inode_unlock_shared(inode);
#else
mutex_unlock(&inode->i_mutex);
#endif
return put_user(attr, user_attr);
}
static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr)
{
struct inode *inode = file_inode(file);
struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb);
int is_dir = S_ISDIR(inode->i_mode);
u32 attr, oldattr;
struct iattr ia;
int err;
err = get_user(attr, user_attr);
if (err)
goto out;
err = mnt_want_write_file(file);
if (err)
goto out;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
inode_lock(inode);
#else
mutex_lock(&inode->i_mutex);
#endif
oldattr = exfat_make_attr(inode);
/*
* Mask attributes so we don't set reserved fields.
*/
attr &= (EXFAT_ATTR_READONLY | EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM |
EXFAT_ATTR_ARCHIVE);
attr |= (is_dir ? EXFAT_ATTR_SUBDIR : 0);
/* Equivalent to a chmod() */
ia.ia_valid = ATTR_MODE | ATTR_CTIME;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
ia.ia_ctime = current_time(inode);
#else
ia.ia_ctime = current_fs_time(inode->i_sb);
#endif
if (is_dir)
ia.ia_mode = exfat_make_mode(sbi, attr, 0777);
else
ia.ia_mode = exfat_make_mode(sbi, attr, 0666 | (inode->i_mode & 0111));
/* The root directory has no attributes */
if (inode->i_ino == EXFAT_ROOT_INO && attr != EXFAT_ATTR_SUBDIR) {
err = -EINVAL;
goto out_unlock_inode;
}
if (((attr | oldattr) & EXFAT_ATTR_SYSTEM) &&
!capable(CAP_LINUX_IMMUTABLE)) {
err = -EPERM;
goto out_unlock_inode;
}
/*
* The security check is questionable... We single
* out the RO attribute for checking by the security
* module, just because it maps to a file mode.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
err = security_inode_setattr(file_mnt_idmap(file),
file->f_path.dentry, &ia);
#else
err = security_inode_setattr(file_mnt_user_ns(file),
file->f_path.dentry, &ia);
#endif
#else
err = security_inode_setattr(file->f_path.dentry, &ia);
#endif
if (err)
goto out_unlock_inode;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
/* This MUST be done before doing anything irreversible... */
err = exfat_setattr(file_mnt_idmap(file), file->f_path.dentry, &ia);
#else
err = exfat_setattr(file_mnt_user_ns(file), file->f_path.dentry, &ia);
#endif
#else
err = exfat_setattr(file->f_path.dentry, &ia);
#endif
if (err)
goto out_unlock_inode;
fsnotify_change(file->f_path.dentry, ia.ia_valid);
exfat_save_attr(inode, attr);
mark_inode_dirty(inode);
out_unlock_inode:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
inode_unlock(inode);
#else
mutex_unlock(&inode->i_mutex);
#endif
mnt_drop_write_file(file);
out:
return err;
}
static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
{ {
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0)
@ -432,8 +586,13 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{ {
struct inode *inode = file_inode(filp); struct inode *inode = file_inode(filp);
u32 __user *user_attr = (u32 __user *)arg;
switch (cmd) { switch (cmd) {
case FAT_IOCTL_GET_ATTRIBUTES:
return exfat_ioctl_get_attributes(inode, user_attr);
case FAT_IOCTL_SET_ATTRIBUTES:
return exfat_ioctl_set_attributes(filp, user_attr);
case FITRIM: case FITRIM:
return exfat_ioctl_fitrim(inode, arg); return exfat_ioctl_fitrim(inode, arg);
default: default:

View File

@ -28,6 +28,9 @@ int __exfat_write_inode(struct inode *inode, int sync)
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
bool is_dir = (ei->type == TYPE_DIR) ? true : false; bool is_dir = (ei->type == TYPE_DIR) ? true : false;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
struct timespec64 ts;
#endif
if (inode->i_ino == EXFAT_ROOT_INO) if (inode->i_ino == EXFAT_ROOT_INO)
return 0; return 0;
@ -57,6 +60,20 @@ int __exfat_write_inode(struct inode *inode, int sync)
&ep->dentry.file.create_time, &ep->dentry.file.create_time,
&ep->dentry.file.create_date, &ep->dentry.file.create_date,
&ep->dentry.file.create_time_cs); &ep->dentry.file.create_time_cs);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
ts = inode_get_mtime(inode);
exfat_set_entry_time(sbi, &ts,
&ep->dentry.file.modify_tz,
&ep->dentry.file.modify_time,
&ep->dentry.file.modify_date,
&ep->dentry.file.modify_time_cs);
ts = inode_get_atime(inode);
exfat_set_entry_time(sbi, &ts,
&ep->dentry.file.access_tz,
&ep->dentry.file.access_time,
&ep->dentry.file.access_date,
NULL);
#else
exfat_set_entry_time(sbi, &inode->i_mtime, exfat_set_entry_time(sbi, &inode->i_mtime,
&ep->dentry.file.modify_tz, &ep->dentry.file.modify_tz,
&ep->dentry.file.modify_time, &ep->dentry.file.modify_time,
@ -67,6 +84,7 @@ int __exfat_write_inode(struct inode *inode, int sync)
&ep->dentry.file.access_time, &ep->dentry.file.access_time,
&ep->dentry.file.access_date, &ep->dentry.file.access_date,
NULL); NULL);
#endif
/* File size should be zero if there is no cluster allocated */ /* File size should be zero if there is no cluster allocated */
on_disk_size = i_size_read(inode); on_disk_size = i_size_read(inode);
@ -380,7 +398,15 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to)
if (to > i_size_read(inode)) { if (to > i_size_read(inode)) {
truncate_pagecache(inode, i_size_read(inode)); truncate_pagecache(inode, i_size_read(inode));
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
#else
inode->i_mtime = inode_set_ctime_current(inode);
#endif
#else
inode->i_mtime = inode->i_ctime = current_time(inode); inode->i_mtime = inode->i_ctime = current_time(inode);
#endif
#else #else
inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC;
#endif #endif
@ -436,13 +462,21 @@ static int exfat_write_end(struct file *file, struct address_space *mapping,
if (err < len) if (err < len)
exfat_write_failed(mapping, pos+len); exfat_write_failed(mapping, pos+len);
if (!(err < 0) && !(ei->attr & ATTR_ARCHIVE)) { if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
#else
inode->i_mtime = inode_set_ctime_current(inode);
#endif
#else
inode->i_mtime = inode->i_ctime = current_time(inode); inode->i_mtime = inode->i_ctime = current_time(inode);
#endif
#else #else
inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC;
#endif #endif
ei->attr |= ATTR_ARCHIVE; ei->attr |= EXFAT_ATTR_ARCHIVE;
mark_inode_dirty(inode); mark_inode_dirty(inode);
} }
@ -629,7 +663,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)
inode->i_generation = prandom_u32(); inode->i_generation = prandom_u32();
#endif #endif
if (info->attr & ATTR_SUBDIR) { /* directory */ if (info->attr & EXFAT_ATTR_SUBDIR) { /* directory */
inode->i_generation &= ~1; inode->i_generation &= ~1;
inode->i_mode = exfat_make_mode(sbi, info->attr, 0777); inode->i_mode = exfat_make_mode(sbi, info->attr, 0777);
inode->i_op = &exfat_dir_inode_operations; inode->i_op = &exfat_dir_inode_operations;
@ -658,10 +692,22 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)
exfat_save_attr(inode, info->attr); exfat_save_attr(inode, info->attr);
inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(inode, info->mtime);
#else
inode->i_mtime = info->mtime; inode->i_mtime = info->mtime;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
inode_set_ctime_to_ts(inode, info->mtime);
#else
inode->i_ctime = info->mtime; inode->i_ctime = info->mtime;
#endif
ei->i_crtime = info->crtime; ei->i_crtime = info->crtime;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_atime_to_ts(inode, info->atime);
#else
inode->i_atime = info->atime; inode->i_atime = info->atime;
#endif
return 0; return 0;
} }

View File

@ -163,6 +163,16 @@ void exfat_truncate_atime(struct timespec *ts)
ts->tv_nsec = 0; ts->tv_nsec = 0;
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
void exfat_truncate_inode_atime(struct inode *inode)
{
struct timespec64 atime = inode_get_atime(inode);
exfat_truncate_atime(&atime);
inode_set_atime_to_ts(inode, atime);
}
#endif
u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type) u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type)
{ {
int i; int i;

View File

@ -378,14 +378,20 @@ static int exfat_find_empty_entry(struct inode *inode,
if (exfat_check_max_dentries(inode)) if (exfat_check_max_dentries(inode))
return -ENOSPC; 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 * Allocate new cluster to this directory
*/ */
exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags); if (ei->start_clu != EXFAT_EOF_CLUSTER) {
/* we trust p_dir->size regardless of FAT type */
if (exfat_find_last_cluster(sb, p_dir, &last_clu))
return -EIO;
exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags);
} else {
/* This directory is empty */
exfat_chain_set(&clu, EXFAT_EOF_CLUSTER, 0,
ALLOC_NO_FAT_CHAIN);
}
/* allocate a cluster */ /* allocate a cluster */
ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode)); ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode));
@ -395,6 +401,11 @@ static int exfat_find_empty_entry(struct inode *inode,
if (exfat_zeroed_cluster(inode, clu.dir)) if (exfat_zeroed_cluster(inode, clu.dir))
return -EIO; return -EIO;
if (ei->start_clu == EXFAT_EOF_CLUSTER) {
ei->start_clu = clu.dir;
p_dir->dir = clu.dir;
}
/* append to the FAT chain */ /* append to the FAT chain */
if (clu.flags != p_dir->flags) { if (clu.flags != p_dir->flags) {
/* no-fat-chain bit is disabled, /* no-fat-chain bit is disabled,
@ -534,7 +545,7 @@ static int exfat_add_entry(struct inode *inode, const char *path,
goto out; goto out;
} }
if (type == TYPE_DIR) { if (type == TYPE_DIR && !sbi->options.zero_size_dir) {
ret = exfat_alloc_new_dir(inode, &clu); ret = exfat_alloc_new_dir(inode, &clu);
if (ret) if (ret)
goto out; goto out;
@ -561,13 +572,16 @@ static int exfat_add_entry(struct inode *inode, const char *path,
info->type = type; info->type = type;
if (type == TYPE_FILE) { if (type == TYPE_FILE) {
info->attr = ATTR_ARCHIVE; info->attr = EXFAT_ATTR_ARCHIVE;
info->start_clu = EXFAT_EOF_CLUSTER; info->start_clu = EXFAT_EOF_CLUSTER;
info->size = 0; info->size = 0;
info->num_subdirs = 0; info->num_subdirs = 0;
} else { } else {
info->attr = ATTR_SUBDIR; info->attr = EXFAT_ATTR_SUBDIR;
info->start_clu = start_clu; if (sbi->options.zero_size_dir)
info->start_clu = EXFAT_EOF_CLUSTER;
else
info->start_clu = start_clu;
info->size = clu_size; info->size = clu_size;
info->num_subdirs = EXFAT_MIN_SUBDIR; info->num_subdirs = EXFAT_MIN_SUBDIR;
} }
@ -611,7 +625,15 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode,
dir->i_version++; dir->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
#else
dir->i_mtime = inode_set_ctime_current(dir);
#endif
#else
dir->i_ctime = dir->i_mtime = current_time(dir); dir->i_ctime = dir->i_mtime = current_time(dir);
#endif
#else #else
dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC;
#endif #endif
@ -632,14 +654,23 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode,
inode->i_version++; inode->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode);
#else
inode->i_mtime = inode->i_atime = inode->i_ctime = inode->i_mtime = inode->i_atime = inode->i_ctime =
EXFAT_I(inode)->i_crtime = current_time(inode); EXFAT_I(inode)->i_crtime = current_time(inode);
#endif
#else #else
inode->i_mtime = inode->i_atime = inode->i_ctime = inode->i_mtime = inode->i_atime = inode->i_ctime =
EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC;
#endif #endif
exfat_truncate_atime(&inode->i_atime); exfat_truncate_atime(&inode->i_atime);
#endif
/* timestamp is already written, so mark_inode_dirty() is unneeded. */ /* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
@ -709,7 +740,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname,
info->type = exfat_get_entry_type(ep); info->type = exfat_get_entry_type(ep);
info->attr = le16_to_cpu(ep->dentry.file.attr); info->attr = le16_to_cpu(ep->dentry.file.attr);
info->size = le64_to_cpu(ep2->dentry.stream.valid_size); info->size = le64_to_cpu(ep2->dentry.stream.valid_size);
if ((info->type == TYPE_FILE) && (info->size == 0)) { if (info->size == 0) {
info->flags = ALLOC_NO_FAT_CHAIN; info->flags = ALLOC_NO_FAT_CHAIN;
info->start_clu = EXFAT_EOF_CLUSTER; info->start_clu = EXFAT_EOF_CLUSTER;
} else { } else {
@ -888,24 +919,43 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
#else #else
dir->i_version++; dir->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
dir->i_mtime = dir->i_atime = current_time(dir); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
simple_inode_init_ts(dir);
exfat_truncate_inode_atime(dir);
#else #else
dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir);
#else
dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir);
#endif
#else
dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME_SEC;
#endif #endif
exfat_truncate_atime(&dir->i_atime); exfat_truncate_atime(&dir->i_atime);
#endif
if (IS_DIRSYNC(dir)) if (IS_DIRSYNC(dir))
exfat_sync_inode(dir); exfat_sync_inode(dir);
else else
mark_inode_dirty(dir); mark_inode_dirty(dir);
clear_nlink(inode); clear_nlink(inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode->i_mtime = inode->i_atime = current_time(inode); simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
#else #else
inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode);
#else
inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);
#endif
#else
inode->i_mtime = inode->i_atime = dir->i_ctime = CURRENT_TIME_SEC;
#endif #endif
exfat_truncate_atime(&inode->i_atime); exfat_truncate_atime(&inode->i_atime);
#endif
exfat_unhash_inode(inode); exfat_unhash_inode(inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
exfat_d_version_set(dentry, inode_query_iversion(dir)); exfat_d_version_set(dentry, inode_query_iversion(dir));
@ -950,7 +1000,15 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
#else
dir->i_mtime = inode_set_ctime_current(dir);
#endif
#else
dir->i_ctime = dir->i_mtime = current_time(dir); dir->i_ctime = dir->i_mtime = current_time(dir);
#endif
#else #else
dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC; dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC;
#endif #endif
@ -971,14 +1029,23 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
#else #else
inode->i_version++; inode->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
inode->i_mtime = inode->i_atime = EXFAT_I(inode)->i_crtime = inode_set_ctime_current(inode);
#else
inode->i_mtime = inode->i_atime = inode->i_ctime = inode->i_mtime = inode->i_atime = inode->i_ctime =
EXFAT_I(inode)->i_crtime = current_time(inode); EXFAT_I(inode)->i_crtime = current_time(inode);
#endif
#else #else
inode->i_mtime = inode->i_atime = inode->i_ctime = inode->i_mtime = inode->i_atime = inode->i_ctime =
EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC;
#endif #endif
exfat_truncate_atime(&inode->i_atime); exfat_truncate_atime(&inode->i_atime);
#endif
/* timestamp is already written, so mark_inode_dirty() is unneeded. */ /* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
@ -1000,6 +1067,9 @@ static int exfat_check_dir_empty(struct super_block *sb,
dentries_per_clu = sbi->dentries_per_clu; dentries_per_clu = sbi->dentries_per_clu;
if (p_dir->dir == EXFAT_EOF_CLUSTER)
return 0;
exfat_chain_dup(&clu, p_dir); exfat_chain_dup(&clu, p_dir);
while (clu.dir != EXFAT_EOF_CLUSTER) { while (clu.dir != EXFAT_EOF_CLUSTER) {
@ -1093,12 +1163,21 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
#else #else
dir->i_version++; dir->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
dir->i_mtime = dir->i_atime = current_time(dir); simple_inode_init_ts(dir);
exfat_truncate_inode_atime(dir);
#else #else
dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
dir->i_mtime = dir->i_atime = inode_set_ctime_current(dir);
#else
dir->i_mtime = dir->i_atime = dir->i_ctime = current_time(dir);
#endif
#else
dir->i_mtime = dir->i_atime = inode->i_ctime = CURRENT_TIME_SEC;
#endif #endif
exfat_truncate_atime(&dir->i_atime); exfat_truncate_atime(&dir->i_atime);
#endif
if (IS_DIRSYNC(dir)) if (IS_DIRSYNC(dir))
exfat_sync_inode(dir); exfat_sync_inode(dir);
else else
@ -1106,12 +1185,21 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
drop_nlink(dir); drop_nlink(dir);
clear_nlink(inode); clear_nlink(inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode->i_mtime = inode->i_atime = current_time(inode); simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
#else #else
inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
inode->i_mtime = inode->i_atime = inode_set_ctime_current(inode);
#else
inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);
#endif
#else
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC;
#endif #endif
exfat_truncate_atime(&inode->i_atime); exfat_truncate_atime(&inode->i_atime);
#endif
exfat_unhash_inode(inode); exfat_unhash_inode(inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
exfat_d_version_set(dentry, inode_query_iversion(dir)); exfat_d_version_set(dentry, inode_query_iversion(dir));
@ -1160,8 +1248,8 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
*epnew = *epold; *epnew = *epold;
if (exfat_get_entry_type(epnew) == TYPE_FILE) { if (exfat_get_entry_type(epnew) == TYPE_FILE) {
epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE);
ei->attr |= ATTR_ARCHIVE; ei->attr |= EXFAT_ATTR_ARCHIVE;
} }
exfat_update_bh(new_bh, sync); exfat_update_bh(new_bh, sync);
brelse(old_bh); brelse(old_bh);
@ -1192,8 +1280,8 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
ei->entry = newentry; ei->entry = newentry;
} else { } else {
if (exfat_get_entry_type(epold) == TYPE_FILE) { if (exfat_get_entry_type(epold) == TYPE_FILE) {
epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); epold->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE);
ei->attr |= ATTR_ARCHIVE; ei->attr |= EXFAT_ATTR_ARCHIVE;
} }
exfat_update_bh(old_bh, sync); exfat_update_bh(old_bh, sync);
brelse(old_bh); brelse(old_bh);
@ -1241,8 +1329,8 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,
*epnew = *epmov; *epnew = *epmov;
if (exfat_get_entry_type(epnew) == TYPE_FILE) { if (exfat_get_entry_type(epnew) == TYPE_FILE) {
epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE);
ei->attr |= ATTR_ARCHIVE; ei->attr |= EXFAT_ATTR_ARCHIVE;
} }
exfat_update_bh(new_bh, IS_DIRSYNC(inode)); exfat_update_bh(new_bh, IS_DIRSYNC(inode));
brelse(mov_bh); brelse(mov_bh);
@ -1383,7 +1471,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
} }
/* Free the clusters if new_inode is a dir(as if exfat_rmdir) */ /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */
if (new_entry_type == TYPE_DIR) { if (new_entry_type == TYPE_DIR &&
new_ei->start_clu != EXFAT_EOF_CLUSTER) {
/* new_ei, new_clu_to_free */ /* new_ei, new_clu_to_free */
struct exfat_chain new_clu_to_free; struct exfat_chain new_clu_to_free;
@ -1463,13 +1552,22 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry,
new_dir->i_version++; new_dir->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry);
EXFAT_I(new_dir)->i_crtime = current_time(new_dir);
#else
new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime =
EXFAT_I(new_dir)->i_crtime = current_time(new_dir); EXFAT_I(new_dir)->i_crtime = current_time(new_dir);
#endif
#else #else
new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime =
EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC; EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
exfat_truncate_inode_atime(new_dir);
#else
exfat_truncate_atime(&new_dir->i_atime); exfat_truncate_atime(&new_dir->i_atime);
#endif
if (IS_DIRSYNC(new_dir)) if (IS_DIRSYNC(new_dir))
exfat_sync_inode(new_dir); exfat_sync_inode(new_dir);
else else
@ -1496,7 +1594,9 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry,
old_dir->i_version++; old_dir->i_version++;
#endif #endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 6, 0)
old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir);
#endif
#else #else
old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC; old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC;
#endif #endif
@ -1518,8 +1618,12 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry,
WARN_ON(new_inode->i_nlink == 0); WARN_ON(new_inode->i_nlink == 0);
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
EXFAT_I(new_inode)->i_crtime = current_time(new_inode);
#else
new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime =
current_time(new_inode); current_time(new_inode);
#endif
#else #else
new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime = new_inode->i_ctime = EXFAT_I(new_inode)->i_crtime =
CURRENT_TIME_SEC; CURRENT_TIME_SEC;

View File

@ -195,6 +195,8 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root)
seq_puts(m, ",sys_tz"); seq_puts(m, ",sys_tz");
else if (opts->time_offset) else if (opts->time_offset)
seq_printf(m, ",time_offset=%d", opts->time_offset); seq_printf(m, ",time_offset=%d", opts->time_offset);
if (opts->zero_size_dir)
seq_puts(m, ",zero_size_dir");
return 0; return 0;
} }
@ -278,6 +280,7 @@ enum {
Opt_keep_last_dots, Opt_keep_last_dots,
Opt_sys_tz, Opt_sys_tz,
Opt_time_offset, Opt_time_offset,
Opt_zero_size_dir,
/* Deprecated options */ /* Deprecated options */
Opt_utf8, Opt_utf8,
@ -323,6 +326,7 @@ static const struct fs_parameter_spec exfat_param_specs[] = {
fsparam_flag("keep_last_dots", Opt_keep_last_dots), fsparam_flag("keep_last_dots", Opt_keep_last_dots),
fsparam_flag("sys_tz", Opt_sys_tz), fsparam_flag("sys_tz", Opt_sys_tz),
fsparam_s32("time_offset", Opt_time_offset), fsparam_s32("time_offset", Opt_time_offset),
fsparam_flag("zero_size_dir", Opt_zero_size_dir),
__fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated, __fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated,
NULL), NULL),
__fsparam(NULL, "debug", Opt_debug, fs_param_deprecated, __fsparam(NULL, "debug", Opt_debug, fs_param_deprecated,
@ -403,6 +407,9 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param)
return -EINVAL; return -EINVAL;
opts->time_offset = result.int_32; opts->time_offset = result.int_32;
break; break;
case Opt_zero_size_dir:
opts->zero_size_dir = true;
break;
case Opt_utf8: case Opt_utf8:
case Opt_debug: case Opt_debug:
case Opt_namecase: case Opt_namecase:
@ -435,6 +442,7 @@ enum {
Opt_debug, Opt_debug,
Opt_namecase, Opt_namecase,
Opt_codepage, Opt_codepage,
Opt_zero_size_dir,
Opt_fs, Opt_fs,
}; };
@ -454,6 +462,7 @@ static const match_table_t exfat_tokens = {
{Opt_namecase, "namecase=%u"}, {Opt_namecase, "namecase=%u"},
{Opt_debug, "debug"}, {Opt_debug, "debug"},
{Opt_utf8, "utf8"}, {Opt_utf8, "utf8"},
{Opt_zero_size_dir, "zero_size_dir"},
{Opt_err, NULL} {Opt_err, NULL}
}; };
@ -545,6 +554,9 @@ static int parse_options(struct super_block *sb, char *options, int silent,
case Opt_namecase: case Opt_namecase:
case Opt_codepage: case Opt_codepage:
break; break;
case Opt_zero_size_dir:
opts->zero_size_dir = true;
break;
default: default:
if (!silent) { if (!silent) {
exfat_err(sb, exfat_err(sb,
@ -628,7 +640,7 @@ static int exfat_read_root(struct inode *inode)
inode->i_version++; inode->i_version++;
#endif #endif
inode->i_generation = 0; inode->i_generation = 0;
inode->i_mode = exfat_make_mode(sbi, ATTR_SUBDIR, 0777); inode->i_mode = exfat_make_mode(sbi, EXFAT_ATTR_SUBDIR, 0777);
inode->i_op = &exfat_dir_inode_operations; inode->i_op = &exfat_dir_inode_operations;
inode->i_fop = &exfat_dir_operations; inode->i_fop = &exfat_dir_operations;
@ -637,15 +649,24 @@ static int exfat_read_root(struct inode *inode)
ei->i_size_aligned = i_size_read(inode); ei->i_size_aligned = i_size_read(inode);
ei->i_size_ondisk = i_size_read(inode); ei->i_size_ondisk = i_size_read(inode);
exfat_save_attr(inode, ATTR_SUBDIR); exfat_save_attr(inode, EXFAT_ATTR_SUBDIR);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
ei->i_crtime = simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
inode->i_mtime = inode->i_atime = ei->i_crtime = inode_set_ctime_current(inode);
#else
inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime =
current_time(inode); current_time(inode);
#endif
#else #else
inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime =
CURRENT_TIME_SEC; CURRENT_TIME_SEC;
#endif #endif
exfat_truncate_atime(&inode->i_atime); exfat_truncate_atime(&inode->i_atime);
#endif
return 0; return 0;
} }