Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0-or-later
config EXFAT_FS
tristate "exFAT filesystem support"
select BUFFER_HEAD
select NLS
select FS_IOMAP
help
This allows you to mount devices formatted with the exFAT file system.
exFAT is typically used on SD-Cards or USB sticks.
To compile this as a module, choose M here: the module will be called
exfat.
config EXFAT_DEFAULT_IOCHARSET
string "Default iocharset for exFAT"
default "utf8"
depends on EXFAT_FS
help
Set this to the default input/output character set to use for
converting between the encoding that is used for user visible
filenames and the UTF-16 character encoding that the exFAT
filesystem uses. This can be overridden with the "iocharset" mount
option for the exFAT filesystems.
+8
View File
@@ -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 iomap.o
+399
View File
@@ -0,0 +1,399 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/blkdev.h>
#include <linux/slab.h>
#include <linux/bitmap.h>
#include <linux/buffer_head.h>
#include <linux/backing-dev.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#if BITS_PER_LONG == 32
#define __le_long __le32
#define lel_to_cpu(A) le32_to_cpu(A)
#define cpu_to_lel(A) cpu_to_le32(A)
#elif BITS_PER_LONG == 64
#define __le_long __le64
#define lel_to_cpu(A) le64_to_cpu(A)
#define cpu_to_lel(A) cpu_to_le64(A)
#else
#error "BITS_PER_LONG not 32 or 64"
#endif
/*
* Allocation Bitmap Management Functions
*/
static bool exfat_test_bitmap_range(struct super_block *sb, unsigned int clu,
unsigned int count)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
unsigned int start = clu;
unsigned int end = clu + count;
unsigned int ent_idx, i, b;
unsigned int bit_offset, bits_to_check;
__le_long *bitmap_le;
unsigned long mask, word;
if (!is_valid_cluster(sbi, start) || !is_valid_cluster(sbi, end - 1))
return false;
while (start < end) {
ent_idx = CLUSTER_TO_BITMAP_ENT(start);
i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
bitmap_le = (__le_long *)sbi->vol_amap[i]->b_data;
/* Calculate how many bits we can check in the current word */
bit_offset = b % BITS_PER_LONG;
bits_to_check = min(end - start,
(unsigned int)(BITS_PER_LONG - bit_offset));
/* Create a bitmask for the range of bits to check */
if (bits_to_check >= BITS_PER_LONG)
mask = ~0UL;
else
mask = ((1UL << bits_to_check) - 1) << bit_offset;
word = lel_to_cpu(bitmap_le[b / BITS_PER_LONG]);
/* Check if all bits in the mask are set */
if ((word & mask) != mask)
return false;
start += bits_to_check;
}
return true;
}
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, j, need_map_size;
sector_t sector, end, ra;
blkcnt_t ra_cnt = 0;
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_err(sb, "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 = kvmalloc_objs(struct buffer_head *, sbi->map_sectors);
if (!sbi->vol_amap)
return -ENOMEM;
sector = ra = exfat_cluster_to_sector(sbi, sbi->map_clu);
end = sector + sbi->map_sectors - 1;
for (i = 0; i < sbi->map_sectors; i++) {
/* Trigger the next readahead in advance. */
exfat_blk_readahead(sb, sector + i, &ra, &ra_cnt, end);
sbi->vol_amap[i] = sb_bread(sb, sector + i);
if (!sbi->vol_amap[i])
goto err_out;
}
if (exfat_test_bitmap_range(sb, sbi->map_clu,
exfat_bytes_to_cluster_round_up(sbi, map_size)) == false)
goto err_out;
return 0;
err_out:
j = 0;
/* release all buffers and free vol_amap */
while (j < i)
brelse(sbi->vol_amap[j++]);
kvfree(sbi->vol_amap);
sbi->vol_amap = NULL;
return -EIO;
}
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);
if (!ep)
return -EIO;
type = exfat_get_entry_type(ep);
if (type == TYPE_BITMAP &&
ep->dentry.bitmap.flags == 0x0) {
int err;
err = exfat_allocate_bitmap(sb, ep);
brelse(bh);
return err;
}
brelse(bh);
if (type == TYPE_UNUSED)
return -EINVAL;
}
if (exfat_get_next_cluster(sb, &clu.dir))
return -EIO;
}
return -EINVAL;
}
void exfat_free_bitmap(struct exfat_sb_info *sbi)
{
int i;
for (i = 0; i < sbi->map_sectors; i++)
__brelse(sbi->vol_amap[i]);
kvfree(sbi->vol_amap);
}
int exfat_set_bitmap(struct super_block *sb, unsigned int clu, bool sync)
{
int i, b;
unsigned int ent_idx;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
if (!is_valid_cluster(sbi, clu))
return -EINVAL;
ent_idx = CLUSTER_TO_BITMAP_ENT(clu);
i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
set_bit_le(b, sbi->vol_amap[i]->b_data);
exfat_update_bh(sbi->vol_amap[i], sync);
return 0;
}
int exfat_clear_bitmap(struct super_block *sb, unsigned int clu, bool sync)
{
int i, b;
unsigned int ent_idx;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
if (!is_valid_cluster(sbi, clu))
return -EIO;
ent_idx = CLUSTER_TO_BITMAP_ENT(clu);
i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
if (!test_bit_le(b, sbi->vol_amap[i]->b_data))
return -EIO;
clear_bit_le(b, sbi->vol_amap[i]->b_data);
exfat_update_bh(sbi->vol_amap[i], sync);
return 0;
}
bool exfat_test_bitmap(struct super_block *sb, unsigned int clu)
{
int i, b;
unsigned int ent_idx;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
if (!sbi->vol_amap)
return true;
if (!is_valid_cluster(sbi, clu))
return false;
ent_idx = CLUSTER_TO_BITMAP_ENT(clu);
i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
if (!test_bit_le(b, sbi->vol_amap[i]->b_data))
return false;
return true;
}
/*
* 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 long clu_bits, clu_mask;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
__le_long bitval;
WARN_ON(clu < EXFAT_FIRST_CLUSTER);
ent_idx = ALIGN_DOWN(CLUSTER_TO_BITMAP_ENT(clu), BITS_PER_LONG);
clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx);
clu_mask = IGNORED_BITS_REMAINED(clu, clu_base);
map_i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
map_b = BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent_idx);
for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters;
i += BITS_PER_LONG) {
bitval = *(__le_long *)(sbi->vol_amap[map_i]->b_data + map_b);
if (clu_mask > 0) {
bitval |= cpu_to_lel(clu_mask);
clu_mask = 0;
}
if (lel_to_cpu(bitval) != ULONG_MAX) {
clu_bits = lel_to_cpu(bitval);
clu_free = clu_base + ffz(clu_bits);
if (clu_free < sbi->num_clusters)
return clu_free;
}
clu_base += BITS_PER_LONG;
map_b += sizeof(long);
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_LONG - 1);
unsigned long *bitmap, clu_bits;
total_clus &= ~last_mask;
for (i = 0; i < total_clus; i += BITS_PER_LONG) {
bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b);
count += hweight_long(*bitmap);
map_b += sizeof(long);
if (map_b >= (unsigned int)sb->s_blocksize) {
map_i++;
map_b = 0;
}
}
if (last_mask) {
bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b);
clu_bits = lel_to_cpu(*(__le_long *)bitmap);
count += hweight_long(clu_bits & BITMAP_LAST_WORD_MASK(last_mask));
}
*ret_count = count;
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;
}
+357
View File
@@ -0,0 +1,357 @@
// 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 <linux/slab.h>
#include <linux/unaligned.h>
#include <linux/buffer_head.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#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,
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);
}
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);
}
/*
* Find the cache that covers or precedes 'fclus' and return the last
* cluster before the next cache range.
*/
static inline unsigned int
exfat_cache_lookup(struct inode *inode, struct exfat_cache_id *cid,
unsigned int fclus, unsigned int end,
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 tail = 0; /* End boundary of hit cache */
/*
* Search range [fclus, end]. Stop early if:
* 1. Cache covers entire range, or
* 2. Next cache starts at current cache tail
*/
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) {
if (p->fcluster < hit->fcluster)
continue;
hit = p;
tail = hit->fcluster + hit->nr_contig;
/* Current cache covers [fclus, end] completely */
if (tail >= end)
break;
} else if (p->fcluster <= end) {
end = p->fcluster - 1;
/*
* If we have a hit and next cache starts within/at
* its tail, caches are contiguous, stop searching.
*/
if (tail && tail >= end)
break;
}
}
if (hit != &nohit) {
unsigned int offset;
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;
offset = min(cid->nr_contig, fclus - cid->fcluster);
*cached_fclus = cid->fcluster + offset;
*cached_dclus = cid->dcluster + offset;
}
spin_unlock(&ei->cache_lru_lock);
/* Return next cache start or 'end' if no more caches */
return end;
}
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 *dclus, unsigned int *count,
unsigned int *last_dclus)
{
struct super_block *sb = inode->i_sb;
struct exfat_inode_info *ei = EXFAT_I(inode);
struct buffer_head *bh = NULL;
struct exfat_cache_id cid;
unsigned int content, fclus;
unsigned int end = cluster + *count - 1;
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;
/*
* This case should not exist, as exfat_map_cluster function doesn't
* call this routine when start_clu == EXFAT_EOF_CLUSTER.
* This case is retained here for routine completeness.
*/
if (*dclus == EXFAT_EOF_CLUSTER) {
*count = 0;
return 0;
}
/* If only the first cluster is needed, return now. */
if (fclus == cluster && *count == 1)
return 0;
cache_init(&cid, fclus, *dclus);
/*
* Update the 'end' to exclude the next cache range, as clusters in
* different cache are typically not contiguous.
*/
end = exfat_cache_lookup(inode, &cid, cluster, end, &fclus, dclus);
/* Return if the cache covers the entire range. */
if (cid.fcluster + cid.nr_contig >= end) {
*count = end - cluster + 1;
return 0;
}
/* Find the first cluster we need. */
while (fclus < cluster) {
if (exfat_ent_get(sb, *dclus, &content, &bh))
return -EIO;
*last_dclus = *dclus;
*dclus = content;
fclus++;
if (content == EXFAT_EOF_CLUSTER)
break;
if (!cache_contiguous(&cid, *dclus))
cache_init(&cid, fclus, *dclus);
}
/*
* Now the cid cache contains the first cluster requested, collect
* the remaining clusters of this contiguous extent.
*/
if (*dclus != EXFAT_EOF_CLUSTER) {
unsigned int clu = *dclus;
while (fclus < end) {
if (exfat_ent_get(sb, clu, &content, &bh))
return -EIO;
if (++clu != content)
break;
fclus++;
}
cid.nr_contig = fclus - cid.fcluster;
*count = fclus - cluster + 1;
/*
* Cache this discontiguous cluster, we'll definitely need
* it later
*/
if (fclus < end && content != EXFAT_EOF_CLUSTER) {
exfat_cache_add(inode, &cid);
cache_init(&cid, fclus + 1, content);
}
} else {
*count = 0;
}
brelse(bh);
exfat_cache_add(inode, &cid);
return 0;
}
File diff suppressed because it is too large Load Diff
+706
View File
@@ -0,0 +1,706 @@
/* 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 <linux/fs.h>
#include <linux/ratelimit.h>
#include <linux/nls.h>
#include <linux/blkdev.h>
#include <linux/backing-dev.h>
#include <uapi/linux/exfat.h>
#include <linux/buffer_head.h>
#define EXFAT_ROOT_INO 1
/*
* 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 = 0, /* no lossy */
NLS_NAME_LOSSY = 1 << 0, /* just detected incorrect filename(s) */
};
#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 ES_IDX_FILE 0
#define ES_IDX_STREAM 1
#define ES_IDX_FIRST_FILENAME 2
#define EXFAT_FILENAME_ENTRY_NUM(name_len) \
DIV_ROUND_UP(name_len, EXFAT_FILE_NAME_LEN)
#define ES_IDX_LAST_FILENAME(name_len) \
(ES_IDX_FIRST_FILENAME + EXFAT_FILENAME_ENTRY_NUM(name_len) - 1)
#define DIR_DELETED 0xFFFFFFF7
/* type values */
#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_VENDOR_EXT 0x0801
#define TYPE_VENDOR_ALLOC 0x0802
#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 EXFAT_HINT_NONE -1
#define EXFAT_MIN_SUBDIR 2
#define EXFAT_BLK_RA_SIZE(sb) \
(min_t(blkcnt_t, (sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \
<< (PAGE_SHIFT - (sb)->s_blocksize_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 IGNORED_BITS_REMAINED(clu, clu_base) ((1UL << ((clu) - (clu_base))) - 1)
#define ES_ENTRY_NUM(name_len) (ES_IDX_LAST_FILENAME(name_len) + 1)
/* 19 entries = 1 file entry + 1 stream entry + 17 filename entries */
#define ES_MAX_ENTRY_NUM ES_ENTRY_NUM(MAX_NAME_LENGTH)
/*
* 19 entries x 32 bytes/entry = 608 bytes.
* The 608 bytes are in 3 sectors at most (even 512 Byte sector).
*/
#define DIR_CACHE_SIZE \
(DIV_ROUND_UP(ES_MAX_ENTRY_NUM << DENTRY_SIZE_BITS, SECTOR_SIZE) + 1)
/* Superblock flags */
#define EXFAT_FLAGS_SHUTDOWN 1
struct exfat_dentry_namebuf {
char *lfn;
int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */
};
/* unicode name structure */
struct exfat_uni_name {
/* +3 for null and for converting */
unsigned short name[MAX_NAME_LENGTH + 3];
u16 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 {
struct super_block *sb;
unsigned int start_off;
int num_bh;
struct buffer_head *__bh[DIR_CACHE_SIZE];
struct buffer_head **bh;
unsigned int num_entries;
bool modified;
};
#define IS_DYNAMIC_ES(es) ((es)->__bh != (es)->bh)
struct exfat_dir_entry {
/* the cluster where file dentry is located */
struct exfat_chain dir;
/* the index of file dentry in ->dir */
int entry;
unsigned int type;
unsigned int start_clu;
unsigned char flags;
unsigned short attr;
loff_t size;
loff_t valid_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 */
sys_tz:1, /* Use local timezone */
discard:1, /* Issue discard requests on deletions */
keep_last_dots:1; /* Keep trailing periods in paths */
int time_offset; /* Offset of timestamps from UTC (in minutes) */
/* Support creating zero-size directory, default: false */
bool zero_size_dir;
};
/*
* 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 long long data_start_bytes;
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_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 */
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_exfat_flags; /* Exfat superblock flags */
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;
spinlock_t inode_hash_lock;
struct hlist_head inode_hashtable[EXFAT_HASH_SIZE];
struct rcu_head rcu;
};
#define EXFAT_CACHE_VALID 0
/*
* EXFAT file system inode in-memory data
*/
struct exfat_inode_info {
/* the cluster where file dentry is located */
struct exfat_chain dir;
/* the index of file dentry in ->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;
/* 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;
/* on-disk position of directory entry or 0 */
loff_t i_pos;
loff_t valid_size;
/* page-aligned size that has been zeroed out for mmap */
loff_t zeroed_size;
/* 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);
}
static inline int exfat_forced_shutdown(struct super_block *sb)
{
return test_bit(EXFAT_FLAGS_SHUTDOWN, &EXFAT_SB(sb)->s_exfat_flags);
}
/*
* 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 & EXFAT_ATTR_READONLY) && !(attr & EXFAT_ATTR_SUBDIR))
mode &= ~0222;
if (attr & EXFAT_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 |= EXFAT_ATTR_SUBDIR;
if (exfat_mode_can_hold_ro(inode) && !(inode->i_mode & 0222))
attr |= EXFAT_ATTR_READONLY;
return attr;
}
static inline void exfat_save_attr(struct inode *inode, unsigned short attr)
{
if (exfat_mode_can_hold_ro(inode))
EXFAT_I(inode)->attr = attr & (EXFAT_ATTR_RWMASK | EXFAT_ATTR_READONLY);
else
EXFAT_I(inode)->attr = attr & EXFAT_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 ((sector_t)(clus - EXFAT_RESERVED_CLUSTERS) << sbi->sect_per_clus_bits) +
sbi->data_start_sector;
}
static inline unsigned 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;
}
static inline bool is_valid_cluster(struct exfat_sb_info *sbi,
unsigned int clus)
{
return clus >= EXFAT_FIRST_CLUSTER && clus < sbi->num_clusters;
}
static inline loff_t exfat_ondisk_size(const struct inode *inode)
{
return ((loff_t)inode->i_blocks) << 9;
}
static inline loff_t exfat_cluster_to_phys_bytes(struct exfat_sb_info *sbi,
unsigned int clus)
{
return ((loff_t)(clus - EXFAT_RESERVED_CLUSTERS) << sbi->cluster_size_bits) +
sbi->data_start_bytes;
}
/*
* helpers for cluster size to byte conversion.
*/
static inline loff_t exfat_cluster_to_bytes(struct exfat_sb_info *sbi,
u32 nr_clusters)
{
return (loff_t)nr_clusters << sbi->cluster_size_bits;
}
static inline blkcnt_t exfat_cluster_to_sectors(struct exfat_sb_info *sbi,
u32 nr_clusters)
{
return (blkcnt_t)nr_clusters << (sbi->cluster_size_bits - 9);
}
static inline u32 exfat_bytes_to_cluster(struct exfat_sb_info *sbi, loff_t size)
{
return (u32)(size >> sbi->cluster_size_bits);
}
static inline u32 exfat_bytes_to_cluster_round_up(struct exfat_sb_info *sbi,
loff_t size)
{
if (size <= 0)
return 0;
return (u32)((size - 1) >> sbi->cluster_size_bits) + 1;
}
static inline u32 exfat_cluster_offset(struct exfat_sb_info *sbi, loff_t off)
{
return off & (sbi->cluster_size - 1);
}
/*
* helpers for block size to byte conversion.
*/
static inline loff_t exfat_block_to_bytes(struct super_block *sb,
sector_t block)
{
return (loff_t)block << sb->s_blocksize_bits;
}
static inline sector_t exfat_bytes_to_block(struct super_block *sb, loff_t size)
{
return (sector_t)(size >> sb->s_blocksize_bits);
}
static inline sector_t exfat_bytes_to_block_round_up(struct super_block *sb,
loff_t size)
{
if (size <= 0)
return 0;
return (sector_t)(((size - 1) >> sb->s_blocksize_bits) + 1);
}
static inline u32 exfat_block_offset(struct super_block *sb, loff_t off)
{
return (u32)(off & (sb->s_blocksize - 1));
}
/*
* helpers for block size to dentry size conversion.
*/
static inline u32 exfat_bytes_to_dentries(loff_t b)
{
return (u32)(b >> DENTRY_SIZE_BITS);
}
static inline u32 exfat_dentries_to_bytes(u32 dentry)
{
return dentry << DENTRY_SIZE_BITS;
}
/*
* helpers for cluster size to dentry size conversion.
*/
static inline u32 exfat_cluster_to_dentries(struct exfat_sb_info *sbi,
u32 nr_clusters)
{
return nr_clusters << (sbi->cluster_size_bits - DENTRY_SIZE_BITS);
}
static inline u32 exfat_dentries_to_cluster(struct exfat_sb_info *sbi,
u32 dentry)
{
return dentry >> (sbi->cluster_size_bits - DENTRY_SIZE_BITS);
}
/* super.c */
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_cluster_walk(sb, (pclu), 1, ALLOC_FAT_CHAIN)
int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
struct exfat_chain *p_chain, bool sync_bmap, bool contig);
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, struct buffer_head **last);
int exfat_ent_set(struct super_block *sb, unsigned int loc,
unsigned int content);
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);
int exfat_blk_readahead(struct super_block *sb, sector_t sec,
sector_t *ra, blkcnt_t *ra_cnt, sector_t end);
static inline int
exfat_cluster_walk(struct super_block *sb, unsigned int *clu,
unsigned int step, int flags)
{
struct buffer_head *bh = NULL;
if (flags == ALLOC_NO_FAT_CHAIN) {
(*clu) += step;
return 0;
}
while (step--) {
if (exfat_ent_get(sb, *clu, clu, &bh))
return -EIO;
}
brelse(bh);
return 0;
}
/* balloc.c */
int exfat_load_bitmap(struct super_block *sb);
void exfat_free_bitmap(struct exfat_sb_info *sbi);
int exfat_set_bitmap(struct super_block *sb, unsigned int clu, bool sync);
int exfat_clear_bitmap(struct super_block *sb, unsigned int clu, bool sync);
bool exfat_test_bitmap(struct super_block *sb, 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);
int exfat_trim_fs(struct inode *inode, struct fstrim_range *range);
/* file.c */
extern const struct file_operations exfat_file_operations;
int __exfat_truncate(struct inode *inode);
int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
int exfat_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, unsigned int request_mask,
unsigned int query_flags);
struct file_kattr;
int exfat_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
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);
int exfat_force_shutdown(struct super_block *sb, u32 flags);
/* namei.c */
extern const struct dentry_operations exfat_dentry_ops;
extern const struct dentry_operations exfat_utf8_dentry_ops;
int exfat_find_empty_entry(struct inode *inode,
struct exfat_chain *p_dir, int num_entries,
struct exfat_entry_set_cache *es);
/* cache.c */
int exfat_cache_init(void);
void exfat_cache_shutdown(void);
void exfat_cache_inval_inode(struct inode *inode);
int exfat_get_cluster(struct inode *inode, unsigned int cluster,
unsigned int *dclus, unsigned int *count, unsigned int *last_dclus);
/* 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);
void exfat_init_dir_entry(struct exfat_entry_set_cache *es,
unsigned int type, unsigned int start_clu,
unsigned long long size, struct timespec64 *ts);
void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
struct exfat_uni_name *p_uniname,
struct exfat_entry_set_cache *old_es, int num_extra);
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
int order, bool free_benign);
void exfat_update_dir_chksum(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,
struct exfat_hint *hint_opt);
int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu);
struct exfat_dentry *exfat_get_dentry(struct super_block *sb,
struct exfat_chain *p_dir, int entry, struct buffer_head **bh);
struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es,
int num);
int exfat_get_dentry_set(struct exfat_entry_set_cache *es,
struct super_block *sb, struct exfat_chain *p_dir, int entry,
unsigned int num_entries);
#define exfat_get_dentry_set_by_ei(es, sb, ei) \
exfat_get_dentry_set(es, sb, &(ei)->dir, (ei)->entry, ES_ALL_ENTRIES)
int exfat_get_empty_dentry_set(struct exfat_entry_set_cache *es,
struct super_block *sb, struct exfat_chain *p_dir, int entry,
unsigned int num_entries);
int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync);
int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir);
int exfat_read_volume_label(struct super_block *sb,
struct exfat_uni_name *label_out);
int exfat_write_volume_label(struct super_block *sb,
struct exfat_uni_name *label);
static inline int exfat_chain_advance(struct super_block *sb,
struct exfat_chain *chain, unsigned int step)
{
unsigned int clu = chain->dir;
if (unlikely(chain->size < step))
return -EIO;
if (exfat_cluster_walk(sb, &clu, step, chain->flags))
return -EIO;
chain->size -= step;
if (chain->size == 0 && chain->flags == ALLOC_NO_FAT_CHAIN)
chain->dir = EXFAT_EOF_CLUSTER;
else
chain->dir = clu;
return 0;
}
/* 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, int sync);
int exfat_write_inode(struct inode *inode, struct writeback_control *wbc);
void exfat_evict_inode(struct inode *inode);
int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int *clu, unsigned int *count, int create,
bool *balloc);
/* 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);
/* 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)
/* expand to pr_*() with prefix */
#define exfat_err(sb, fmt, ...) \
pr_err("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
#define exfat_warn(sb, fmt, ...) \
pr_warn("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
#define exfat_info(sb, fmt, ...) \
pr_info("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
#define exfat_debug(sb, fmt, ...) \
pr_debug("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__)
void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts,
u8 tz, __le16 time, __le16 date, u8 time_cs);
void exfat_truncate_atime(struct timespec64 *ts);
void exfat_truncate_inode_atime(struct inode *inode);
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);
int 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);
#endif /* !_EXFAT_FS_H */
+197
View File
@@ -0,0 +1,197 @@
/* 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 <linux/types.h>
#define BOOT_SIGNATURE 0xAA55
#define EXBOOT_SIGNATURE 0xAA550000
#define STR_EXFAT "EXFAT " /* size should be 8 */
#define EXFAT_MAX_FILE_LEN 255
#define VOLUME_DIRTY 0x0002
#define MEDIA_FAILURE 0x0004
#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)
#define EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5)
/* AllocationPossible and NoFatChain field in GeneralSecondaryFlags Field */
#define ALLOC_POSSIBLE 0x01
#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 EXFAT_VENDOR_EXT 0xE0 /* vendor extension entry */
#define EXFAT_VENDOR_ALLOC 0xE1 /* vendor allocation entry */
#define IS_EXFAT_CRITICAL_PRI(x) (x < 0xA0)
#define IS_EXFAT_BENIGN_PRI(x) (x < 0xC0)
#define IS_EXFAT_CRITICAL_SEC(x) (x < 0xE0)
/* checksum types */
#define CS_DIR_ENTRY 0
#define CS_BOOT_SECTOR 1
#define CS_DEFAULT 2
/* file attributes */
#define EXFAT_ATTR_READONLY 0x0001
#define EXFAT_ATTR_HIDDEN 0x0002
#define EXFAT_ATTR_SYSTEM 0x0004
#define EXFAT_ATTR_VOLUME 0x0008
#define EXFAT_ATTR_SUBDIR 0x0010
#define EXFAT_ATTR_ARCHIVE 0x0020
#define EXFAT_ATTR_RWMASK (EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM | \
EXFAT_ATTR_VOLUME | EXFAT_ATTR_SUBDIR | \
EXFAT_ATTR_ARCHIVE)
#define BOOTSEC_JUMP_BOOT_LEN 3
#define BOOTSEC_FS_NAME_LEN 8
#define BOOTSEC_OLDBPB_LEN 53
#define EXFAT_FILE_NAME_LEN 15
#define EXFAT_VOLUME_LABEL_LEN 11
#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];
__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 {
__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_cs;
__u8 modify_time_cs;
__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 */
struct {
__u8 char_count;
__le16 volume_label[EXFAT_VOLUME_LABEL_LEN];
__u8 reserved[8];
} __packed volume_label; /* volume label directory entry */
struct {
__u8 flags;
__u8 vendor_guid[16];
__u8 vendor_defined[14];
} __packed vendor_ext; /* vendor extension directory entry */
struct {
__u8 flags;
__u8 vendor_guid[16];
__u8 vendor_defined[2];
__le32 start_clu;
__le64 size;
} __packed vendor_alloc; /* vendor allocation directory entry */
struct {
__u8 flags;
__u8 custom_defined[18];
__le32 start_clu;
__le64 size;
} __packed generic_secondary; /* generic secondary directory entry */
} __packed dentry;
} __packed;
#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 */
+585
View File
@@ -0,0 +1,585 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/slab.h>
#include <linux/unaligned.h>
#include <linux/buffer_head.h>
#include <linux/blkdev.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh)
{
struct buffer_head *c_bh;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
sector_t sec = bh->b_blocknr;
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);
err = exfat_update_bh(c_bh, sb->s_flags & SB_SYNCHRONOUS);
brelse(c_bh);
}
return err;
}
static int exfat_end_bh(struct super_block *sb, struct buffer_head *bh)
{
int err;
err = exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS);
if (!err)
err = exfat_mirror_bh(sb, bh);
brelse(bh);
return err;
}
static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
unsigned int *content, struct buffer_head **cache)
{
unsigned int off;
sector_t sec;
struct buffer_head *bh = *cache;
sec = FAT_ENT_OFFSET_SECTOR(sb, loc);
off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc);
if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) {
brelse(bh);
bh = sb_bread(sb, sec);
*cache = bh;
if (unlikely(!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;
return 0;
}
static int __exfat_ent_set(struct super_block *sb, unsigned int loc,
unsigned int content, struct buffer_head **cache)
{
sector_t sec;
__le32 *fat_entry;
struct buffer_head *bh = cache ? *cache : NULL;
unsigned int off;
sec = FAT_ENT_OFFSET_SECTOR(sb, loc);
off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc);
if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) {
if (bh)
exfat_end_bh(sb, bh);
bh = sb_bread(sb, sec);
if (cache)
*cache = bh;
if (unlikely(!bh))
return -EIO;
}
fat_entry = (__le32 *)&(bh->b_data[off]);
*fat_entry = cpu_to_le32(content);
if (!cache)
exfat_end_bh(sb, bh);
return 0;
}
int exfat_ent_set(struct super_block *sb, unsigned int loc,
unsigned int content)
{
return __exfat_ent_set(sb, loc, content, NULL);
}
/*
* Caller must release the buffer_head if no error return.
*/
int exfat_ent_get(struct super_block *sb, unsigned int loc,
unsigned int *content, struct buffer_head **cache)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
if (!is_valid_cluster(sbi, loc)) {
exfat_fs_error_ratelimit(sb,
"invalid access to FAT (entry 0x%08x)",
loc);
goto err;
}
if (unlikely(__exfat_ent_get(sb, loc, content, cache))) {
exfat_fs_error_ratelimit(sb,
"failed to access to FAT (entry 0x%08x)",
loc);
goto err;
}
if (unlikely(*content == EXFAT_FREE_CLUSTER)) {
exfat_fs_error_ratelimit(sb,
"invalid access to FAT free cluster (entry 0x%08x)",
loc);
goto err;
}
if (unlikely(*content == EXFAT_BAD_CLUSTER)) {
exfat_fs_error_ratelimit(sb,
"invalid access to FAT bad cluster (entry 0x%08x)",
loc);
goto err;
}
if (*content != EXFAT_EOF_CLUSTER && !is_valid_cluster(sbi, *content)) {
exfat_fs_error_ratelimit(sb,
"invalid access to FAT (entry 0x%08x) bogus content (0x%08x)",
loc, *content);
goto err;
}
return 0;
err:
/* Avoid double release */
brelse(*cache);
*cache = NULL;
return -EIO;
}
int exfat_blk_readahead(struct super_block *sb, sector_t sec,
sector_t *ra, blkcnt_t *ra_cnt, sector_t end)
{
struct blk_plug plug;
if (sec < *ra)
return 0;
*ra += *ra_cnt;
/* No blocks left (or only the last block), skip readahead. */
if (*ra >= end)
return 0;
*ra_cnt = min(end - *ra + 1, EXFAT_BLK_RA_SIZE(sb));
if (*ra_cnt == 0) {
/* Move 'ra' to the end to disable readahead. */
*ra = end;
return 0;
}
blk_start_plug(&plug);
for (unsigned int i = 0; i < *ra_cnt; i++)
sb_breadahead(sb, *ra + i);
blk_finish_plug(&plug);
return 0;
}
int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
unsigned int len)
{
struct buffer_head *bh = NULL;
sector_t sec, end, ra;
blkcnt_t ra_cnt = 0;
if (!len)
return 0;
ra = FAT_ENT_OFFSET_SECTOR(sb, chain);
end = FAT_ENT_OFFSET_SECTOR(sb, chain + len - 1);
while (len > 1) {
sec = FAT_ENT_OFFSET_SECTOR(sb, chain);
exfat_blk_readahead(sb, sec, &ra, &ra_cnt, end);
if (__exfat_ent_set(sb, chain, chain + 1, &bh))
return -EIO;
chain++;
len--;
}
if (__exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER, &bh))
return -EIO;
exfat_end_bh(sb, bh);
return 0;
}
static inline void exfat_discard_cluster(struct super_block *sb,
unsigned int clu, unsigned int num_clusters)
{
int ret;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
ret = sb_issue_discard(sb, exfat_cluster_to_sector(sbi, clu),
sbi->sect_per_clus * num_clusters, GFP_NOFS, 0);
if (ret == -EOPNOTSUPP) {
exfat_err(sb, "discard not supported by device, disabling");
sbi->options.discard = 0;
}
}
/* This function must be called with bitmap_lock held */
static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain)
{
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 ||
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 (!is_valid_cluster(sbi, p_chain->dir)) {
exfat_err(sb, "invalid start cluster (%u)", p_chain->dir);
return -EIO;
}
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) {
int err;
unsigned int last_cluster = p_chain->dir + p_chain->size - 1;
do {
bool sync = false;
if (clu < last_cluster)
next_cmap_i =
BITMAP_OFFSET_SECTOR_INDEX(sb, CLUSTER_TO_BITMAP_ENT(clu+1));
/* flush bitmap only if index would be changed or for last cluster */
if (clu == last_cluster || cur_cmap_i != next_cmap_i) {
sync = true;
cur_cmap_i = next_cmap_i;
}
err = exfat_clear_bitmap(sb, clu, (sync && IS_DIRSYNC(inode)));
if (err)
break;
clu++;
num_clusters++;
} while (num_clusters < p_chain->size);
if (sbi->options.discard)
exfat_discard_cluster(sb, p_chain->dir, p_chain->size);
} else {
unsigned int nr_clu = 1;
do {
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;
}
if (exfat_clear_bitmap(sb, clu, (sync && IS_DIRSYNC(inode))))
break;
if (sbi->options.discard) {
if (n_clu == clu + 1)
nr_clu++;
else {
exfat_discard_cluster(sb, clu - nr_clu + 1, nr_clu);
nr_clu = 1;
}
}
clu = n_clu;
num_clusters++;
if (err)
break;
if (num_clusters >= sbi->num_clusters - EXFAT_FIRST_CLUSTER) {
/*
* The cluster chain includes a loop, scan the
* bitmap to get the number of used clusters.
*/
exfat_count_used_clusters(sb, &sbi->used_clusters);
return 0;
}
} while (clu != EXFAT_EOF_CLUSTER);
}
sbi->used_clusters -= num_clusters;
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)
{
struct buffer_head *bh = NULL;
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, &bh))
return -EIO;
} while (next != EXFAT_EOF_CLUSTER && count <= p_chain->size);
brelse(bh);
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;
}
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 *bh;
sector_t blknr, last_blknr, i;
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 */
for (i = blknr; i < last_blknr; i++) {
bh = sb_getblk(sb, i);
if (!bh)
return -ENOMEM;
memset(bh->b_data, 0, sb->s_blocksize);
set_buffer_uptodate(bh);
mark_buffer_dirty(bh);
brelse(bh);
}
if (IS_DIRSYNC(dir))
return sync_blockdev_range(sb->s_bdev,
exfat_block_to_bytes(sb, blknr),
exfat_block_to_bytes(sb, last_blknr) - 1);
return 0;
}
int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
struct exfat_chain *p_chain, bool sync_bmap, bool contig)
{
int ret = -ENOSPC;
unsigned int 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;
mutex_lock(&sbi->bitmap_lock);
hint_clu = p_chain->dir;
/* find new cluster */
if (hint_clu == EXFAT_EOF_CLUSTER) {
if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) {
exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)",
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) {
ret = -ENOSPC;
goto unlock;
}
}
/* check cluster validation */
if (!is_valid_cluster(sbi, hint_clu)) {
if (hint_clu != sbi->num_clusters)
exfat_err(sb, "hint_cluster is invalid (%u), rewind to the first cluster",
hint_clu);
hint_clu = EXFAT_FIRST_CLUSTER;
p_chain->flags = ALLOC_FAT_CHAIN;
}
p_chain->dir = EXFAT_EOF_CLUSTER;
while ((new_clu = exfat_find_free_bitmap(sb, hint_clu)) !=
EXFAT_EOF_CLUSTER) {
if (new_clu != hint_clu) {
if (p_chain->flags == ALLOC_NO_FAT_CHAIN) {
if (exfat_chain_cont_cluster(sb, p_chain->dir,
p_chain->size)) {
ret = -EIO;
goto free_cluster;
}
p_chain->flags = ALLOC_FAT_CHAIN;
}
if (contig && p_chain->size > 0) {
hint_clu = last_clu;
goto done;
}
}
/* update allocation bitmap */
if (exfat_set_bitmap(sb, new_clu, sync_bmap)) {
ret = -EIO;
goto free_cluster;
}
/* 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;
}
}
p_chain->size++;
last_clu = new_clu;
if (p_chain->size == num_alloc) {
done:
sbi->clu_srch_ptr = hint_clu;
sbi->used_clusters += p_chain->size;
mutex_unlock(&sbi->bitmap_lock);
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,
p_chain->size)) {
ret = -EIO;
goto free_cluster;
}
p_chain->flags = ALLOC_FAT_CHAIN;
}
}
}
free_cluster:
__exfat_free_cluster(inode, p_chain);
unlock:
mutex_unlock(&sbi->bitmap_lock);
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);
struct buffer_head *bh = NULL;
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, &bh))
return -EIO;
if (clu == EXFAT_EOF_CLUSTER)
break;
}
brelse(bh);
*ret_count = count;
/*
* since exfat_count_used_clusters() is not called, sbi->used_clusters
* cannot be used here.
*/
if (unlikely(i == sbi->num_clusters && clu != EXFAT_EOF_CLUSTER)) {
exfat_fs_error(sb, "The cluster chain has a loop");
return -EIO;
}
return 0;
}
+979
View File
@@ -0,0 +1,979 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/cred.h>
#include <linux/buffer_head.h>
#include <linux/blkdev.h>
#include <linux/fsnotify.h>
#include <linux/security.h>
#include <linux/msdos_fs.h>
#include <linux/writeback.h>
#include <linux/filelock.h>
#include <linux/falloc.h>
#include <linux/fileattr.h>
#include <linux/iomap.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#include "iomap.h"
static int exfat_cont_expand(struct inode *inode, loff_t size)
{
int ret;
unsigned int num_clusters, new_num_clusters, last_clu;
struct exfat_inode_info *ei = EXFAT_I(inode);
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_chain clu;
loff_t oldsize = i_size_read(inode);
truncate_pagecache(inode, oldsize);
ret = inode_newsize_ok(inode, size);
if (ret)
return ret;
num_clusters = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
/* integer overflow is already checked in inode_newsize_ok(). */
new_num_clusters = exfat_bytes_to_cluster_round_up(sbi, size);
if (new_num_clusters == num_clusters)
goto out;
if (num_clusters) {
exfat_chain_set(&clu, ei->start_clu, num_clusters, ei->flags);
ret = exfat_find_last_cluster(sb, &clu, &last_clu);
if (ret)
return ret;
clu.dir = last_clu + 1;
} else {
last_clu = EXFAT_EOF_CLUSTER;
clu.dir = EXFAT_EOF_CLUSTER;
}
clu.size = 0;
clu.flags = ei->flags;
ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters,
&clu, inode_needs_sync(inode), false);
if (ret)
return ret;
/* Append new clusters to chain */
if (num_clusters) {
if (clu.flags != ei->flags)
if (exfat_chain_cont_cluster(sb, ei->start_clu, num_clusters))
goto free_clu;
if (clu.flags == ALLOC_FAT_CHAIN)
if (exfat_ent_set(sb, last_clu, clu.dir))
goto free_clu;
} else
ei->start_clu = clu.dir;
ei->flags = clu.flags;
out:
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
/* Expanded range not zeroed, do not update valid_size */
i_size_write(inode, size);
/*
* When extending file size, call truncate_pagecache() first,
* then update i_size, and call pagecache_isize_extended()
* to ensures the straddling folio is properly marked RO so
* page_mkwrite() is called and post-EOF area is zeroed.
*/
pagecache_isize_extended(inode, oldsize, inode->i_size);
inode->i_blocks = round_up(size, sbi->cluster_size) >> 9;
mark_inode_dirty(inode);
if (IS_SYNC(inode))
return write_inode_now(inode, 1);
return 0;
free_clu:
exfat_free_cluster(inode, &clu);
return -EIO;
}
/*
* Preallocate space for a file. This implements exfat's fallocate file
* operation, which gets called from sys_fallocate system call. User space
* requests len bytes at offset. In contrary to fat, we only support
* FALLOC_FL_ALLOCATE_RANGE because by leaving the valid data length(VDL)
* field, it is unnecessary to zero out the newly allocated clusters.
*/
static long exfat_fallocate(struct file *file, int mode,
loff_t offset, loff_t len)
{
struct inode *inode = file->f_mapping->host;
loff_t newsize = offset + len;
int err = 0;
/* No support for other modes */
if (mode != FALLOC_FL_ALLOCATE_RANGE)
return -EOPNOTSUPP;
/* No support for dir */
if (!S_ISREG(inode->i_mode))
return -EOPNOTSUPP;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
inode_lock(inode);
if (newsize <= i_size_read(inode))
goto error;
/* This is just an expanding truncate */
err = exfat_cont_expand(inode, newsize);
error:
inode_unlock(inode);
return err;
}
static bool exfat_allow_set_time(struct mnt_idmap *idmap,
struct exfat_sb_info *sbi, struct inode *inode)
{
mode_t allow_utime = sbi->options.allow_utime;
if (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode),
current_fsuid())) {
if (vfsgid_in_group_p(i_gid_into_vfsgid(idmap, inode)))
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)
{
unsigned int num_clusters_new, num_clusters_phys;
unsigned int last_clu = EXFAT_FREE_CLUSTER;
struct exfat_chain clu;
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
/* check if the given file ID is opened */
if (ei->type != TYPE_FILE && ei->type != TYPE_DIR)
return -EPERM;
exfat_set_volume_dirty(sb);
num_clusters_new = exfat_bytes_to_cluster_round_up(sbi, i_size_read(inode));
num_clusters_phys = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags);
if (i_size_read(inode) > 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;
}
if (i_size_read(inode) < ei->valid_size)
ei->valid_size = ei->zeroed_size = i_size_read(inode);
if (ei->type == TYPE_FILE)
ei->attr |= EXFAT_ATTR_ARCHIVE;
/*
* update the directory entry
*
* If the directory entry is updated by mark_inode_dirty(), the
* directory entry will be written after a writeback cycle of
* updating the bitmap/FAT, which may result in clusters being
* freed but referenced by the directory entry in the event of a
* sudden power failure.
* __exfat_write_inode() is called for directory entry, bitmap
* and FAT to be written in a same writeback.
*/
if (__exfat_write_inode(inode, inode_needs_sync(inode)))
return -EIO;
/* 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;
/* 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;
return 0;
}
static void exfat_truncate(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);
int err;
mutex_lock(&sbi->s_lock);
if (ei->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);
if (err)
goto write_size;
inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
write_size:
mutex_unlock(&sbi->s_lock);
}
int exfat_getattr(struct mnt_idmap *idmap, 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(idmap, request_mask, 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;
stat->blksize = EXFAT_SB(inode->i_sb)->cluster_size;
return 0;
}
int exfat_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
{
/*
* exFAT compares filenames through an upcase table, so lookup
* is always case-insensitive. Long names are stored in UTF-16
* with case intact; CASENONPRESERVING stays clear.
*/
fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
fa->flags |= FS_CASEFOLD_FL;
return 0;
}
int exfat_setattr(struct mnt_idmap *idmap, 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 (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
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(idmap, sbi, inode)) {
attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET |
ATTR_TIMES_SET);
}
error = setattr_prepare(idmap, dentry, attr);
attr->ia_valid = ia_valid;
if (error)
goto out;
if (((attr->ia_valid & ATTR_UID) &&
(!uid_eq(from_vfsuid(idmap, i_user_ns(inode), attr->ia_vfsuid),
sbi->options.fs_uid))) ||
((attr->ia_valid & ATTR_GID) &&
(!gid_eq(from_vfsgid(idmap, i_user_ns(inode), attr->ia_vfsgid),
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)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
setattr_copy(idmap, inode, attr);
exfat_truncate_inode_atime(inode);
if (attr->ia_valid & ATTR_SIZE) {
/*
* Wait for any in-flight DIO to finish before truncating to
* prevent a concurrent DIO from writing to clusters that are
* about to be freed.
*/
inode_dio_wait(inode);
down_write(&EXFAT_I(inode)->truncate_lock);
truncate_setsize(inode, attr->ia_size);
/*
* __exfat_write_inode() is called from exfat_truncate(), inode
* is already written by it, so mark_inode_dirty() is unneeded.
*/
exfat_truncate(inode);
up_write(&EXFAT_I(inode)->truncate_lock);
} else
mark_inode_dirty(inode);
out:
return error;
}
/*
* modified ioctls from fat/file.c by Welmer Almesberger
*/
static int exfat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr)
{
u32 attr;
inode_lock_shared(inode);
attr = exfat_make_attr(inode);
inode_unlock_shared(inode);
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;
inode_lock(inode);
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;
ia.ia_ctime = current_time(inode);
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.
*/
err = security_inode_setattr(file_mnt_idmap(file),
file->f_path.dentry, &ia);
if (err)
goto out_unlock_inode;
/* This MUST be done before doing anything irreversible... */
err = exfat_setattr(file_mnt_idmap(file), file->f_path.dentry, &ia);
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:
inode_unlock(inode);
mnt_drop_write_file(file);
out:
return err;
}
static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
{
struct fstrim_range range;
int ret = 0;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (!bdev_max_discard_sectors(inode->i_sb->s_bdev))
return -EOPNOTSUPP;
if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range)))
return -EFAULT;
range.minlen = max_t(unsigned int, range.minlen,
bdev_discard_granularity(inode->i_sb->s_bdev));
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;
}
static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
{
u32 flags;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (get_user(flags, (__u32 __user *)arg))
return -EFAULT;
return exfat_force_shutdown(sb, flags);
}
static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long arg)
{
int ret;
char label[FSLABEL_MAX] = {0};
struct exfat_uni_name uniname;
ret = exfat_read_volume_label(sb, &uniname);
if (ret < 0)
return ret;
ret = exfat_utf16_to_nls(sb, &uniname, label, uniname.name_len);
if (ret < 0)
return ret;
if (copy_to_user((char __user *)arg, label, ret + 1))
return -EFAULT;
return 0;
}
static int exfat_ioctl_set_volume_label(struct super_block *sb,
unsigned long arg)
{
int ret = 0, lossy, label_len;
char label[FSLABEL_MAX] = {0};
struct exfat_uni_name uniname;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (copy_from_user(label, (char __user *)arg, FSLABEL_MAX))
return -EFAULT;
memset(&uniname, 0, sizeof(uniname));
label_len = strnlen(label, FSLABEL_MAX - 1);
if (label[0]) {
ret = exfat_nls_to_utf16(sb, label, label_len,
&uniname, &lossy);
if (ret < 0)
return ret;
else if (lossy & NLS_NAME_LOSSY)
return -EINVAL;
}
uniname.name_len = ret;
return exfat_write_volume_label(sb, &uniname);
}
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
u32 __user *user_attr = (u32 __user *)arg;
switch (cmd) {
case 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 EXFAT_IOC_SHUTDOWN:
return exfat_ioctl_shutdown(inode->i_sb, arg);
case FITRIM:
return exfat_ioctl_fitrim(inode, arg);
case FS_IOC_GETFSLABEL:
return exfat_ioctl_get_volume_label(inode->i_sb, arg);
case FS_IOC_SETFSLABEL:
return exfat_ioctl_set_volume_label(inode->i_sb, 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;
int err;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
err = simple_fsync_noflush(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);
}
static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t old_valid_size = ei->valid_size;
int ret = 0;
if (old_valid_size < new_valid_size) {
if (i_size_read(inode) < new_valid_size) {
i_size_write(inode, new_valid_size);
mark_inode_dirty(inode);
}
ret = iomap_zero_range(inode, old_valid_size,
new_valid_size - old_valid_size, NULL,
&exfat_write_iomap_ops, NULL, NULL);
if (ret) {
truncate_setsize(inode, old_valid_size);
exfat_truncate(inode);
}
}
return ret;
}
static ssize_t exfat_fallback_buffered_write(struct kiocb *iocb,
struct iov_iter *from)
{
loff_t offset = iocb->ki_pos, end;
ssize_t written;
int ret;
iocb->ki_flags &= ~IOCB_DIRECT;
written = iomap_file_buffered_write(iocb, from, &exfat_write_iomap_ops,
NULL, NULL);
if (written < 0)
return written;
end = iocb->ki_pos + written - 1;
ret = filemap_write_and_wait_range(iocb->ki_filp->f_mapping,
offset, end);
if (ret)
return -EIO;
invalidate_mapping_pages(iocb->ki_filp->f_mapping,
offset >> PAGE_SHIFT,
end >> PAGE_SHIFT);
return written;
}
static ssize_t exfat_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
ssize_t ret;
ret = iomap_dio_rw(iocb, from, &exfat_write_iomap_ops,
&exfat_write_dio_ops, 0, NULL, 0);
if (ret == -ENOTBLK)
ret = 0;
else if (ret < 0)
return ret;
if (iov_iter_count(from)) {
ssize_t written;
written = exfat_fallback_buffered_write(iocb, from);
if (written < 0)
return written;
ret += written;
}
return ret;
}
static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
{
ssize_t ret;
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = iocb->ki_pos;
loff_t valid_size;
int err;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
inode_lock(inode);
if (pos > i_size_read(inode))
truncate_pagecache(inode, i_size_read(inode));
valid_size = ei->valid_size;
ret = generic_write_checks(iocb, iter);
if (ret <= 0)
goto unlock;
err = file_modified(iocb->ki_filp);
if (err) {
ret = err;
goto unlock;
}
if (pos > valid_size) {
ret = exfat_extend_valid_size(inode, pos);
if (ret < 0 && ret != -ENOSPC) {
exfat_err(inode->i_sb,
"write: fail to zero from %llu to %llu(%zd)",
valid_size, pos, ret);
}
if (ret < 0)
goto unlock;
}
if (iocb->ki_flags & IOCB_DIRECT)
ret = exfat_dio_write_iter(iocb, iter);
else
ret = iomap_file_buffered_write(iocb, iter,
&exfat_write_iomap_ops, NULL, NULL);
if (ret < 0)
goto unlock;
inode_unlock(inode);
if (pos > valid_size)
pos = valid_size;
if (iocb->ki_pos > pos) {
ssize_t err = generic_write_sync(iocb, iocb->ki_pos - pos);
if (err < 0)
return err;
}
return ret;
unlock:
inode_unlock(inode);
return ret;
}
static ssize_t exfat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
struct inode *inode = file_inode(iocb->ki_filp);
ssize_t ret;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
inode_lock_shared(inode);
if (iocb->ki_flags & IOCB_DIRECT) {
file_accessed(iocb->ki_filp);
ret = iomap_dio_rw(iocb, iter, &exfat_iomap_ops, NULL, 0,
NULL, 0);
} else {
ret = generic_file_read_iter(iocb, iter);
}
inode_unlock_shared(inode);
return ret;
}
static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
struct exfat_inode_info *ei = EXFAT_I(inode);
vm_fault_t ret;
loff_t new_valid_size, mmap_valid_size;
if (!inode_trylock(inode))
return VM_FAULT_RETRY;
mmap_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
new_valid_size = min(mmap_valid_size, i_size_read(inode));
if (ei->valid_size < new_valid_size) {
if (ei->zeroed_size < mmap_valid_size) {
int err;
/*
* Only zero the range that hasn't been zeroed yet for
* this mmap write path. zeroed_size tracks the largest
* page-aligned offset that has already been zeroed.
*
* This prevents unnecessarily zeroing out the entire
* tail page on every page fault when userspace writes
* data byte-by-byte through mmap (after a small
* fallocate). It fixes data corruption in the tail page
* while preserving the existing valid_size semantics.
*/
err = iomap_zero_range(inode, ei->zeroed_size,
mmap_valid_size - ei->zeroed_size, NULL,
&exfat_iomap_ops, NULL, NULL);
if (err < 0) {
inode_unlock(inode);
return vmf_fs_error(err);
}
ei->zeroed_size = mmap_valid_size;
}
ei->valid_size = new_valid_size;
mark_inode_dirty(inode);
}
sb_start_pagefault(inode->i_sb);
file_update_time(vmf->vma->vm_file);
filemap_invalidate_lock_shared(inode->i_mapping);
ret = iomap_page_mkwrite(vmf, &exfat_write_iomap_ops, NULL);
filemap_invalidate_unlock_shared(inode->i_mapping);
sb_end_pagefault(inode->i_sb);
inode_unlock(inode);
return ret;
}
static const struct vm_operations_struct exfat_file_vm_ops = {
.fault = filemap_fault,
.map_pages = filemap_map_pages,
.page_mkwrite = exfat_page_mkwrite,
};
static int exfat_file_mmap_prepare(struct vm_area_desc *desc)
{
struct file *file = desc->file;
if (unlikely(exfat_forced_shutdown(file_inode(desc->file)->i_sb)))
return -EIO;
if (vma_desc_test_all(desc, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)) {
struct inode *inode = file_inode(file);
loff_t from, to;
int err;
from = ((loff_t)desc->pgoff << PAGE_SHIFT);
to = min_t(loff_t, i_size_read(inode),
from + vma_desc_size(desc));
if (EXFAT_I(inode)->valid_size < to) {
err = exfat_extend_valid_size(inode, to);
if (err)
return err;
}
}
file_accessed(file);
desc->vm_ops = &exfat_file_vm_ops;
return 0;
}
static ssize_t exfat_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len, unsigned int flags)
{
if (unlikely(exfat_forced_shutdown(file_inode(in)->i_sb)))
return -EIO;
return filemap_splice_read(in, ppos, pipe, len, flags);
}
static int exfat_file_open(struct inode *inode, struct file *filp)
{
int err;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
err = generic_file_open(inode, filp);
if (err)
return err;
filp->f_mode |= FMODE_CAN_ODIRECT;
return 0;
}
static loff_t exfat_file_llseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file->f_mapping->host;
switch (whence) {
case SEEK_HOLE:
inode_lock_shared(inode);
offset = iomap_seek_hole(inode, offset, &exfat_iomap_ops);
inode_unlock_shared(inode);
break;
case SEEK_DATA:
inode_lock_shared(inode);
offset = iomap_seek_data(inode, offset, &exfat_iomap_ops);
inode_unlock_shared(inode);
break;
default:
return generic_file_llseek(file, offset, whence);
}
if (offset < 0)
return offset;
return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
}
const struct file_operations exfat_file_operations = {
.open = exfat_file_open,
.llseek = exfat_file_llseek,
.read_iter = exfat_file_read_iter,
.write_iter = exfat_file_write_iter,
.unlocked_ioctl = exfat_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = exfat_compat_ioctl,
#endif
.mmap_prepare = exfat_file_mmap_prepare,
.fsync = exfat_file_fsync,
.splice_read = exfat_splice_read,
.splice_write = iter_file_splice_write,
.fallocate = exfat_fallocate,
.setlease = generic_setlease,
};
const struct inode_operations exfat_file_inode_operations = {
.setattr = exfat_setattr,
.getattr = exfat_getattr,
.fileattr_get = exfat_fileattr_get,
};
+460
View File
@@ -0,0 +1,460 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/init.h>
#include <linux/buffer_head.h>
#include <linux/mpage.h>
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <linux/time.h>
#include <linux/writeback.h>
#include <linux/uio.h>
#include <linux/random.h>
#include <linux/iversion.h>
#include <linux/iomap.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#include "iomap.h"
int __exfat_write_inode(struct inode *inode, int sync)
{
unsigned long long on_disk_size;
unsigned long long on_disk_valid_size;
struct exfat_dentry *ep, *ep2;
struct exfat_entry_set_cache es;
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);
struct timespec64 ts;
if (inode->i_ino == EXFAT_ROOT_INO)
return 0;
/*
* If the inode 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_volume_dirty(sb);
/* get the directory entry of given file or directory */
if (exfat_get_dentry_set_by_ei(&es, sb, ei))
return -EIO;
ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
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_cs);
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);
/*
* During a DIO write, valid_size is updated eagerly in iomap_end (so
* that concurrent buffered reads see IOMAP_MAPPED) while i_size is
* updated asynchronously in end_io. The FAT chain was already
* extended to cover ceil(valid_size/cluster_size) clusters. Use the
* maximum so the on-disk size field always covers the FAT chain,
* preventing fsck from reporting "more clusters are allocated".
*/
on_disk_size = max_t(unsigned long long, i_size_read(inode),
ei->valid_size);
if (ei->start_clu == EXFAT_EOF_CLUSTER)
on_disk_size = 0;
/*
* valid_size on disk must reflect only confirmed data (up to i_size)
* and must not exceed on_disk_size.
*/
on_disk_valid_size = min_t(unsigned long long, ei->valid_size,
i_size_read(inode));
if (ei->start_clu == EXFAT_EOF_CLUSTER)
on_disk_valid_size = 0;
ep2->dentry.stream.size = cpu_to_le64(on_disk_size);
ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_valid_size);
if (on_disk_size) {
ep2->dentry.stream.flags = ei->flags;
ep2->dentry.stream.start_clu = cpu_to_le32(ei->start_clu);
} else {
ep2->dentry.stream.flags = ALLOC_FAT_CHAIN;
ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER;
}
exfat_update_dir_chksum(&es);
return exfat_put_dentry_set(&es, sync);
}
int exfat_write_inode(struct inode *inode, struct writeback_control *wbc)
{
int ret;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
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
*/
int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int *clu, unsigned int *count, int create,
bool *balloc)
{
int ret;
unsigned int last_clu;
struct exfat_chain new_clu;
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;
num_clusters = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
if (clu_offset > num_clusters ||
*count > num_clusters - clu_offset)
num_to_be_allocated = clu_offset + *count - num_clusters;
if (!create && (num_to_be_allocated > 0)) {
*clu = EXFAT_EOF_CLUSTER;
return 0;
}
*clu = last_clu = ei->start_clu;
if (*clu == EXFAT_EOF_CLUSTER) {
*count = 0;
} else if (ei->flags == ALLOC_NO_FAT_CHAIN) {
last_clu += num_clusters - 1;
if (clu_offset < num_clusters) {
*clu += clu_offset;
*count = min(num_clusters - clu_offset, *count);
} else {
*clu = EXFAT_EOF_CLUSTER;
*count = 0;
}
} else {
int err = exfat_get_cluster(inode, clu_offset,
clu, count, &last_clu);
if (err)
return -EIO;
}
if (*clu == EXFAT_EOF_CLUSTER) {
exfat_set_volume_dirty(sb);
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,
inode_needs_sync(inode), true);
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;
} else {
if (new_clu.flags != ei->flags) {
/* no-fat-chain bit is disabled,
* so fat-chain should be synced with
* alloc-bitmap
*/
if (exfat_chain_cont_cluster(sb, ei->start_clu,
num_clusters))
return -EIO;
ei->flags = ALLOC_FAT_CHAIN;
}
if (new_clu.flags == ALLOC_FAT_CHAIN)
if (exfat_ent_set(sb, last_clu, new_clu.dir))
return -EIO;
}
*clu = new_clu.dir;
*count = new_clu.size;
inode->i_blocks += exfat_cluster_to_sectors(sbi, new_clu.size);
if (balloc)
*balloc = true;
}
/* hint information */
ei->hint_bmap.off = local_clu_offset;
ei->hint_bmap.clu = *clu;
return 0;
}
static int exfat_read_folio(struct file *file, struct folio *folio)
{
struct iomap_read_folio_ctx ctx = {
.cur_folio = folio,
.ops = &exfat_iomap_bio_read_ops,
};
iomap_read_folio(&exfat_iomap_ops, &ctx, NULL);
return 0;
}
static void exfat_readahead(struct readahead_control *rac)
{
struct address_space *mapping = rac->mapping;
struct inode *inode = mapping->host;
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = readahead_pos(rac);
struct iomap_read_folio_ctx ctx = {
.ops = &exfat_iomap_bio_read_ops,
.rac = rac,
};
/* Range cross valid_size, read it page by page. */
if (ei->valid_size < i_size_read(inode) &&
pos <= ei->valid_size &&
ei->valid_size < pos + readahead_length(rac))
return;
iomap_readahead(&exfat_iomap_ops, &ctx, NULL);
}
static int exfat_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
struct iomap_writepage_ctx wpc = {
.inode = mapping->host,
.wbc = wbc,
.ops = &exfat_writeback_ops,
};
if (unlikely(exfat_forced_shutdown(mapping->host->i_sb)))
return -EIO;
return iomap_writepages(&wpc);
}
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 = iomap_bmap(mapping, block, &exfat_iomap_ops);
up_read(&EXFAT_I(mapping->host)->truncate_lock);
return blocknr;
}
static const struct address_space_operations exfat_aops = {
.read_folio = exfat_read_folio,
.readahead = exfat_readahead,
.writepages = exfat_writepages,
.dirty_folio = iomap_dirty_folio,
.bmap = exfat_aop_bmap,
.migrate_folio = filemap_migrate_folio,
.is_partially_uptodate = iomap_is_partially_uptodate,
.error_remove_folio = generic_error_remove_folio,
.release_folio = iomap_release_folio,
.invalidate_folio = iomap_invalidate_folio,
.swap_activate = exfat_iomap_swap_activate,
};
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;
ei->dir = info->dir;
ei->entry = info->entry;
ei->attr = info->attr;
ei->start_clu = info->start_clu;
ei->flags = info->flags;
ei->type = info->type;
ei->valid_size = info->valid_size;
ei->zeroed_size = info->valid_size;
ei->version = 0;
ei->hint_stat.eidx = 0;
ei->hint_stat.clu = info->start_clu;
ei->hint_femp.eidx = EXFAT_HINT_NONE;
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 = get_random_u32();
if (info->attr & EXFAT_ATTR_SUBDIR) { /* directory */
inode->i_generation &= ~1;
inode->i_mode = exfat_make_mode(sbi, info->attr, 0777);
inode->i_op = &exfat_dir_inode_operations;
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);
exfat_save_attr(inode, info->attr);
inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
inode_set_mtime_to_ts(inode, info->mtime);
inode_set_ctime_to_ts(inode, info->mtime);
ei->i_crtime = info->crtime;
inode_set_atime_to_ts(inode, info->atime);
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_final(&inode->i_data);
if (!inode->i_nlink) {
i_size_write(inode, 0);
mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
__exfat_truncate(inode);
mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
}
clear_inode(inode);
exfat_cache_inval_inode(inode);
exfat_unhash_inode(inode);
}
+268
View File
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* iomap callack functions
*
* Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
*/
#include <linux/iomap.h>
#include <linux/pagemap.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#include "iomap.h"
/*
* exfat_file_write_dio_end_io - Direct I/O write completion handler
*
* Updates i_size if the write extended the file. Called from the dio layer
* after I/O completion.
*/
static int exfat_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
int error, unsigned int flags)
{
struct inode *inode = file_inode(iocb->ki_filp);
if (error)
return error;
if (size && i_size_read(inode) < iocb->ki_pos + size) {
i_size_write(inode, iocb->ki_pos + size);
mark_inode_dirty(inode);
}
return 0;
}
const struct iomap_dio_ops exfat_write_dio_ops = {
.end_io = exfat_file_write_dio_end_io,
};
static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, bool may_alloc)
{
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 cluster, num_clusters;
loff_t cluster_offset, cluster_length;
int err;
bool balloc = false;
if (!may_alloc) {
/* Completely beyond EOF. Treat as hole */
if (i_size_read(inode) <= offset) {
iomap->type = IOMAP_HOLE;
iomap->addr = IOMAP_NULL_ADDR;
iomap->offset = offset;
iomap->length = length;
return 0;
}
/* Clamp length if the requested range goes beyond i_size */
if (offset + length > i_size_read(inode))
length = round_up(i_size_read(inode),
i_blocksize(inode)) - offset;
}
num_clusters = exfat_bytes_to_cluster_round_up(sbi,
offset + length) - exfat_bytes_to_cluster(sbi, offset);
mutex_lock(&sbi->s_lock);
iomap->bdev = inode->i_sb->s_bdev;
iomap->offset = offset;
err = exfat_map_cluster(inode, exfat_bytes_to_cluster(sbi, offset),
&cluster, &num_clusters, may_alloc, &balloc);
if (err)
goto out;
cluster_offset = exfat_cluster_offset(sbi, offset);
cluster_length = exfat_cluster_to_bytes(sbi, num_clusters);
iomap->length = min_t(loff_t, length, cluster_length - cluster_offset);
iomap->addr = exfat_cluster_to_phys_bytes(sbi, cluster) + cluster_offset;
iomap->type = IOMAP_MAPPED;
iomap->flags = IOMAP_F_MERGED;
if (may_alloc || flags & IOMAP_ZERO) {
if (balloc)
iomap->flags |= IOMAP_F_NEW;
else if (iomap->offset + iomap->length >= ei->valid_size) {
/*
* This is a write that starts at or extends beyond
* the current valid_size. The region between the old
* valid_size and the end of this write needs to be
* zeroed in the page cache to prevent stale data
* exposure (see IOMAP_F_ZERO_TAIL handling in
* __iomap_write_begin()).
*/
iomap->flags |= IOMAP_F_ZERO_TAIL;
}
} else {
/*
* valid_size is tracked in byte granularity and
* marks the exact boundary between valid data and
* holes (or unwritten space).
*
* When IOMAP_REPORT is set (used by lseek(SEEK_HOLE)
* and SEEK_DATA), we return IOMAP_HOLE. This allows
* iomap_seek_hole_iter() to directly return the
* precise byte position.
*
* For normal I/O paths (without IOMAP_REPORT) we
* return IOMAP_UNWRITTEN so the write path can
* distinguish it from a real hole.
*/
if (offset >= ei->valid_size) {
iomap->type = flags & IOMAP_REPORT ?
IOMAP_HOLE : IOMAP_UNWRITTEN;
} else if (offset + iomap->length > ei->valid_size) {
if (flags & IOMAP_REPORT) {
/*
* For SEEK_HOLE/SEEK_DATA, clip the length
* to the exact byte boundary (valid_size).
* This ensures the caller gets the precise
* hole position in byte units.
*/
iomap->length = ei->valid_size - iomap->offset;
} else
iomap->length = round_up(ei->valid_size,
i_blocksize(inode)) -
iomap->offset;
}
}
iomap->flags |= IOMAP_F_MERGED;
out:
mutex_unlock(&sbi->s_lock);
return err;
}
static int exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
{
return __exfat_iomap_begin(inode, offset, length, flags, iomap, false);
}
static int exfat_write_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
{
return __exfat_iomap_begin(inode, offset, length, flags, iomap, true);
}
const struct iomap_ops exfat_iomap_ops = {
.iomap_begin = exfat_iomap_begin,
};
/*
* exfat_write_iomap_end - Update the state after write
*
* Extends ->valid_size to cover the newly written range.
* Marks the inode dirty if metadata was changed.
*/
static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
ssize_t written, unsigned int flags, struct iomap *iomap)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
bool dirtied = false;
loff_t end;
if (!written)
return 0;
end = pos + written;
if (ei->valid_size < end) {
ei->valid_size = end;
if (ei->zeroed_size < end)
ei->zeroed_size = end;
dirtied = true;
}
if (dirtied || iomap->flags & IOMAP_F_SIZE_CHANGED)
mark_inode_dirty(inode);
return written;
}
const struct iomap_ops exfat_write_iomap_ops = {
.iomap_begin = exfat_write_iomap_begin,
.iomap_end = exfat_write_iomap_end,
};
/*
* exfat_writeback_range - Map folio during writeback
*
* Called for each folio during writeback. If the folio falls outside the
* current iomap, remaps by calling read_iomap_begin.
*/
static ssize_t exfat_writeback_range(struct iomap_writepage_ctx *wpc,
struct folio *folio, u64 offset, unsigned int len, u64 end_pos)
{
if (offset < wpc->iomap.offset ||
offset >= wpc->iomap.offset + wpc->iomap.length) {
int error;
error = __exfat_iomap_begin(wpc->inode, offset, len,
0, &wpc->iomap, false);
if (error)
return error;
}
return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
}
const struct iomap_writeback_ops exfat_writeback_ops = {
.writeback_range = exfat_writeback_range,
.writeback_submit = iomap_ioend_writeback_submit,
};
/**
* exfat_iomap_read_end_io - iomap read bio completion handler for exFAT
* @bio: bio that has completed reading
*
* exfat_iomap_begin() rounds up MAPPED extents to the block boundary of
* valid_size. This ensures that any subsequent blocks are treated as
* IOMAP_UNWRITTEN, but it also causes the "straddle block" containing
* valid_size to be read from disk. The disk data beyond valid_size in
* this block is stale and must be zeroed to prevent data leakage.
*/
static void exfat_iomap_read_end_io(struct bio *bio)
{
int error = blk_status_to_errno(bio->bi_status);
struct folio_iter iter;
bio_for_each_folio_all(iter, bio) {
struct folio *folio = iter.folio;
struct exfat_inode_info *ei = EXFAT_I(folio->mapping->host);
s64 valid_size;
loff_t pos = folio_pos(folio);
valid_size = ei->valid_size;
if (pos + iter.offset < valid_size &&
pos + iter.offset + iter.length > valid_size)
folio_zero_segment(folio, offset_in_folio(folio, valid_size),
iter.offset + iter.length);
iomap_finish_folio_read(folio, iter.offset, iter.length, error);
}
bio_put(bio);
}
static void exfat_iomap_bio_submit_read(const struct iomap_iter *iter,
struct iomap_read_folio_ctx *ctx)
{
iomap_bio_submit_read_endio(iter, ctx, exfat_iomap_read_end_io);
}
const struct iomap_read_ops exfat_iomap_bio_read_ops = {
.read_folio_range = iomap_bio_read_folio_range,
.submit_read = exfat_iomap_bio_submit_read,
};
int exfat_iomap_swap_activate(struct swap_info_struct *sis,
struct file *file, sector_t *span)
{
return iomap_swapfile_activate(sis, file, span, &exfat_iomap_ops);
}
+18
View File
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2026 Namjae Jeon <linkinjeon@kernel.org>
*/
#ifndef _LINUX_EXFAT_IOMAP_H
#define _LINUX_EXFAT_IOMAP_H
extern const struct iomap_dio_ops exfat_write_dio_ops;
extern const struct iomap_ops exfat_iomap_ops;
extern const struct iomap_ops exfat_write_iomap_ops;
extern const struct iomap_writeback_ops exfat_writeback_ops;
extern const struct iomap_read_ops exfat_iomap_bio_read_ops;
int exfat_iomap_swap_activate(struct swap_info_struct *sis,
struct file *file, sector_t *span);
#endif /* _LINUX_EXFAT_IOMAP_H */
+207
View File
@@ -0,0 +1,207 @@
// 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 <linux/time.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/buffer_head.h>
#include <linux/blk_types.h>
#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_err(sb, "error, %pV", &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_err(sb, "Filesystem has been set read-only");
}
}
#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);
}
static inline int exfat_tz_offset(struct exfat_sb_info *sbi)
{
if (sbi->options.sys_tz)
return -sys_tz.tz_minuteswest;
return sbi->options.time_offset;
}
/* Convert a EXFAT time/date pair to a UNIX date (seconds since 1 1 70). */
void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts,
u8 tz, __le16 time, __le16 date, u8 time_cs)
{
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_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;
if (tz & EXFAT_TZ_VALID)
/* Adjust timezone to UTC0. */
exfat_adjust_tz(ts, tz & ~EXFAT_TZ_VALID);
else
ts->tv_sec -= exfat_tz_offset(sbi) * 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_cs)
{
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_cs field represent 0 ~ 199cs(1990 ms) */
if (time_cs)
*time_cs = (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;
}
/*
* 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;
}
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);
}
u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type)
{
int i;
u8 *c = (u8 *)data;
for (i = 0; i < len; i++, c++) {
if (unlikely(type == CS_DIR_ENTRY && (i == 2 || i == 3)))
continue;
chksum = ((chksum << 15) | (chksum >> 1)) + *c;
}
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;
}
int exfat_update_bh(struct buffer_head *bh, int sync)
{
int err = 0;
set_buffer_uptodate(bh);
mark_buffer_dirty(bh);
if (sync)
err = sync_dirty_buffer(bh);
return err;
}
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], REQ_SYNC);
}
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)
{
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);
}
File diff suppressed because it is too large Load Diff
+809
View File
@@ -0,0 +1,809 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/buffer_head.h>
#include <linux/unaligned.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
/* Upcase table 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;
__le16 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_err(sb, "failed to %s (err : %d) nls len : %d",
__func__, unilen, len);
return unilen;
}
if (unilen > MAX_NAME_LENGTH) {
exfat_debug(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d",
__func__, len, unilen, MAX_NAME_LENGTH);
return -ENAMETOOLONG;
}
for (i = 0; i < unilen; i++) {
if (*uniname < 0x0020 ||
exfat_wstrchr(bad_uni_chars, *uniname))
lossy |= NLS_NAME_LOSSY;
upname[i] = cpu_to_le16(exfat_toupper(sb, *uniname));
uniname++;
}
*uniname = '\0';
p_uniname->name_len = unilen;
p_uniname->name_hash = exfat_calc_chksum16(upname, unilen << 1, 0,
CS_DEFAULT);
if (p_lossy)
*p_lossy = lossy;
return unilen;
}
#define SURROGATE_MASK 0xfffff800
#define SURROGATE_PAIR 0x0000d800
#define SURROGATE_LOW 0x00000400
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;
__le16 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] = cpu_to_le16(exfat_toupper(sb, *uniname));
uniname++;
unilen++;
}
*uniname = '\0';
p_uniname->name_len = unilen;
p_uniname->name_hash = exfat_calc_chksum16(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;
u32 chksum = 0;
unsigned char skip = false;
unsigned short *upcase_table;
upcase_table = kvcalloc(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_err(sb, "failed to read sector(0x%llx)",
(unsigned long long)sector);
return -EIO;
}
sector++;
for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) {
unsigned short uni = get_unaligned_le16(bh->b_data + i);
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++;
}
}
chksum = exfat_calc_chksum32(bh->b_data, i, chksum, CS_DEFAULT);
brelse(bh);
}
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, chksum, utbl_checksum);
return -EINVAL;
}
static int exfat_load_default_upcase_table(struct super_block *sb)
{
int i;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
unsigned char skip = false;
unsigned short uni = 0, *upcase_table;
unsigned int index = 0;
upcase_table = kvcalloc(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 */
return -EIO;
}
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);
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);
if (tbl_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));
} else {
exfat_fs_error(sb,
"bad upcase table size (0 bytes). Please run fsck");
ret = -EINVAL;
}
brelse(bh);
if (ret && ret != -EIO) {
/* free memory from exfat_load_upcase_table call */
exfat_free_upcase_table(sbi);
goto load_default;
}
/* load successfully */
return ret;
}
if (exfat_get_next_cluster(sb, &clu.dir))
return -EIO;
}
exfat_fs_error(sb, "no upcase table entry. Please run fsck");
load_default:
/* load default upcase table */
return exfat_load_default_upcase_table(sb);
}
void exfat_free_upcase_table(struct exfat_sb_info *sbi)
{
kvfree(sbi->vol_utbl);
sbi->vol_utbl = NULL;
}
+944
View File
@@ -0,0 +1,944 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*/
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/mount.h>
#include <linux/cred.h>
#include <linux/statfs.h>
#include <linux/seq_file.h>
#include <linux/blkdev.h>
#include <linux/fs_struct.h>
#include <linux/iversion.h>
#include <linux/nls.h>
#include <linux/buffer_head.h>
#include <linux/magic.h>
#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_set_iocharset(struct exfat_mount_options *opts,
char *iocharset)
{
opts->iocharset = iocharset;
if (!strcmp(opts->iocharset, "utf8"))
opts->utf8 = 1;
else
opts->utf8 = 0;
}
static void exfat_put_super(struct super_block *sb)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
mutex_lock(&sbi->s_lock);
exfat_clear_volume_dirty(sb);
exfat_free_bitmap(sbi);
brelse(sbi->boot_bh);
mutex_unlock(&sbi->s_lock);
}
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);
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 = u64_to_fsid(id);
/* Unicode utf16 255 characters */
buf->f_namelen = EXFAT_MAX_FILE_LEN * NLS_MAX_CHARSET_SIZE;
return 0;
}
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;
/* retain persistent-flags */
new_flags |= sbi->vol_flags_persistent;
/* flags are not changed */
if (sbi->vol_flags == new_flags)
return 0;
sbi->vol_flags = new_flags;
/* skip updating volume dirty flag,
* if this volume has been mounted with read-only
*/
if (sb_rdonly(sb))
return 0;
p_boot->vol_flags = cpu_to_le16(new_flags);
set_buffer_uptodate(sbi->boot_bh);
mark_buffer_dirty(sbi->boot_bh);
__sync_dirty_buffer(sbi->boot_bh, REQ_SYNC | REQ_FUA | REQ_PREFLUSH);
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;
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);
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->keep_last_dots)
seq_puts(m, ",keep_last_dots");
if (opts->sys_tz)
seq_puts(m, ",sys_tz");
else if (opts->time_offset)
seq_printf(m, ",time_offset=%d", opts->time_offset);
if (opts->zero_size_dir)
seq_puts(m, ",zero_size_dir");
return 0;
}
int exfat_force_shutdown(struct super_block *sb, u32 flags)
{
int ret;
struct exfat_sb_info *sbi = sb->s_fs_info;
struct exfat_mount_options *opts = &sbi->options;
if (exfat_forced_shutdown(sb))
return 0;
switch (flags) {
case EXFAT_GOING_DOWN_DEFAULT:
case EXFAT_GOING_DOWN_FULLSYNC:
ret = bdev_freeze(sb->s_bdev);
if (ret)
return ret;
bdev_thaw(sb->s_bdev);
set_bit(EXFAT_FLAGS_SHUTDOWN, &sbi->s_exfat_flags);
break;
case EXFAT_GOING_DOWN_NOSYNC:
set_bit(EXFAT_FLAGS_SHUTDOWN, &sbi->s_exfat_flags);
break;
default:
return -EINVAL;
}
if (opts->discard)
opts->discard = 0;
return 0;
}
static void exfat_shutdown(struct super_block *sb)
{
exfat_force_shutdown(sb, EXFAT_GOING_DOWN_NOSYNC);
}
static struct inode *exfat_alloc_inode(struct super_block *sb)
{
struct exfat_inode_info *ei;
ei = alloc_inode_sb(sb, 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,
.statfs = exfat_statfs,
.show_options = exfat_show_options,
.shutdown = exfat_shutdown,
};
enum {
Opt_uid,
Opt_gid,
Opt_umask,
Opt_dmask,
Opt_fmask,
Opt_allow_utime,
Opt_charset,
Opt_errors,
Opt_discard,
Opt_keep_last_dots,
Opt_sys_tz,
Opt_time_offset,
Opt_zero_size_dir,
/* Deprecated options */
Opt_utf8,
Opt_debug,
Opt_namecase,
Opt_codepage,
};
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_uid("uid", Opt_uid),
fsparam_gid("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, exfat_param_enums),
fsparam_flag_no("discard", Opt_discard),
fsparam_flag("keep_last_dots", Opt_keep_last_dots),
fsparam_flag("sys_tz", Opt_sys_tz),
fsparam_s32("time_offset", Opt_time_offset),
fsparam_flag_no("zero_size_dir", Opt_zero_size_dir),
__fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated,
NULL),
__fsparam(NULL, "debug", Opt_debug, fs_param_deprecated,
NULL),
__fsparam(fs_param_is_u32, "namecase", Opt_namecase,
fs_param_deprecated, NULL),
__fsparam(fs_param_is_u32, "codepage", Opt_codepage,
fs_param_deprecated, NULL),
{}
};
static 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 = result.uid;
break;
case Opt_gid:
opts->fs_gid = result.gid;
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);
exfat_set_iocharset(opts, param->string);
param->string = NULL;
break;
case Opt_errors:
opts->errors = result.uint_32;
break;
case Opt_discard:
opts->discard = !result.negated;
break;
case Opt_keep_last_dots:
opts->keep_last_dots = 1;
break;
case Opt_sys_tz:
opts->sys_tz = 1;
break;
case Opt_time_offset:
/*
* Make the limit 24 just in case someone invents something
* unusual.
*/
if (result.int_32 < -24 * 60 || result.int_32 > 24 * 60)
return -EINVAL;
opts->time_offset = result.int_32;
break;
case Opt_zero_size_dir:
opts->zero_size_dir = !result.negated;
break;
case Opt_utf8:
case Opt_debug:
case Opt_namecase:
case Opt_codepage:
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 exfat_chain *root_clu)
{
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
int num_subdirs;
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->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;
i_size_write(inode, exfat_cluster_to_bytes(sbi, root_clu->size));
num_subdirs = exfat_count_dir_entries(sb, root_clu);
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, EXFAT_ATTR_SUBDIR, 0777);
inode->i_op = &exfat_dir_inode_operations;
inode->i_fop = &exfat_dir_operations;
inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff;
exfat_save_attr(inode, EXFAT_ATTR_SUBDIR);
ei->i_crtime = simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
return 0;
}
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)) {
exfat_err(sb, "bogus logical sector size %u", logical_sect);
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 -EIO;
}
if (logical_sect > sb->s_blocksize) {
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 -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 -EIO;
}
}
return 0;
}
static int exfat_read_boot_sector(struct super_block *sb)
{
struct boot_sector *p_boot;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
/* set block size to read super block */
if (!sb_min_blocksize(sb, 512)) {
exfat_err(sb, "unable to set blocksize");
return -EINVAL;
}
/* read boot sector */
sbi->boot_bh = sb_bread(sb, 0);
if (!sbi->boot_bh) {
exfat_err(sb, "unable to read boot sector");
return -EIO;
}
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");
return -EINVAL;
}
if (memcmp(p_boot->fs_name, STR_EXFAT, BOOTSEC_FS_NAME_LEN)) {
exfat_err(sb, "invalid fs_name"); /* fs_name may unprintable */
return -EINVAL;
}
/*
* 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)))
return -EINVAL;
if (p_boot->num_fats != 1 && p_boot->num_fats != 2) {
exfat_err(sb, "bogus number of FAT structure");
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",
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",
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 +
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 = 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->data_start_bytes = sbi->data_start_sector << p_boot->sect_size_bits;
sbi->num_sectors = le64_to_cpu(p_boot->vol_length);
/* because the cluster index starts with 2 */
sbi->num_clusters = le32_to_cpu(p_boot->clu_count) +
EXFAT_RESERVED_CLUSTERS;
sbi->root_dir = le32_to_cpu(p_boot->root_cluster);
sbi->dentries_per_clu = 1 <<
(sbi->cluster_size_bits - DENTRY_SIZE_BITS);
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;
/* check consistencies */
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 <
(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)
exfat_warn(sb, "Medium has reported failures. Some data may be lost.");
/*
* Set to the max possible volume size for this volume's cluster size so
* that any integer overflow from bytes to cluster size conversion is
* checked in inode_newsize_ok(). Clamped to MAX_LFS_FILESIZE for 32-bit
* machines.
*/
sb->s_maxbytes = min(MAX_LFS_FILESIZE,
exfat_cluster_to_bytes(sbi, (loff_t)EXFAT_MAX_NUM_CLUSTER));
/* check logical sector size */
if (exfat_calibrate_blocksize(sb, 1 << p_boot->sect_size_bits))
return -EIO;
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,
struct exfat_chain *root_clu)
{
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_verify_boot_region(sb);
if (ret) {
exfat_err(sb, "invalid boot region");
goto free_bh;
}
/*
* Call exfat_count_num_cluster() before searching for up-case and
* bitmap directory entries to avoid infinite loop if they are missing
* and the cluster chain includes a loop.
*/
exfat_chain_set(root_clu, sbi->root_dir, 0, ALLOC_FAT_CHAIN);
ret = exfat_count_num_clusters(sb, root_clu, &root_clu->size);
if (ret) {
exfat_err(sb, "failed to count the number of clusters in root");
goto free_bh;
}
ret = exfat_create_upcase_table(sb);
if (ret) {
exfat_err(sb, "failed to load upcase table");
goto free_bh;
}
ret = exfat_load_bitmap(sb);
if (ret) {
exfat_err(sb, "failed to load alloc-bitmap");
goto free_bh;
}
if (!exfat_test_bitmap(sb, sbi->root_dir)) {
exfat_warn(sb, "failed to test first cluster bit of root dir(%u)",
sbi->root_dir);
/*
* The first cluster bit of the root directory should never
* be unset except when storage is corrupted. This bit is
* set to allow operations after mount.
*/
exfat_set_bitmap(sb, sbi->root_dir, false);
}
ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
if (ret) {
exfat_err(sb, "failed to scan clusters");
goto free_alloc_bitmap;
}
return 0;
free_alloc_bitmap:
exfat_free_bitmap(sbi);
free_bh:
brelse(sbi->boot_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;
struct exfat_chain root_clu;
int err;
if (opts->allow_utime == (unsigned short)-1)
opts->allow_utime = ~opts->fs_dmask & 0022;
if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) {
exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard");
opts->discard = 0;
}
sb->s_flags |= SB_NODIRATIME;
sb->s_magic = EXFAT_SUPER_MAGIC;
sb->s_op = &exfat_sops;
sb->s_time_gran = 10 * NSEC_PER_MSEC;
sb->s_time_min = EXFAT_MIN_TIMESTAMP_SECS;
sb->s_time_max = EXFAT_MAX_TIMESTAMP_SECS;
err = __exfat_fill_super(sb, &root_clu);
if (err) {
exfat_err(sb, "failed to recognize exfat type");
goto check_nls_io;
}
/* set up enough so that it can read an inode */
exfat_hash_init(sb);
if (sbi->options.utf8)
set_default_d_op(sb, &exfat_utf8_dentry_ops);
else {
sbi->nls_io = load_nls(sbi->options.iocharset);
if (!sbi->nls_io) {
exfat_err(sb, "IO charset %s not found",
sbi->options.iocharset);
err = -EINVAL;
goto free_table;
}
set_default_d_op(sb, &exfat_dentry_ops);
}
root_inode = new_inode(sb);
if (!root_inode) {
exfat_err(sb, "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, &root_clu);
if (err) {
exfat_err(sb, "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_err(sb, "failed to get the root dentry");
err = -ENOMEM;
goto free_table;
}
return 0;
put_inode:
iput(root_inode);
sb->s_root = NULL;
free_table:
exfat_free_bitmap(sbi);
brelse(sbi->boot_bh);
check_nls_io:
return err;
}
static int exfat_get_tree(struct fs_context *fc)
{
return get_tree_bdev(fc, exfat_fill_super);
}
static void exfat_free_sbi(struct exfat_sb_info *sbi)
{
exfat_free_iocharset(sbi);
kfree(sbi);
}
static void exfat_free(struct fs_context *fc)
{
struct exfat_sb_info *sbi = fc->s_fs_info;
if (sbi)
exfat_free_sbi(sbi);
}
static int exfat_reconfigure(struct fs_context *fc)
{
struct super_block *sb = fc->root->d_sb;
struct exfat_sb_info *remount_sbi = fc->s_fs_info;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_mount_options *new_opts = &remount_sbi->options;
struct exfat_mount_options *cur_opts = &sbi->options;
fc->sb_flags |= SB_NODIRATIME;
sync_filesystem(sb);
mutex_lock(&sbi->s_lock);
exfat_clear_volume_dirty(sb);
mutex_unlock(&sbi->s_lock);
if (new_opts->allow_utime == (unsigned short)-1)
new_opts->allow_utime = ~new_opts->fs_dmask & 0022;
/*
* Since the old settings of these mount options are cached in
* inodes or dentries, they cannot be modified dynamically.
*/
if (strcmp(new_opts->iocharset, cur_opts->iocharset) ||
new_opts->keep_last_dots != cur_opts->keep_last_dots ||
new_opts->sys_tz != cur_opts->sys_tz ||
new_opts->time_offset != cur_opts->time_offset ||
!uid_eq(new_opts->fs_uid, cur_opts->fs_uid) ||
!gid_eq(new_opts->fs_gid, cur_opts->fs_gid) ||
new_opts->fs_fmask != cur_opts->fs_fmask ||
new_opts->fs_dmask != cur_opts->fs_dmask ||
new_opts->allow_utime != cur_opts->allow_utime)
return -EINVAL;
if (new_opts->discard != cur_opts->discard &&
new_opts->discard &&
!bdev_max_discard_sectors(sb->s_bdev)) {
exfat_warn(sb, "remounting with \"discard\" option, but the device does not support discard");
return -EINVAL;
}
swap(*cur_opts, *new_opts);
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)
{
struct exfat_sb_info *sbi;
sbi = kzalloc_obj(struct exfat_sb_info);
if (!sbi)
return -ENOMEM;
mutex_init(&sbi->s_lock);
mutex_init(&sbi->bitmap_lock);
ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL,
DEFAULT_RATELIMIT_BURST);
if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE && fc->root) {
struct super_block *sb = fc->root->d_sb;
struct exfat_mount_options *cur_opts = &EXFAT_SB(sb)->options;
sbi->options.fs_uid = cur_opts->fs_uid;
sbi->options.fs_gid = cur_opts->fs_gid;
sbi->options.fs_fmask = cur_opts->fs_fmask;
sbi->options.fs_dmask = cur_opts->fs_dmask;
} else {
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.errors = EXFAT_ERRORS_RO;
exfat_set_iocharset(&sbi->options, exfat_default_iocharset);
fc->s_fs_info = sbi;
fc->ops = &exfat_context_ops;
return 0;
}
static void 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_upcase_table(sbi);
exfat_free_sbi(sbi);
}
static void exfat_kill_sb(struct super_block *sb)
{
struct exfat_sb_info *sbi = sb->s_fs_info;
kill_block_super(sb);
if (sbi)
call_rcu(&sbi->rcu, delayed_free);
}
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 = exfat_kill_sb,
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
};
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);
}
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,
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_ALIAS_FS("exfat");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("exFAT filesystem support");
MODULE_AUTHOR("Samsung Electronics Co., Ltd.");