summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/exfat/cache.c149
-rw-r--r--fs/exfat/exfat_fs.h7
-rw-r--r--fs/exfat/fatent.c62
-rw-r--r--fs/exfat/file.c16
-rw-r--r--fs/exfat/inode.c57
-rw-r--r--fs/exfat/namei.c4
6 files changed, 170 insertions, 125 deletions
diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c
index d5ce0ae660ba..7c8b4182f5de 100644
--- a/fs/exfat/cache.c
+++ b/fs/exfat/cache.c
@@ -80,41 +80,66 @@ static inline void exfat_cache_update_lru(struct inode *inode,
list_move(&cache->cache_list, &ei->cache_lru);
}
-static unsigned int exfat_cache_lookup(struct inode *inode,
- unsigned int fclus, struct exfat_cache_id *cid,
+/*
+ * 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 offset = EXFAT_EOF_CLUSTER;
+ 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 && hit->fcluster < p->fcluster) {
+ if (p->fcluster <= fclus) {
+ if (p->fcluster < hit->fcluster)
+ continue;
+
hit = p;
- if (hit->fcluster + hit->nr_contig < fclus) {
- offset = hit->nr_contig;
- } else {
- offset = fclus - hit->fcluster;
+ 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) {
- exfat_cache_update_lru(inode, hit);
+ 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 offset;
+ /* Return next cache start or 'end' if no more caches */
+ return end;
}
static struct exfat_cache *exfat_cache_merge(struct inode *inode,
@@ -234,15 +259,15 @@ static inline void cache_init(struct exfat_cache_id *cid,
}
int exfat_get_cluster(struct inode *inode, unsigned int cluster,
- unsigned int *fclus, unsigned int *dclus,
- unsigned int *last_dclus, int allow_eof)
+ unsigned int *dclus, unsigned int *count,
+ unsigned int *last_dclus)
{
struct super_block *sb = inode->i_sb;
- struct exfat_sb_info *sbi = EXFAT_SB(sb);
- unsigned int limit = sbi->num_clusters;
struct exfat_inode_info *ei = EXFAT_I(inode);
+ struct buffer_head *bh = NULL;
struct exfat_cache_id cid;
- unsigned int content;
+ unsigned int content, fclus;
+ unsigned int end = cluster + *count - 1;
if (ei->start_clu == EXFAT_FREE_CLUSTER) {
exfat_fs_error(sb,
@@ -251,64 +276,82 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster,
return -EIO;
}
- *fclus = 0;
+ fclus = 0;
*dclus = ei->start_clu;
*last_dclus = *dclus;
/*
- * Don`t use exfat_cache if zero offset or non-cluster allocation
+ * 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 (cluster == 0 || *dclus == EXFAT_EOF_CLUSTER)
+ if (*dclus == EXFAT_EOF_CLUSTER) {
+ *count = 0;
return 0;
-
- cache_init(&cid, EXFAT_EOF_CLUSTER, EXFAT_EOF_CLUSTER);
-
- if (exfat_cache_lookup(inode, cluster, &cid, fclus, dclus) ==
- EXFAT_EOF_CLUSTER) {
- /*
- * dummy, always not contiguous
- * This is reinitialized by cache_init(), later.
- */
- WARN_ON(cid.id != EXFAT_CACHE_VALID ||
- cid.fcluster != EXFAT_EOF_CLUSTER ||
- cid.dcluster != EXFAT_EOF_CLUSTER ||
- cid.nr_contig != 0);
}
- if (*fclus == cluster)
+ /* If only the first cluster is needed, return now. */
+ if (fclus == cluster && *count == 1)
return 0;
- while (*fclus < cluster) {
- /* prevent the infinite loop of cluster chain */
- if (*fclus > limit) {
- exfat_fs_error(sb,
- "detected the cluster chain loop (i_pos %u)",
- (*fclus));
- return -EIO;
- }
+ 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);
- if (exfat_ent_get(sb, *dclus, &content))
+ /* 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) {
- if (!allow_eof) {
- exfat_fs_error(sb,
- "invalid cluster chain (i_pos %u, last_clus 0x%08x is EOF)",
- *fclus, (*last_dclus));
- return -EIO;
- }
+ fclus++;
+ if (content == EXFAT_EOF_CLUSTER)
break;
- }
if (!cache_contiguous(&cid, *dclus))
- cache_init(&cid, *fclus, *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;
}
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 176fef62574c..2dbed5f8ec26 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -432,13 +432,13 @@ int exfat_set_volume_dirty(struct super_block *sb);
int exfat_clear_volume_dirty(struct super_block *sb);
/* fatent.c */
-#define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu)
+#define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu, NULL)
int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
struct exfat_chain *p_chain, bool sync_bmap);
int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain);
int exfat_ent_get(struct super_block *sb, unsigned int loc,
- unsigned int *content);
+ 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,
@@ -486,8 +486,7 @@ 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 *fclus, unsigned int *dclus,
- unsigned int *last_dclus, int allow_eof);
+ unsigned int *dclus, unsigned int *count, unsigned int *last_dclus);
/* dir.c */
extern const struct inode_operations exfat_dir_inode_operations;
diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
index c9c5f2e3a05e..f87576ca7032 100644
--- a/fs/exfat/fatent.c
+++ b/fs/exfat/fatent.c
@@ -36,18 +36,23 @@ static int exfat_mirror_bh(struct super_block *sb, sector_t sec,
}
static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
- unsigned int *content)
+ unsigned int *content, struct buffer_head **last)
{
unsigned int off;
sector_t sec;
- struct buffer_head *bh;
+ struct buffer_head *bh = last ? *last : NULL;
sec = FAT_ENT_OFFSET_SECTOR(sb, loc);
off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc);
- bh = sb_bread(sb, sec);
- if (!bh)
- return -EIO;
+ if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) {
+ brelse(bh);
+ bh = sb_bread(sb, sec);
+ if (last)
+ *last = bh;
+ if (unlikely(!bh))
+ return -EIO;
+ }
*content = le32_to_cpu(*(__le32 *)(&bh->b_data[off]));
@@ -55,7 +60,8 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
if (*content > EXFAT_BAD_CLUSTER)
*content = EXFAT_EOF_CLUSTER;
- brelse(bh);
+ if (!last)
+ brelse(bh);
return 0;
}
@@ -82,49 +88,58 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc,
return 0;
}
+/*
+ * Caller must release the buffer_head if no error return.
+ */
int exfat_ent_get(struct super_block *sb, unsigned int loc,
- unsigned int *content)
+ unsigned int *content, struct buffer_head **last)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
- int err;
if (!is_valid_cluster(sbi, loc)) {
exfat_fs_error_ratelimit(sb,
"invalid access to FAT (entry 0x%08x)",
loc);
- return -EIO;
+ goto err;
}
- err = __exfat_ent_get(sb, loc, content);
- if (err) {
+ if (unlikely(__exfat_ent_get(sb, loc, content, last))) {
exfat_fs_error_ratelimit(sb,
- "failed to access to FAT (entry 0x%08x, err:%d)",
- loc, err);
- return err;
+ "failed to access to FAT (entry 0x%08x)",
+ loc);
+ goto err;
}
- if (*content == EXFAT_FREE_CLUSTER) {
+ if (unlikely(*content == EXFAT_FREE_CLUSTER)) {
exfat_fs_error_ratelimit(sb,
"invalid access to FAT free cluster (entry 0x%08x)",
loc);
- return -EIO;
+ goto err;
}
- if (*content == EXFAT_BAD_CLUSTER) {
+ if (unlikely(*content == EXFAT_BAD_CLUSTER)) {
exfat_fs_error_ratelimit(sb,
"invalid access to FAT bad cluster (entry 0x%08x)",
loc);
- return -EIO;
+ 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);
- return -EIO;
+ goto err;
}
return 0;
+err:
+ if (last) {
+ brelse(*last);
+
+ /* Avoid double release */
+ *last = NULL;
+ }
+ return -EIO;
}
int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
@@ -192,6 +207,7 @@ static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain
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;
@@ -281,6 +297,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain)
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;
@@ -293,10 +310,11 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain,
do {
count++;
clu = next;
- if (exfat_ent_get(sb, 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))",
@@ -469,6 +487,7 @@ int exfat_count_num_clusters(struct super_block *sb,
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;
@@ -484,12 +503,13 @@ int exfat_count_num_clusters(struct super_block *sb,
count = 0;
for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters; i++) {
count++;
- if (exfat_ent_get(sb, clu, &clu))
+ if (exfat_ent_get(sb, clu, &clu, &bh))
return -EIO;
if (clu == EXFAT_EOF_CLUSTER)
break;
}
+ brelse(bh);
*ret_count = count;
/*
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index b60ee0e1bec9..90cd540afeaa 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -683,6 +683,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
if (iocb->ki_pos > pos) {
ssize_t err = generic_write_sync(iocb, iocb->ki_pos - pos);
+
if (err < 0)
return err;
}
@@ -708,21 +709,18 @@ static ssize_t exfat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{
int err;
- struct vm_area_struct *vma = vmf->vma;
- struct file *file = vma->vm_file;
- struct inode *inode = file_inode(file);
+ struct inode *inode = file_inode(vmf->vma->vm_file);
struct exfat_inode_info *ei = EXFAT_I(inode);
- loff_t start, end;
+ loff_t new_valid_size;
if (!inode_trylock(inode))
return VM_FAULT_RETRY;
- start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
- end = min_t(loff_t, i_size_read(inode),
- start + vma->vm_end - vma->vm_start);
+ new_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
+ new_valid_size = min(new_valid_size, i_size_read(inode));
- if (ei->valid_size < end) {
- err = exfat_extend_valid_size(inode, end);
+ if (ei->valid_size < new_valid_size) {
+ err = exfat_extend_valid_size(inode, new_valid_size);
if (err < 0) {
inode_unlock(inode);
return vmf_fs_error(err);
diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c
index f9501c3a3666..2fb2d2d5d503 100644
--- a/fs/exfat/inode.c
+++ b/fs/exfat/inode.c
@@ -124,7 +124,7 @@ void exfat_sync_inode(struct inode *inode)
* *clu = (~0), if it's unable to allocate a new cluster
*/
static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
- unsigned int *clu, int create)
+ unsigned int *clu, unsigned int *count, int create)
{
int ret;
unsigned int last_clu;
@@ -147,39 +147,22 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
*clu = last_clu = ei->start_clu;
- if (ei->flags == ALLOC_NO_FAT_CHAIN) {
- if (clu_offset > 0 && *clu != EXFAT_EOF_CLUSTER) {
- last_clu += clu_offset - 1;
-
- if (clu_offset == num_clusters)
- *clu = EXFAT_EOF_CLUSTER;
- else
- *clu += clu_offset;
+ 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 if (ei->type == TYPE_FILE) {
- unsigned int fclus = 0;
+ } else {
int err = exfat_get_cluster(inode, clu_offset,
- &fclus, clu, &last_clu, 1);
+ clu, count, &last_clu);
if (err)
return -EIO;
-
- clu_offset -= fclus;
- } else {
- /* hint information */
- if (clu_offset > 0 && ei->hint_bmap.off != EXFAT_EOF_CLUSTER &&
- ei->hint_bmap.off > 0 && clu_offset >= ei->hint_bmap.off) {
- clu_offset -= ei->hint_bmap.off;
- /* hint_bmap.clu should be valid */
- WARN_ON(ei->hint_bmap.clu < 2);
- *clu = ei->hint_bmap.clu;
- }
-
- while (clu_offset > 0 && *clu != EXFAT_EOF_CLUSTER) {
- last_clu = *clu;
- if (exfat_get_next_cluster(sb, clu))
- return -EIO;
- clu_offset--;
- }
}
if (*clu == EXFAT_EOF_CLUSTER) {
@@ -251,7 +234,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
num_to_be_allocated--;
}
}
-
+ *count = 1;
}
/* hint information */
@@ -270,7 +253,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
int err = 0;
unsigned long mapped_blocks = 0;
- unsigned int cluster, sec_offset;
+ unsigned int cluster, sec_offset, count;
sector_t last_block;
sector_t phys = 0;
sector_t valid_blks;
@@ -283,8 +266,9 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
goto done;
/* Is this block already allocated? */
+ count = EXFAT_B_TO_CLU_ROUND_UP(bh_result->b_size, sbi);
err = exfat_map_cluster(inode, iblock >> sbi->sect_per_clus_bits,
- &cluster, create);
+ &cluster, &count, create);
if (err) {
if (err != -ENOSPC)
exfat_fs_error_ratelimit(sb,
@@ -300,7 +284,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
sec_offset = iblock & (sbi->sect_per_clus - 1);
phys = exfat_cluster_to_sector(sbi, cluster) + sec_offset;
- mapped_blocks = sbi->sect_per_clus - sec_offset;
+ mapped_blocks = ((unsigned long)count << sbi->sect_per_clus_bits) - sec_offset;
max_blocks = min(mapped_blocks, max_blocks);
map_bh(bh_result, sb, phys);
@@ -511,8 +495,9 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
exfat_write_failed(mapping, size);
return ret;
- } else
- size = pos + ret;
+ }
+
+ size = pos + ret;
if (rw == WRITE) {
/*
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index dfe957493d49..670116ae9ec8 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -304,8 +304,8 @@ int exfat_find_empty_entry(struct inode *inode,
struct exfat_chain *p_dir, int num_entries,
struct exfat_entry_set_cache *es)
{
- int dentry;
- unsigned int ret, last_clu;
+ int dentry, ret;
+ unsigned int last_clu;
loff_t size = 0;
struct exfat_chain clu;
struct super_block *sb = inode->i_sb;