diff options
| -rw-r--r-- | fs/exfat/cache.c | 149 | ||||
| -rw-r--r-- | fs/exfat/exfat_fs.h | 7 | ||||
| -rw-r--r-- | fs/exfat/fatent.c | 62 | ||||
| -rw-r--r-- | fs/exfat/file.c | 16 | ||||
| -rw-r--r-- | fs/exfat/inode.c | 57 | ||||
| -rw-r--r-- | fs/exfat/namei.c | 4 |
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; |
