| // SPDX-License-Identifier: GPL-2.0 |
| #ifndef NO_BCACHEFS_FS |
| |
| #include "bcachefs.h" |
| #include "btree_iter.h" |
| #include "extents.h" |
| #include "fs-io.h" |
| #include "fs-io-pagecache.h" |
| #include "subvolume.h" |
| |
| #include <linux/pagevec.h> |
| #include <linux/writeback.h> |
| |
| int bch2_filemap_get_contig_folios_d(struct address_space *mapping, |
| loff_t start, u64 end, |
| fgf_t fgp_flags, gfp_t gfp, |
| folios *fs) |
| { |
| struct folio *f; |
| u64 pos = start; |
| int ret = 0; |
| |
| while (pos < end) { |
| if ((u64) pos >= (u64) start + (1ULL << 20)) |
| fgp_flags &= ~FGP_CREAT; |
| |
| ret = darray_make_room_gfp(fs, 1, gfp & GFP_KERNEL); |
| if (ret) |
| break; |
| |
| f = __filemap_get_folio(mapping, pos >> PAGE_SHIFT, fgp_flags, gfp); |
| if (IS_ERR_OR_NULL(f)) |
| break; |
| |
| BUG_ON(fs->nr && folio_pos(f) != pos); |
| |
| pos = folio_end_pos(f); |
| darray_push(fs, f); |
| } |
| |
| if (!fs->nr && !ret && (fgp_flags & FGP_CREAT)) |
| ret = -ENOMEM; |
| |
| return fs->nr ? 0 : ret; |
| } |
| |
| /* pagecache_block must be held */ |
| int bch2_write_invalidate_inode_pages_range(struct address_space *mapping, |
| loff_t start, loff_t end) |
| { |
| int ret; |
| |
| /* |
| * XXX: the way this is currently implemented, we can spin if a process |
| * is continually redirtying a specific page |
| */ |
| do { |
| if (!mapping->nrpages) |
| return 0; |
| |
| ret = filemap_write_and_wait_range(mapping, start, end); |
| if (ret) |
| break; |
| |
| if (!mapping->nrpages) |
| return 0; |
| |
| ret = invalidate_inode_pages2_range(mapping, |
| start >> PAGE_SHIFT, |
| end >> PAGE_SHIFT); |
| } while (ret == -EBUSY); |
| |
| return ret; |
| } |
| |
| #if 0 |
| /* Useful for debug tracing: */ |
| static const char * const bch2_folio_sector_states[] = { |
| #define x(n) #n, |
| BCH_FOLIO_SECTOR_STATE() |
| #undef x |
| NULL |
| }; |
| #endif |
| |
| static inline enum bch_folio_sector_state |
| folio_sector_dirty(enum bch_folio_sector_state state) |
| { |
| switch (state) { |
| case SECTOR_unallocated: |
| return SECTOR_dirty; |
| case SECTOR_reserved: |
| return SECTOR_dirty_reserved; |
| default: |
| return state; |
| } |
| } |
| |
| static inline enum bch_folio_sector_state |
| folio_sector_undirty(enum bch_folio_sector_state state) |
| { |
| switch (state) { |
| case SECTOR_dirty: |
| return SECTOR_unallocated; |
| case SECTOR_dirty_reserved: |
| return SECTOR_reserved; |
| default: |
| return state; |
| } |
| } |
| |
| static inline enum bch_folio_sector_state |
| folio_sector_reserve(enum bch_folio_sector_state state) |
| { |
| switch (state) { |
| case SECTOR_unallocated: |
| return SECTOR_reserved; |
| case SECTOR_dirty: |
| return SECTOR_dirty_reserved; |
| default: |
| return state; |
| } |
| } |
| |
| /* for newly allocated folios: */ |
| struct bch_folio *__bch2_folio_create(struct folio *folio, gfp_t gfp) |
| { |
| struct bch_folio *s; |
| |
| s = kzalloc(sizeof(*s) + |
| sizeof(struct bch_folio_sector) * |
| folio_sectors(folio), gfp); |
| if (!s) |
| return NULL; |
| |
| spin_lock_init(&s->lock); |
| folio_attach_private(folio, s); |
| return s; |
| } |
| |
| struct bch_folio *bch2_folio_create(struct folio *folio, gfp_t gfp) |
| { |
| return bch2_folio(folio) ?: __bch2_folio_create(folio, gfp); |
| } |
| |
| static unsigned bkey_to_sector_state(struct bkey_s_c k) |
| { |
| if (bkey_extent_is_reservation(k)) |
| return SECTOR_reserved; |
| if (bkey_extent_is_allocation(k.k)) |
| return SECTOR_allocated; |
| return SECTOR_unallocated; |
| } |
| |
| static void __bch2_folio_set(struct folio *folio, |
| unsigned pg_offset, unsigned pg_len, |
| unsigned nr_ptrs, unsigned state) |
| { |
| struct bch_folio *s = bch2_folio(folio); |
| unsigned i, sectors = folio_sectors(folio); |
| |
| BUG_ON(pg_offset >= sectors); |
| BUG_ON(pg_offset + pg_len > sectors); |
| |
| spin_lock(&s->lock); |
| |
| for (i = pg_offset; i < pg_offset + pg_len; i++) { |
| s->s[i].nr_replicas = nr_ptrs; |
| bch2_folio_sector_set(folio, s, i, state); |
| } |
| |
| if (i == sectors) |
| s->uptodate = true; |
| |
| spin_unlock(&s->lock); |
| } |
| |
| /* |
| * Initialize bch_folio state (allocated/unallocated, nr_replicas) from the |
| * extents btree: |
| */ |
| int bch2_folio_set(struct bch_fs *c, subvol_inum inum, |
| struct folio **fs, unsigned nr_folios) |
| { |
| struct btree_trans *trans; |
| struct btree_iter iter; |
| struct bkey_s_c k; |
| struct bch_folio *s; |
| u64 offset = folio_sector(fs[0]); |
| unsigned folio_idx; |
| u32 snapshot; |
| bool need_set = false; |
| int ret; |
| |
| for (folio_idx = 0; folio_idx < nr_folios; folio_idx++) { |
| s = bch2_folio_create(fs[folio_idx], GFP_KERNEL); |
| if (!s) |
| return -ENOMEM; |
| |
| need_set |= !s->uptodate; |
| } |
| |
| if (!need_set) |
| return 0; |
| |
| folio_idx = 0; |
| trans = bch2_trans_get(c); |
| retry: |
| bch2_trans_begin(trans); |
| |
| ret = bch2_subvolume_get_snapshot(trans, inum.subvol, &snapshot); |
| if (ret) |
| goto err; |
| |
| for_each_btree_key_norestart(trans, iter, BTREE_ID_extents, |
| SPOS(inum.inum, offset, snapshot), |
| BTREE_ITER_SLOTS, k, ret) { |
| unsigned nr_ptrs = bch2_bkey_nr_ptrs_fully_allocated(k); |
| unsigned state = bkey_to_sector_state(k); |
| |
| while (folio_idx < nr_folios) { |
| struct folio *folio = fs[folio_idx]; |
| u64 folio_start = folio_sector(folio); |
| u64 folio_end = folio_end_sector(folio); |
| unsigned folio_offset = max(bkey_start_offset(k.k), folio_start) - |
| folio_start; |
| unsigned folio_len = min(k.k->p.offset, folio_end) - |
| folio_offset - folio_start; |
| |
| BUG_ON(k.k->p.offset < folio_start); |
| BUG_ON(bkey_start_offset(k.k) > folio_end); |
| |
| if (!bch2_folio(folio)->uptodate) |
| __bch2_folio_set(folio, folio_offset, folio_len, nr_ptrs, state); |
| |
| if (k.k->p.offset < folio_end) |
| break; |
| folio_idx++; |
| } |
| |
| if (folio_idx == nr_folios) |
| break; |
| } |
| |
| offset = iter.pos.offset; |
| bch2_trans_iter_exit(trans, &iter); |
| err: |
| if (bch2_err_matches(ret, BCH_ERR_transaction_restart)) |
| goto retry; |
| bch2_trans_put(trans); |
| |
| return ret; |
| } |
| |
| void bch2_bio_page_state_set(struct bio *bio, struct bkey_s_c k) |
| { |
| struct bvec_iter iter; |
| struct folio_vec fv; |
| unsigned nr_ptrs = k.k->type == KEY_TYPE_reflink_v |
| ? 0 : bch2_bkey_nr_ptrs_fully_allocated(k); |
| unsigned state = bkey_to_sector_state(k); |
| |
| bio_for_each_folio(fv, bio, iter) |
| __bch2_folio_set(fv.fv_folio, |
| fv.fv_offset >> 9, |
| fv.fv_len >> 9, |
| nr_ptrs, state); |
| } |
| |
| void bch2_mark_pagecache_unallocated(struct bch_inode_info *inode, |
| u64 start, u64 end) |
| { |
| pgoff_t index = start >> PAGE_SECTORS_SHIFT; |
| pgoff_t end_index = (end - 1) >> PAGE_SECTORS_SHIFT; |
| struct folio_batch fbatch; |
| unsigned i, j; |
| |
| if (end <= start) |
| return; |
| |
| folio_batch_init(&fbatch); |
| |
| while (filemap_get_folios(inode->v.i_mapping, |
| &index, end_index, &fbatch)) { |
| for (i = 0; i < folio_batch_count(&fbatch); i++) { |
| struct folio *folio = fbatch.folios[i]; |
| u64 folio_start = folio_sector(folio); |
| u64 folio_end = folio_end_sector(folio); |
| unsigned folio_offset = max(start, folio_start) - folio_start; |
| unsigned folio_len = min(end, folio_end) - folio_offset - folio_start; |
| struct bch_folio *s; |
| |
| BUG_ON(end <= folio_start); |
| |
| folio_lock(folio); |
| s = bch2_folio(folio); |
| |
| if (s) { |
| spin_lock(&s->lock); |
| for (j = folio_offset; j < folio_offset + folio_len; j++) |
| s->s[j].nr_replicas = 0; |
| spin_unlock(&s->lock); |
| } |
| |
| folio_unlock(folio); |
| } |
| folio_batch_release(&fbatch); |
| cond_resched(); |
| } |
| } |
| |
| void bch2_mark_pagecache_reserved(struct bch_inode_info *inode, |
| u64 start, u64 end) |
| { |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; |
| pgoff_t index = start >> PAGE_SECTORS_SHIFT; |
| pgoff_t end_index = (end - 1) >> PAGE_SECTORS_SHIFT; |
| struct folio_batch fbatch; |
| s64 i_sectors_delta = 0; |
| unsigned i, j; |
| |
| if (end <= start) |
| return; |
| |
| folio_batch_init(&fbatch); |
| |
| while (filemap_get_folios(inode->v.i_mapping, |
| &index, end_index, &fbatch)) { |
| for (i = 0; i < folio_batch_count(&fbatch); i++) { |
| struct folio *folio = fbatch.folios[i]; |
| u64 folio_start = folio_sector(folio); |
| u64 folio_end = folio_end_sector(folio); |
| unsigned folio_offset = max(start, folio_start) - folio_start; |
| unsigned folio_len = min(end, folio_end) - folio_offset - folio_start; |
| struct bch_folio *s; |
| |
| BUG_ON(end <= folio_start); |
| |
| folio_lock(folio); |
| s = bch2_folio(folio); |
| |
| if (s) { |
| spin_lock(&s->lock); |
| for (j = folio_offset; j < folio_offset + folio_len; j++) { |
| i_sectors_delta -= s->s[j].state == SECTOR_dirty; |
| bch2_folio_sector_set(folio, s, j, |
| folio_sector_reserve(s->s[j].state)); |
| } |
| spin_unlock(&s->lock); |
| } |
| |
| folio_unlock(folio); |
| } |
| folio_batch_release(&fbatch); |
| cond_resched(); |
| } |
| |
| bch2_i_sectors_acct(c, inode, NULL, i_sectors_delta); |
| } |
| |
| static inline unsigned sectors_to_reserve(struct bch_folio_sector *s, |
| unsigned nr_replicas) |
| { |
| return max(0, (int) nr_replicas - |
| s->nr_replicas - |
| s->replicas_reserved); |
| } |
| |
| int bch2_get_folio_disk_reservation(struct bch_fs *c, |
| struct bch_inode_info *inode, |
| struct folio *folio, bool check_enospc) |
| { |
| struct bch_folio *s = bch2_folio_create(folio, 0); |
| unsigned nr_replicas = inode_nr_replicas(c, inode); |
| struct disk_reservation disk_res = { 0 }; |
| unsigned i, sectors = folio_sectors(folio), disk_res_sectors = 0; |
| int ret; |
| |
| if (!s) |
| return -ENOMEM; |
| |
| for (i = 0; i < sectors; i++) |
| disk_res_sectors += sectors_to_reserve(&s->s[i], nr_replicas); |
| |
| if (!disk_res_sectors) |
| return 0; |
| |
| ret = bch2_disk_reservation_get(c, &disk_res, |
| disk_res_sectors, 1, |
| !check_enospc |
| ? BCH_DISK_RESERVATION_NOFAIL |
| : 0); |
| if (unlikely(ret)) |
| return ret; |
| |
| for (i = 0; i < sectors; i++) |
| s->s[i].replicas_reserved += |
| sectors_to_reserve(&s->s[i], nr_replicas); |
| |
| return 0; |
| } |
| |
| void bch2_folio_reservation_put(struct bch_fs *c, |
| struct bch_inode_info *inode, |
| struct bch2_folio_reservation *res) |
| { |
| bch2_disk_reservation_put(c, &res->disk); |
| bch2_quota_reservation_put(c, inode, &res->quota); |
| } |
| |
| int bch2_folio_reservation_get(struct bch_fs *c, |
| struct bch_inode_info *inode, |
| struct folio *folio, |
| struct bch2_folio_reservation *res, |
| unsigned offset, unsigned len) |
| { |
| struct bch_folio *s = bch2_folio_create(folio, 0); |
| unsigned i, disk_sectors = 0, quota_sectors = 0; |
| int ret; |
| |
| if (!s) |
| return -ENOMEM; |
| |
| BUG_ON(!s->uptodate); |
| |
| for (i = round_down(offset, block_bytes(c)) >> 9; |
| i < round_up(offset + len, block_bytes(c)) >> 9; |
| i++) { |
| disk_sectors += sectors_to_reserve(&s->s[i], |
| res->disk.nr_replicas); |
| quota_sectors += s->s[i].state == SECTOR_unallocated; |
| } |
| |
| if (disk_sectors) { |
| ret = bch2_disk_reservation_add(c, &res->disk, disk_sectors, 0); |
| if (unlikely(ret)) |
| return ret; |
| } |
| |
| if (quota_sectors) { |
| ret = bch2_quota_reservation_add(c, inode, &res->quota, |
| quota_sectors, true); |
| if (unlikely(ret)) { |
| struct disk_reservation tmp = { |
| .sectors = disk_sectors |
| }; |
| |
| bch2_disk_reservation_put(c, &tmp); |
| res->disk.sectors -= disk_sectors; |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void bch2_clear_folio_bits(struct folio *folio) |
| { |
| struct bch_inode_info *inode = to_bch_ei(folio->mapping->host); |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; |
| struct bch_folio *s = bch2_folio(folio); |
| struct disk_reservation disk_res = { 0 }; |
| int i, sectors = folio_sectors(folio), dirty_sectors = 0; |
| |
| if (!s) |
| return; |
| |
| EBUG_ON(!folio_test_locked(folio)); |
| EBUG_ON(folio_test_writeback(folio)); |
| |
| for (i = 0; i < sectors; i++) { |
| disk_res.sectors += s->s[i].replicas_reserved; |
| s->s[i].replicas_reserved = 0; |
| |
| dirty_sectors -= s->s[i].state == SECTOR_dirty; |
| bch2_folio_sector_set(folio, s, i, folio_sector_undirty(s->s[i].state)); |
| } |
| |
| bch2_disk_reservation_put(c, &disk_res); |
| |
| bch2_i_sectors_acct(c, inode, NULL, dirty_sectors); |
| |
| bch2_folio_release(folio); |
| } |
| |
| void bch2_set_folio_dirty(struct bch_fs *c, |
| struct bch_inode_info *inode, |
| struct folio *folio, |
| struct bch2_folio_reservation *res, |
| unsigned offset, unsigned len) |
| { |
| struct bch_folio *s = bch2_folio(folio); |
| unsigned i, dirty_sectors = 0; |
| |
| WARN_ON((u64) folio_pos(folio) + offset + len > |
| round_up((u64) i_size_read(&inode->v), block_bytes(c))); |
| |
| BUG_ON(!s->uptodate); |
| |
| spin_lock(&s->lock); |
| |
| for (i = round_down(offset, block_bytes(c)) >> 9; |
| i < round_up(offset + len, block_bytes(c)) >> 9; |
| i++) { |
| unsigned sectors = sectors_to_reserve(&s->s[i], |
| res->disk.nr_replicas); |
| |
| /* |
| * This can happen if we race with the error path in |
| * bch2_writepage_io_done(): |
| */ |
| sectors = min_t(unsigned, sectors, res->disk.sectors); |
| |
| s->s[i].replicas_reserved += sectors; |
| res->disk.sectors -= sectors; |
| |
| dirty_sectors += s->s[i].state == SECTOR_unallocated; |
| |
| bch2_folio_sector_set(folio, s, i, folio_sector_dirty(s->s[i].state)); |
| } |
| |
| spin_unlock(&s->lock); |
| |
| bch2_i_sectors_acct(c, inode, &res->quota, dirty_sectors); |
| |
| if (!folio_test_dirty(folio)) |
| filemap_dirty_folio(inode->v.i_mapping, folio); |
| } |
| |
| vm_fault_t bch2_page_fault(struct vm_fault *vmf) |
| { |
| struct file *file = vmf->vma->vm_file; |
| struct address_space *mapping = file->f_mapping; |
| struct address_space *fdm = faults_disabled_mapping(); |
| struct bch_inode_info *inode = file_bch_inode(file); |
| vm_fault_t ret; |
| |
| if (fdm == mapping) |
| return VM_FAULT_SIGBUS; |
| |
| /* Lock ordering: */ |
| if (fdm > mapping) { |
| struct bch_inode_info *fdm_host = to_bch_ei(fdm->host); |
| |
| if (bch2_pagecache_add_tryget(inode)) |
| goto got_lock; |
| |
| bch2_pagecache_block_put(fdm_host); |
| |
| bch2_pagecache_add_get(inode); |
| bch2_pagecache_add_put(inode); |
| |
| bch2_pagecache_block_get(fdm_host); |
| |
| /* Signal that lock has been dropped: */ |
| set_fdm_dropped_locks(); |
| return VM_FAULT_SIGBUS; |
| } |
| |
| bch2_pagecache_add_get(inode); |
| got_lock: |
| ret = filemap_fault(vmf); |
| bch2_pagecache_add_put(inode); |
| |
| return ret; |
| } |
| |
| vm_fault_t bch2_page_mkwrite(struct vm_fault *vmf) |
| { |
| struct folio *folio = page_folio(vmf->page); |
| struct file *file = vmf->vma->vm_file; |
| struct bch_inode_info *inode = file_bch_inode(file); |
| struct address_space *mapping = file->f_mapping; |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; |
| struct bch2_folio_reservation res; |
| unsigned len; |
| loff_t isize; |
| vm_fault_t ret; |
| |
| bch2_folio_reservation_init(c, inode, &res); |
| |
| sb_start_pagefault(inode->v.i_sb); |
| file_update_time(file); |
| |
| /* |
| * Not strictly necessary, but helps avoid dio writes livelocking in |
| * bch2_write_invalidate_inode_pages_range() - can drop this if/when we get |
| * a bch2_write_invalidate_inode_pages_range() that works without dropping |
| * page lock before invalidating page |
| */ |
| bch2_pagecache_add_get(inode); |
| |
| folio_lock(folio); |
| isize = i_size_read(&inode->v); |
| |
| if (folio->mapping != mapping || folio_pos(folio) >= isize) { |
| folio_unlock(folio); |
| ret = VM_FAULT_NOPAGE; |
| goto out; |
| } |
| |
| len = min_t(loff_t, folio_size(folio), isize - folio_pos(folio)); |
| |
| if (bch2_folio_set(c, inode_inum(inode), &folio, 1) ?: |
| bch2_folio_reservation_get(c, inode, folio, &res, 0, len)) { |
| folio_unlock(folio); |
| ret = VM_FAULT_SIGBUS; |
| goto out; |
| } |
| |
| bch2_set_folio_dirty(c, inode, folio, &res, 0, len); |
| bch2_folio_reservation_put(c, inode, &res); |
| |
| folio_wait_stable(folio); |
| ret = VM_FAULT_LOCKED; |
| out: |
| bch2_pagecache_add_put(inode); |
| sb_end_pagefault(inode->v.i_sb); |
| |
| return ret; |
| } |
| |
| void bch2_invalidate_folio(struct folio *folio, size_t offset, size_t length) |
| { |
| if (offset || length < folio_size(folio)) |
| return; |
| |
| bch2_clear_folio_bits(folio); |
| } |
| |
| bool bch2_release_folio(struct folio *folio, gfp_t gfp_mask) |
| { |
| if (folio_test_dirty(folio) || folio_test_writeback(folio)) |
| return false; |
| |
| bch2_clear_folio_bits(folio); |
| return true; |
| } |
| |
| /* fseek: */ |
| |
| static int folio_data_offset(struct folio *folio, loff_t pos, |
| unsigned min_replicas) |
| { |
| struct bch_folio *s = bch2_folio(folio); |
| unsigned i, sectors = folio_sectors(folio); |
| |
| if (s) |
| for (i = folio_pos_to_s(folio, pos); i < sectors; i++) |
| if (s->s[i].state >= SECTOR_dirty && |
| s->s[i].nr_replicas + s->s[i].replicas_reserved >= min_replicas) |
| return i << SECTOR_SHIFT; |
| |
| return -1; |
| } |
| |
| loff_t bch2_seek_pagecache_data(struct inode *vinode, |
| loff_t start_offset, |
| loff_t end_offset, |
| unsigned min_replicas, |
| bool nonblock) |
| { |
| struct folio_batch fbatch; |
| pgoff_t start_index = start_offset >> PAGE_SHIFT; |
| pgoff_t end_index = end_offset >> PAGE_SHIFT; |
| pgoff_t index = start_index; |
| unsigned i; |
| loff_t ret; |
| int offset; |
| |
| folio_batch_init(&fbatch); |
| |
| while (filemap_get_folios(vinode->i_mapping, |
| &index, end_index, &fbatch)) { |
| for (i = 0; i < folio_batch_count(&fbatch); i++) { |
| struct folio *folio = fbatch.folios[i]; |
| |
| if (!nonblock) { |
| folio_lock(folio); |
| } else if (!folio_trylock(folio)) { |
| folio_batch_release(&fbatch); |
| return -EAGAIN; |
| } |
| |
| offset = folio_data_offset(folio, |
| max(folio_pos(folio), start_offset), |
| min_replicas); |
| if (offset >= 0) { |
| ret = clamp(folio_pos(folio) + offset, |
| start_offset, end_offset); |
| folio_unlock(folio); |
| folio_batch_release(&fbatch); |
| return ret; |
| } |
| folio_unlock(folio); |
| } |
| folio_batch_release(&fbatch); |
| cond_resched(); |
| } |
| |
| return end_offset; |
| } |
| |
| /* |
| * Search for a hole in a folio. |
| * |
| * The filemap layer returns -ENOENT if no folio exists, so reuse the same error |
| * code to indicate a pagecache hole exists at the returned offset. Otherwise |
| * return 0 if the folio is filled with data, or an error code. This function |
| * can return -EAGAIN if nonblock is specified. |
| */ |
| static int folio_hole_offset(struct address_space *mapping, loff_t *offset, |
| unsigned min_replicas, bool nonblock) |
| { |
| struct folio *folio; |
| struct bch_folio *s; |
| unsigned i, sectors; |
| int ret = -ENOENT; |
| |
| folio = __filemap_get_folio(mapping, *offset >> PAGE_SHIFT, |
| FGP_LOCK|(nonblock ? FGP_NOWAIT : 0), 0); |
| if (IS_ERR(folio)) |
| return PTR_ERR(folio); |
| |
| s = bch2_folio(folio); |
| if (!s) |
| goto unlock; |
| |
| sectors = folio_sectors(folio); |
| for (i = folio_pos_to_s(folio, *offset); i < sectors; i++) |
| if (s->s[i].state < SECTOR_dirty || |
| s->s[i].nr_replicas + s->s[i].replicas_reserved < min_replicas) { |
| *offset = max(*offset, |
| folio_pos(folio) + (i << SECTOR_SHIFT)); |
| goto unlock; |
| } |
| |
| *offset = folio_end_pos(folio); |
| ret = 0; |
| unlock: |
| folio_unlock(folio); |
| folio_put(folio); |
| return ret; |
| } |
| |
| loff_t bch2_seek_pagecache_hole(struct inode *vinode, |
| loff_t start_offset, |
| loff_t end_offset, |
| unsigned min_replicas, |
| bool nonblock) |
| { |
| struct address_space *mapping = vinode->i_mapping; |
| loff_t offset = start_offset; |
| loff_t ret = 0; |
| |
| while (!ret && offset < end_offset) |
| ret = folio_hole_offset(mapping, &offset, min_replicas, nonblock); |
| |
| if (ret && ret != -ENOENT) |
| return ret; |
| return min(offset, end_offset); |
| } |
| |
| int bch2_clamp_data_hole(struct inode *inode, |
| u64 *hole_start, |
| u64 *hole_end, |
| unsigned min_replicas, |
| bool nonblock) |
| { |
| loff_t ret; |
| |
| ret = bch2_seek_pagecache_hole(inode, |
| *hole_start << 9, *hole_end << 9, min_replicas, nonblock) >> 9; |
| if (ret < 0) |
| return ret; |
| |
| *hole_start = ret; |
| |
| if (*hole_start == *hole_end) |
| return 0; |
| |
| ret = bch2_seek_pagecache_data(inode, |
| *hole_start << 9, *hole_end << 9, min_replicas, nonblock) >> 9; |
| if (ret < 0) |
| return ret; |
| |
| *hole_end = ret; |
| return 0; |
| } |
| |
| #endif /* NO_BCACHEFS_FS */ |