| // SPDX-License-Identifier: GPL-2.0 | |
| #ifndef NO_BCACHEFS_FS | |
| #include "bcachefs.h" | |
| #include "alloc_foreground.h" | |
| #include "bkey_buf.h" | |
| #include "btree_update.h" | |
| #include "buckets.h" | |
| #include "clock.h" | |
| #include "error.h" | |
| #include "extents.h" | |
| #include "extent_update.h" | |
| #include "fs.h" | |
| #include "fs-io.h" | |
| #include "fs-io-buffered.h" | |
| #include "fs-io-pagecache.h" | |
| #include "fsck.h" | |
| #include "inode.h" | |
| #include "journal.h" | |
| #include "io_misc.h" | |
| #include "keylist.h" | |
| #include "quota.h" | |
| #include "reflink.h" | |
| #include "trace.h" | |
| #include <linux/aio.h> | |
| #include <linux/backing-dev.h> | |
| #include <linux/falloc.h> | |
| #include <linux/migrate.h> | |
| #include <linux/mmu_context.h> | |
| #include <linux/pagevec.h> | |
| #include <linux/rmap.h> | |
| #include <linux/sched/signal.h> | |
| #include <linux/task_io_accounting_ops.h> | |
| #include <linux/uio.h> | |
| #include <trace/events/writeback.h> | |
| struct nocow_flush { | |
| struct closure *cl; | |
| struct bch_dev *ca; | |
| struct bio bio; | |
| }; | |
| static void nocow_flush_endio(struct bio *_bio) | |
| { | |
| struct nocow_flush *bio = container_of(_bio, struct nocow_flush, bio); | |
| closure_put(bio->cl); | |
| percpu_ref_put(&bio->ca->io_ref); | |
| bio_put(&bio->bio); | |
| } | |
| void bch2_inode_flush_nocow_writes_async(struct bch_fs *c, | |
| struct bch_inode_info *inode, | |
| struct closure *cl) | |
| { | |
| struct nocow_flush *bio; | |
| struct bch_dev *ca; | |
| struct bch_devs_mask devs; | |
| unsigned dev; | |
| dev = find_first_bit(inode->ei_devs_need_flush.d, BCH_SB_MEMBERS_MAX); | |
| if (dev == BCH_SB_MEMBERS_MAX) | |
| return; | |
| devs = inode->ei_devs_need_flush; | |
| memset(&inode->ei_devs_need_flush, 0, sizeof(inode->ei_devs_need_flush)); | |
| for_each_set_bit(dev, devs.d, BCH_SB_MEMBERS_MAX) { | |
| rcu_read_lock(); | |
| ca = rcu_dereference(c->devs[dev]); | |
| if (ca && !percpu_ref_tryget(&ca->io_ref)) | |
| ca = NULL; | |
| rcu_read_unlock(); | |
| if (!ca) | |
| continue; | |
| bio = container_of(bio_alloc_bioset(ca->disk_sb.bdev, 0, | |
| REQ_OP_WRITE|REQ_PREFLUSH, | |
| GFP_KERNEL, | |
| &c->nocow_flush_bioset), | |
| struct nocow_flush, bio); | |
| bio->cl = cl; | |
| bio->ca = ca; | |
| bio->bio.bi_end_io = nocow_flush_endio; | |
| closure_bio_submit(&bio->bio, cl); | |
| } | |
| } | |
| static int bch2_inode_flush_nocow_writes(struct bch_fs *c, | |
| struct bch_inode_info *inode) | |
| { | |
| struct closure cl; | |
| closure_init_stack(&cl); | |
| bch2_inode_flush_nocow_writes_async(c, inode, &cl); | |
| closure_sync(&cl); | |
| return 0; | |
| } | |
| /* i_size updates: */ | |
| struct inode_new_size { | |
| loff_t new_size; | |
| u64 now; | |
| unsigned fields; | |
| }; | |
| static int inode_set_size(struct btree_trans *trans, | |
| struct bch_inode_info *inode, | |
| struct bch_inode_unpacked *bi, | |
| void *p) | |
| { | |
| struct inode_new_size *s = p; | |
| bi->bi_size = s->new_size; | |
| if (s->fields & ATTR_ATIME) | |
| bi->bi_atime = s->now; | |
| if (s->fields & ATTR_MTIME) | |
| bi->bi_mtime = s->now; | |
| if (s->fields & ATTR_CTIME) | |
| bi->bi_ctime = s->now; | |
| return 0; | |
| } | |
| int __must_check bch2_write_inode_size(struct bch_fs *c, | |
| struct bch_inode_info *inode, | |
| loff_t new_size, unsigned fields) | |
| { | |
| struct inode_new_size s = { | |
| .new_size = new_size, | |
| .now = bch2_current_time(c), | |
| .fields = fields, | |
| }; | |
| return bch2_write_inode(c, inode, inode_set_size, &s, fields); | |
| } | |
| void __bch2_i_sectors_acct(struct bch_fs *c, struct bch_inode_info *inode, | |
| struct quota_res *quota_res, s64 sectors) | |
| { | |
| bch2_fs_inconsistent_on((s64) inode->v.i_blocks + sectors < 0, c, | |
| "inode %lu i_blocks underflow: %llu + %lli < 0 (ondisk %lli)", | |
| inode->v.i_ino, (u64) inode->v.i_blocks, sectors, | |
| inode->ei_inode.bi_sectors); | |
| inode->v.i_blocks += sectors; | |
| #ifdef CONFIG_BCACHEFS_QUOTA | |
| if (quota_res && | |
| !test_bit(EI_INODE_SNAPSHOT, &inode->ei_flags) && | |
| sectors > 0) { | |
| BUG_ON(sectors > quota_res->sectors); | |
| BUG_ON(sectors > inode->ei_quota_reserved); | |
| quota_res->sectors -= sectors; | |
| inode->ei_quota_reserved -= sectors; | |
| } else { | |
| bch2_quota_acct(c, inode->ei_qid, Q_SPC, sectors, KEY_TYPE_QUOTA_WARN); | |
| } | |
| #endif | |
| } | |
| /* fsync: */ | |
| /* | |
| * inode->ei_inode.bi_journal_seq won't be up to date since it's set in an | |
| * insert trigger: look up the btree inode instead | |
| */ | |
| static int bch2_flush_inode(struct bch_fs *c, | |
| struct bch_inode_info *inode) | |
| { | |
| if (c->opts.journal_flush_disabled) | |
| return 0; | |
| if (!bch2_write_ref_tryget(c, BCH_WRITE_REF_fsync)) | |
| return -EROFS; | |
| struct bch_inode_unpacked u; | |
| int ret = bch2_inode_find_by_inum(c, inode_inum(inode), &u) ?: | |
| bch2_journal_flush_seq(&c->journal, u.bi_journal_seq, TASK_INTERRUPTIBLE) ?: | |
| bch2_inode_flush_nocow_writes(c, inode); | |
| bch2_write_ref_put(c, BCH_WRITE_REF_fsync); | |
| return ret; | |
| } | |
| int bch2_fsync(struct file *file, loff_t start, loff_t end, int datasync) | |
| { | |
| struct bch_inode_info *inode = file_bch_inode(file); | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| int ret, err; | |
| trace_bch2_fsync(file, datasync); | |
| ret = file_write_and_wait_range(file, start, end); | |
| if (ret) | |
| goto out; | |
| ret = sync_inode_metadata(&inode->v, 1); | |
| if (ret) | |
| goto out; | |
| ret = bch2_flush_inode(c, inode); | |
| out: | |
| ret = bch2_err_class(ret); | |
| if (ret == -EROFS) | |
| ret = -EIO; | |
| err = file_check_and_advance_wb_err(file); | |
| if (!ret) | |
| ret = err; | |
| return ret; | |
| } | |
| /* truncate: */ | |
| static inline int range_has_data(struct bch_fs *c, u32 subvol, | |
| struct bpos start, | |
| struct bpos end) | |
| { | |
| return bch2_trans_run(c, | |
| for_each_btree_key_in_subvolume_upto(trans, iter, BTREE_ID_extents, start, end, | |
| subvol, 0, k, ({ | |
| bkey_extent_is_data(k.k) && !bkey_extent_is_unwritten(k); | |
| }))); | |
| } | |
| static int __bch2_truncate_folio(struct bch_inode_info *inode, | |
| pgoff_t index, loff_t start, loff_t end) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| struct address_space *mapping = inode->v.i_mapping; | |
| struct bch_folio *s; | |
| unsigned start_offset; | |
| unsigned end_offset; | |
| unsigned i; | |
| struct folio *folio; | |
| s64 i_sectors_delta = 0; | |
| int ret = 0; | |
| u64 end_pos; | |
| folio = filemap_lock_folio(mapping, index); | |
| if (IS_ERR_OR_NULL(folio)) { | |
| /* | |
| * XXX: we're doing two index lookups when we end up reading the | |
| * folio | |
| */ | |
| ret = range_has_data(c, inode->ei_inum.subvol, | |
| POS(inode->v.i_ino, (index << PAGE_SECTORS_SHIFT)), | |
| POS(inode->v.i_ino, (index << PAGE_SECTORS_SHIFT) + PAGE_SECTORS)); | |
| if (ret <= 0) | |
| return ret; | |
| folio = __filemap_get_folio(mapping, index, | |
| FGP_LOCK|FGP_CREAT, GFP_KERNEL); | |
| if (IS_ERR_OR_NULL(folio)) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| } | |
| BUG_ON(start >= folio_end_pos(folio)); | |
| BUG_ON(end <= folio_pos(folio)); | |
| start_offset = max(start, folio_pos(folio)) - folio_pos(folio); | |
| end_offset = min_t(u64, end, folio_end_pos(folio)) - folio_pos(folio); | |
| /* Folio boundary? Nothing to do */ | |
| if (start_offset == 0 && | |
| end_offset == folio_size(folio)) { | |
| ret = 0; | |
| goto unlock; | |
| } | |
| s = bch2_folio_create(folio, 0); | |
| if (!s) { | |
| ret = -ENOMEM; | |
| goto unlock; | |
| } | |
| if (!folio_test_uptodate(folio)) { | |
| ret = bch2_read_single_folio(folio, mapping); | |
| if (ret) | |
| goto unlock; | |
| } | |
| ret = bch2_folio_set(c, inode_inum(inode), &folio, 1); | |
| if (ret) | |
| goto unlock; | |
| for (i = round_up(start_offset, block_bytes(c)) >> 9; | |
| i < round_down(end_offset, block_bytes(c)) >> 9; | |
| i++) { | |
| s->s[i].nr_replicas = 0; | |
| i_sectors_delta -= s->s[i].state == SECTOR_dirty; | |
| bch2_folio_sector_set(folio, s, i, SECTOR_unallocated); | |
| } | |
| bch2_i_sectors_acct(c, inode, NULL, i_sectors_delta); | |
| /* | |
| * Caller needs to know whether this folio will be written out by | |
| * writeback - doing an i_size update if necessary - or whether it will | |
| * be responsible for the i_size update. | |
| * | |
| * Note that we shouldn't ever see a folio beyond EOF, but check and | |
| * warn if so. This has been observed by failure to clean up folios | |
| * after a short write and there's still a chance reclaim will fix | |
| * things up. | |
| */ | |
| WARN_ON_ONCE(folio_pos(folio) >= inode->v.i_size); | |
| end_pos = folio_end_pos(folio); | |
| if (inode->v.i_size > folio_pos(folio)) | |
| end_pos = min_t(u64, inode->v.i_size, end_pos); | |
| ret = s->s[folio_pos_to_s(folio, end_pos - 1)].state >= SECTOR_dirty; | |
| folio_zero_segment(folio, start_offset, end_offset); | |
| /* | |
| * Bit of a hack - we don't want truncate to fail due to -ENOSPC. | |
| * | |
| * XXX: because we aren't currently tracking whether the folio has actual | |
| * data in it (vs. just 0s, or only partially written) this wrong. ick. | |
| */ | |
| BUG_ON(bch2_get_folio_disk_reservation(c, inode, folio, false)); | |
| /* | |
| * This removes any writeable userspace mappings; we need to force | |
| * .page_mkwrite to be called again before any mmapped writes, to | |
| * redirty the full page: | |
| */ | |
| folio_mkclean(folio); | |
| filemap_dirty_folio(mapping, folio); | |
| unlock: | |
| folio_unlock(folio); | |
| folio_put(folio); | |
| out: | |
| return ret; | |
| } | |
| static int bch2_truncate_folio(struct bch_inode_info *inode, loff_t from) | |
| { | |
| return __bch2_truncate_folio(inode, from >> PAGE_SHIFT, | |
| from, ANYSINT_MAX(loff_t)); | |
| } | |
| static int bch2_truncate_folios(struct bch_inode_info *inode, | |
| loff_t start, loff_t end) | |
| { | |
| int ret = __bch2_truncate_folio(inode, start >> PAGE_SHIFT, | |
| start, end); | |
| if (ret >= 0 && | |
| start >> PAGE_SHIFT != end >> PAGE_SHIFT) | |
| ret = __bch2_truncate_folio(inode, | |
| (end - 1) >> PAGE_SHIFT, | |
| start, end); | |
| return ret; | |
| } | |
| static int bch2_extend(struct mnt_idmap *idmap, | |
| struct bch_inode_info *inode, | |
| struct bch_inode_unpacked *inode_u, | |
| struct iattr *iattr) | |
| { | |
| struct address_space *mapping = inode->v.i_mapping; | |
| int ret; | |
| /* | |
| * sync appends: | |
| * | |
| * this has to be done _before_ extending i_size: | |
| */ | |
| ret = filemap_write_and_wait_range(mapping, inode_u->bi_size, S64_MAX); | |
| if (ret) | |
| return ret; | |
| truncate_setsize(&inode->v, iattr->ia_size); | |
| return bch2_setattr_nonsize(idmap, inode, iattr); | |
| } | |
| int bchfs_truncate(struct mnt_idmap *idmap, | |
| struct bch_inode_info *inode, struct iattr *iattr) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| struct address_space *mapping = inode->v.i_mapping; | |
| struct bch_inode_unpacked inode_u; | |
| s64 i_sectors_delta = 0; | |
| int ret = 0; | |
| /* | |
| * If the truncate call with change the size of the file, the | |
| * cmtimes should be updated. If the size will not change, we | |
| * do not need to update the cmtimes. | |
| */ | |
| if (iattr->ia_size != inode->v.i_size) { | |
| if (!(iattr->ia_valid & ATTR_MTIME)) | |
| ktime_get_coarse_real_ts64(&iattr->ia_mtime); | |
| if (!(iattr->ia_valid & ATTR_CTIME)) | |
| ktime_get_coarse_real_ts64(&iattr->ia_ctime); | |
| iattr->ia_valid |= ATTR_MTIME|ATTR_CTIME; | |
| } | |
| inode_dio_wait(&inode->v); | |
| bch2_pagecache_block_get(inode); | |
| ret = bch2_inode_find_by_inum(c, inode_inum(inode), &inode_u); | |
| if (ret) | |
| goto err; | |
| /* | |
| * check this before next assertion; on filesystem error our normal | |
| * invariants are a bit broken (truncate has to truncate the page cache | |
| * before the inode). | |
| */ | |
| ret = bch2_journal_error(&c->journal); | |
| if (ret) | |
| goto err; | |
| WARN_ONCE(!test_bit(EI_INODE_ERROR, &inode->ei_flags) && | |
| inode->v.i_size < inode_u.bi_size, | |
| "truncate spotted in mem i_size < btree i_size: %llu < %llu\n", | |
| (u64) inode->v.i_size, inode_u.bi_size); | |
| if (iattr->ia_size > inode->v.i_size) { | |
| ret = bch2_extend(idmap, inode, &inode_u, iattr); | |
| goto err; | |
| } | |
| iattr->ia_valid &= ~ATTR_SIZE; | |
| ret = bch2_truncate_folio(inode, iattr->ia_size); | |
| if (unlikely(ret < 0)) | |
| goto err; | |
| truncate_setsize(&inode->v, iattr->ia_size); | |
| /* | |
| * When extending, we're going to write the new i_size to disk | |
| * immediately so we need to flush anything above the current on disk | |
| * i_size first: | |
| * | |
| * Also, when extending we need to flush the page that i_size currently | |
| * straddles - if it's mapped to userspace, we need to ensure that | |
| * userspace has to redirty it and call .mkwrite -> set_page_dirty | |
| * again to allocate the part of the page that was extended. | |
| */ | |
| if (iattr->ia_size > inode_u.bi_size) | |
| ret = filemap_write_and_wait_range(mapping, | |
| inode_u.bi_size, | |
| iattr->ia_size - 1); | |
| else if (iattr->ia_size & (PAGE_SIZE - 1)) | |
| ret = filemap_write_and_wait_range(mapping, | |
| round_down(iattr->ia_size, PAGE_SIZE), | |
| iattr->ia_size - 1); | |
| if (ret) | |
| goto err; | |
| ret = bch2_truncate(c, inode_inum(inode), iattr->ia_size, &i_sectors_delta); | |
| bch2_i_sectors_acct(c, inode, NULL, i_sectors_delta); | |
| if (unlikely(ret)) { | |
| /* | |
| * If we error here, VFS caches are now inconsistent with btree | |
| */ | |
| set_bit(EI_INODE_ERROR, &inode->ei_flags); | |
| goto err; | |
| } | |
| bch2_fs_inconsistent_on(!inode->v.i_size && inode->v.i_blocks && | |
| !bch2_journal_error(&c->journal), c, | |
| "inode %lu truncated to 0 but i_blocks %llu (ondisk %lli)", | |
| inode->v.i_ino, (u64) inode->v.i_blocks, | |
| inode->ei_inode.bi_sectors); | |
| ret = bch2_setattr_nonsize(idmap, inode, iattr); | |
| err: | |
| bch2_pagecache_block_put(inode); | |
| return bch2_err_class(ret); | |
| } | |
| /* fallocate: */ | |
| static int inode_update_times_fn(struct btree_trans *trans, | |
| struct bch_inode_info *inode, | |
| struct bch_inode_unpacked *bi, void *p) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| bi->bi_mtime = bi->bi_ctime = bch2_current_time(c); | |
| return 0; | |
| } | |
| static noinline long bchfs_fpunch(struct bch_inode_info *inode, loff_t offset, loff_t len) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| u64 end = offset + len; | |
| u64 block_start = round_up(offset, block_bytes(c)); | |
| u64 block_end = round_down(end, block_bytes(c)); | |
| bool truncated_last_page; | |
| int ret = 0; | |
| ret = bch2_truncate_folios(inode, offset, end); | |
| if (unlikely(ret < 0)) | |
| goto err; | |
| truncated_last_page = ret; | |
| truncate_pagecache_range(&inode->v, offset, end - 1); | |
| if (block_start < block_end) { | |
| s64 i_sectors_delta = 0; | |
| ret = bch2_fpunch(c, inode_inum(inode), | |
| block_start >> 9, block_end >> 9, | |
| &i_sectors_delta); | |
| bch2_i_sectors_acct(c, inode, NULL, i_sectors_delta); | |
| } | |
| mutex_lock(&inode->ei_update_lock); | |
| if (end >= inode->v.i_size && !truncated_last_page) { | |
| ret = bch2_write_inode_size(c, inode, inode->v.i_size, | |
| ATTR_MTIME|ATTR_CTIME); | |
| } else { | |
| ret = bch2_write_inode(c, inode, inode_update_times_fn, NULL, | |
| ATTR_MTIME|ATTR_CTIME); | |
| } | |
| mutex_unlock(&inode->ei_update_lock); | |
| err: | |
| return ret; | |
| } | |
| static noinline long bchfs_fcollapse_finsert(struct bch_inode_info *inode, | |
| loff_t offset, loff_t len, | |
| bool insert) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| struct address_space *mapping = inode->v.i_mapping; | |
| s64 i_sectors_delta = 0; | |
| int ret = 0; | |
| if ((offset | len) & (block_bytes(c) - 1)) | |
| return -EINVAL; | |
| if (insert) { | |
| if (offset >= inode->v.i_size) | |
| return -EINVAL; | |
| } else { | |
| if (offset + len >= inode->v.i_size) | |
| return -EINVAL; | |
| } | |
| ret = bch2_write_invalidate_inode_pages_range(mapping, offset, LLONG_MAX); | |
| if (ret) | |
| return ret; | |
| if (insert) | |
| i_size_write(&inode->v, inode->v.i_size + len); | |
| ret = bch2_fcollapse_finsert(c, inode_inum(inode), offset >> 9, len >> 9, | |
| insert, &i_sectors_delta); | |
| if (!ret && !insert) | |
| i_size_write(&inode->v, inode->v.i_size - len); | |
| bch2_i_sectors_acct(c, inode, NULL, i_sectors_delta); | |
| return ret; | |
| } | |
| static noinline int __bchfs_fallocate(struct bch_inode_info *inode, int mode, | |
| u64 start_sector, u64 end_sector) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| struct btree_trans *trans = bch2_trans_get(c); | |
| struct btree_iter iter; | |
| struct bpos end_pos = POS(inode->v.i_ino, end_sector); | |
| struct bch_io_opts opts; | |
| int ret = 0; | |
| bch2_inode_opts_get(&opts, c, &inode->ei_inode); | |
| bch2_trans_iter_init(trans, &iter, BTREE_ID_extents, | |
| POS(inode->v.i_ino, start_sector), | |
| BTREE_ITER_slots|BTREE_ITER_intent); | |
| while (!ret) { | |
| s64 i_sectors_delta = 0; | |
| struct quota_res quota_res = { 0 }; | |
| struct bkey_s_c k; | |
| unsigned sectors; | |
| bool is_allocation; | |
| u64 hole_start, hole_end; | |
| u32 snapshot; | |
| bch2_trans_begin(trans); | |
| if (bkey_ge(iter.pos, end_pos)) | |
| break; | |
| ret = bch2_subvolume_get_snapshot(trans, | |
| inode->ei_inum.subvol, &snapshot); | |
| if (ret) | |
| goto bkey_err; | |
| bch2_btree_iter_set_snapshot(&iter, snapshot); | |
| k = bch2_btree_iter_peek_slot(&iter); | |
| if ((ret = bkey_err(k))) | |
| goto bkey_err; | |
| hole_start = iter.pos.offset; | |
| hole_end = bpos_min(k.k->p, end_pos).offset; | |
| is_allocation = bkey_extent_is_allocation(k.k); | |
| /* already reserved */ | |
| if (bkey_extent_is_reservation(k) && | |
| bch2_bkey_nr_ptrs_fully_allocated(k) >= opts.data_replicas) { | |
| bch2_btree_iter_advance(&iter); | |
| continue; | |
| } | |
| if (bkey_extent_is_data(k.k) && | |
| !(mode & FALLOC_FL_ZERO_RANGE)) { | |
| bch2_btree_iter_advance(&iter); | |
| continue; | |
| } | |
| if (!(mode & FALLOC_FL_ZERO_RANGE)) { | |
| /* | |
| * Lock ordering - can't be holding btree locks while | |
| * blocking on a folio lock: | |
| */ | |
| if (bch2_clamp_data_hole(&inode->v, | |
| &hole_start, | |
| &hole_end, | |
| opts.data_replicas, true)) { | |
| ret = drop_locks_do(trans, | |
| (bch2_clamp_data_hole(&inode->v, | |
| &hole_start, | |
| &hole_end, | |
| opts.data_replicas, false), 0)); | |
| if (ret) | |
| goto bkey_err; | |
| } | |
| bch2_btree_iter_set_pos(&iter, POS(iter.pos.inode, hole_start)); | |
| if (ret) | |
| goto bkey_err; | |
| if (hole_start == hole_end) | |
| continue; | |
| } | |
| sectors = hole_end - hole_start; | |
| if (!is_allocation) { | |
| ret = bch2_quota_reservation_add(c, inode, | |
| "a_res, sectors, true); | |
| if (unlikely(ret)) | |
| goto bkey_err; | |
| } | |
| ret = bch2_extent_fallocate(trans, inode_inum(inode), &iter, | |
| sectors, opts, &i_sectors_delta, | |
| writepoint_hashed((unsigned long) current)); | |
| if (ret) | |
| goto bkey_err; | |
| bch2_i_sectors_acct(c, inode, "a_res, i_sectors_delta); | |
| if (bch2_mark_pagecache_reserved(inode, &hole_start, | |
| iter.pos.offset, true)) { | |
| ret = drop_locks_do(trans, | |
| bch2_mark_pagecache_reserved(inode, &hole_start, | |
| iter.pos.offset, false)); | |
| if (ret) | |
| goto bkey_err; | |
| } | |
| bkey_err: | |
| bch2_quota_reservation_put(c, inode, "a_res); | |
| if (bch2_err_matches(ret, BCH_ERR_transaction_restart)) | |
| ret = 0; | |
| } | |
| if (bch2_err_matches(ret, ENOSPC) && (mode & FALLOC_FL_ZERO_RANGE)) { | |
| struct quota_res quota_res = { 0 }; | |
| s64 i_sectors_delta = 0; | |
| bch2_fpunch_at(trans, &iter, inode_inum(inode), | |
| end_sector, &i_sectors_delta); | |
| bch2_i_sectors_acct(c, inode, "a_res, i_sectors_delta); | |
| bch2_quota_reservation_put(c, inode, "a_res); | |
| } | |
| bch2_trans_iter_exit(trans, &iter); | |
| bch2_trans_put(trans); | |
| return ret; | |
| } | |
| static noinline long bchfs_fallocate(struct bch_inode_info *inode, int mode, | |
| loff_t offset, loff_t len) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| u64 end = offset + len; | |
| u64 block_start = round_down(offset, block_bytes(c)); | |
| u64 block_end = round_up(end, block_bytes(c)); | |
| bool truncated_last_page = false; | |
| int ret, ret2 = 0; | |
| if (!(mode & FALLOC_FL_KEEP_SIZE) && end > inode->v.i_size) { | |
| ret = inode_newsize_ok(&inode->v, end); | |
| if (ret) | |
| return ret; | |
| } | |
| if (mode & FALLOC_FL_ZERO_RANGE) { | |
| ret = bch2_truncate_folios(inode, offset, end); | |
| if (unlikely(ret < 0)) | |
| return ret; | |
| truncated_last_page = ret; | |
| truncate_pagecache_range(&inode->v, offset, end - 1); | |
| block_start = round_up(offset, block_bytes(c)); | |
| block_end = round_down(end, block_bytes(c)); | |
| } | |
| ret = __bchfs_fallocate(inode, mode, block_start >> 9, block_end >> 9); | |
| /* | |
| * On -ENOSPC in ZERO_RANGE mode, we still want to do the inode update, | |
| * so that the VFS cache i_size is consistent with the btree i_size: | |
| */ | |
| if (ret && | |
| !(bch2_err_matches(ret, ENOSPC) && (mode & FALLOC_FL_ZERO_RANGE))) | |
| return ret; | |
| if (mode & FALLOC_FL_KEEP_SIZE && end > inode->v.i_size) | |
| end = inode->v.i_size; | |
| if (end >= inode->v.i_size && | |
| (((mode & FALLOC_FL_ZERO_RANGE) && !truncated_last_page) || | |
| !(mode & FALLOC_FL_KEEP_SIZE))) { | |
| spin_lock(&inode->v.i_lock); | |
| i_size_write(&inode->v, end); | |
| spin_unlock(&inode->v.i_lock); | |
| mutex_lock(&inode->ei_update_lock); | |
| ret2 = bch2_write_inode_size(c, inode, end, 0); | |
| mutex_unlock(&inode->ei_update_lock); | |
| } | |
| return ret ?: ret2; | |
| } | |
| long bch2_fallocate_dispatch(struct file *file, int mode, | |
| loff_t offset, loff_t len) | |
| { | |
| struct bch_inode_info *inode = file_bch_inode(file); | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| long ret; | |
| if (!bch2_write_ref_tryget(c, BCH_WRITE_REF_fallocate)) | |
| return -EROFS; | |
| inode_lock(&inode->v); | |
| inode_dio_wait(&inode->v); | |
| bch2_pagecache_block_get(inode); | |
| ret = file_modified(file); | |
| if (ret) | |
| goto err; | |
| if (!(mode & ~(FALLOC_FL_KEEP_SIZE|FALLOC_FL_ZERO_RANGE))) | |
| ret = bchfs_fallocate(inode, mode, offset, len); | |
| else if (mode == (FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE)) | |
| ret = bchfs_fpunch(inode, offset, len); | |
| else if (mode == FALLOC_FL_INSERT_RANGE) | |
| ret = bchfs_fcollapse_finsert(inode, offset, len, true); | |
| else if (mode == FALLOC_FL_COLLAPSE_RANGE) | |
| ret = bchfs_fcollapse_finsert(inode, offset, len, false); | |
| else | |
| ret = -EOPNOTSUPP; | |
| err: | |
| bch2_pagecache_block_put(inode); | |
| inode_unlock(&inode->v); | |
| bch2_write_ref_put(c, BCH_WRITE_REF_fallocate); | |
| return bch2_err_class(ret); | |
| } | |
| /* | |
| * Take a quota reservation for unallocated blocks in a given file range | |
| * Does not check pagecache | |
| */ | |
| static int quota_reserve_range(struct bch_inode_info *inode, | |
| struct quota_res *res, | |
| u64 start, u64 end) | |
| { | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| u64 sectors = end - start; | |
| int ret = bch2_trans_run(c, | |
| for_each_btree_key_in_subvolume_upto(trans, iter, | |
| BTREE_ID_extents, | |
| POS(inode->v.i_ino, start), | |
| POS(inode->v.i_ino, end - 1), | |
| inode->ei_inum.subvol, 0, k, ({ | |
| if (bkey_extent_is_allocation(k.k)) { | |
| u64 s = min(end, k.k->p.offset) - | |
| max(start, bkey_start_offset(k.k)); | |
| BUG_ON(s > sectors); | |
| sectors -= s; | |
| } | |
| 0; | |
| }))); | |
| return ret ?: bch2_quota_reservation_add(c, inode, res, sectors, true); | |
| } | |
| loff_t bch2_remap_file_range(struct file *file_src, loff_t pos_src, | |
| struct file *file_dst, loff_t pos_dst, | |
| loff_t len, unsigned remap_flags) | |
| { | |
| struct bch_inode_info *src = file_bch_inode(file_src); | |
| struct bch_inode_info *dst = file_bch_inode(file_dst); | |
| struct bch_fs *c = src->v.i_sb->s_fs_info; | |
| struct quota_res quota_res = { 0 }; | |
| s64 i_sectors_delta = 0; | |
| u64 aligned_len; | |
| loff_t ret = 0; | |
| if (remap_flags & ~(REMAP_FILE_DEDUP|REMAP_FILE_ADVISORY)) | |
| return -EINVAL; | |
| if ((pos_src & (block_bytes(c) - 1)) || | |
| (pos_dst & (block_bytes(c) - 1))) | |
| return -EINVAL; | |
| if (src == dst && | |
| abs(pos_src - pos_dst) < len) | |
| return -EINVAL; | |
| lock_two_nondirectories(&src->v, &dst->v); | |
| bch2_lock_inodes(INODE_PAGECACHE_BLOCK, src, dst); | |
| inode_dio_wait(&src->v); | |
| inode_dio_wait(&dst->v); | |
| ret = generic_remap_file_range_prep(file_src, pos_src, | |
| file_dst, pos_dst, | |
| &len, remap_flags); | |
| if (ret < 0 || len == 0) | |
| goto err; | |
| aligned_len = round_up((u64) len, block_bytes(c)); | |
| ret = bch2_write_invalidate_inode_pages_range(dst->v.i_mapping, | |
| pos_dst, pos_dst + len - 1); | |
| if (ret) | |
| goto err; | |
| ret = quota_reserve_range(dst, "a_res, pos_dst >> 9, | |
| (pos_dst + aligned_len) >> 9); | |
| if (ret) | |
| goto err; | |
| if (!(remap_flags & REMAP_FILE_DEDUP)) | |
| file_update_time(file_dst); | |
| bch2_mark_pagecache_unallocated(src, pos_src >> 9, | |
| (pos_src + aligned_len) >> 9); | |
| ret = bch2_remap_range(c, | |
| inode_inum(dst), pos_dst >> 9, | |
| inode_inum(src), pos_src >> 9, | |
| aligned_len >> 9, | |
| pos_dst + len, &i_sectors_delta); | |
| if (ret < 0) | |
| goto err; | |
| /* | |
| * due to alignment, we might have remapped slightly more than requsted | |
| */ | |
| ret = min((u64) ret << 9, (u64) len); | |
| bch2_i_sectors_acct(c, dst, "a_res, i_sectors_delta); | |
| spin_lock(&dst->v.i_lock); | |
| if (pos_dst + ret > dst->v.i_size) | |
| i_size_write(&dst->v, pos_dst + ret); | |
| spin_unlock(&dst->v.i_lock); | |
| if ((file_dst->f_flags & (__O_SYNC | O_DSYNC)) || | |
| IS_SYNC(file_inode(file_dst))) | |
| ret = bch2_flush_inode(c, dst); | |
| err: | |
| bch2_quota_reservation_put(c, dst, "a_res); | |
| bch2_unlock_inodes(INODE_PAGECACHE_BLOCK, src, dst); | |
| unlock_two_nondirectories(&src->v, &dst->v); | |
| return bch2_err_class(ret); | |
| } | |
| /* fseek: */ | |
| static loff_t bch2_seek_data(struct file *file, u64 offset) | |
| { | |
| struct bch_inode_info *inode = file_bch_inode(file); | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| subvol_inum inum = inode_inum(inode); | |
| u64 isize, next_data = MAX_LFS_FILESIZE; | |
| isize = i_size_read(&inode->v); | |
| if (offset >= isize) | |
| return -ENXIO; | |
| int ret = bch2_trans_run(c, | |
| for_each_btree_key_in_subvolume_upto(trans, iter, BTREE_ID_extents, | |
| POS(inode->v.i_ino, offset >> 9), | |
| POS(inode->v.i_ino, U64_MAX), | |
| inum.subvol, 0, k, ({ | |
| if (bkey_extent_is_data(k.k)) { | |
| next_data = max(offset, bkey_start_offset(k.k) << 9); | |
| break; | |
| } else if (k.k->p.offset >> 9 > isize) | |
| break; | |
| 0; | |
| }))); | |
| if (ret) | |
| return ret; | |
| if (next_data > offset) | |
| next_data = bch2_seek_pagecache_data(&inode->v, | |
| offset, next_data, 0, false); | |
| if (next_data >= isize) | |
| return -ENXIO; | |
| return vfs_setpos(file, next_data, MAX_LFS_FILESIZE); | |
| } | |
| static loff_t bch2_seek_hole(struct file *file, u64 offset) | |
| { | |
| struct bch_inode_info *inode = file_bch_inode(file); | |
| struct bch_fs *c = inode->v.i_sb->s_fs_info; | |
| subvol_inum inum = inode_inum(inode); | |
| u64 isize, next_hole = MAX_LFS_FILESIZE; | |
| isize = i_size_read(&inode->v); | |
| if (offset >= isize) | |
| return -ENXIO; | |
| int ret = bch2_trans_run(c, | |
| for_each_btree_key_in_subvolume_upto(trans, iter, BTREE_ID_extents, | |
| POS(inode->v.i_ino, offset >> 9), | |
| POS(inode->v.i_ino, U64_MAX), | |
| inum.subvol, BTREE_ITER_slots, k, ({ | |
| if (k.k->p.inode != inode->v.i_ino) { | |
| next_hole = bch2_seek_pagecache_hole(&inode->v, | |
| offset, MAX_LFS_FILESIZE, 0, false); | |
| break; | |
| } else if (!bkey_extent_is_data(k.k)) { | |
| next_hole = bch2_seek_pagecache_hole(&inode->v, | |
| max(offset, bkey_start_offset(k.k) << 9), | |
| k.k->p.offset << 9, 0, false); | |
| if (next_hole < k.k->p.offset << 9) | |
| break; | |
| } else { | |
| offset = max(offset, bkey_start_offset(k.k) << 9); | |
| } | |
| 0; | |
| }))); | |
| if (ret) | |
| return ret; | |
| if (next_hole > isize) | |
| next_hole = isize; | |
| return vfs_setpos(file, next_hole, MAX_LFS_FILESIZE); | |
| } | |
| loff_t bch2_llseek(struct file *file, loff_t offset, int whence) | |
| { | |
| loff_t ret; | |
| switch (whence) { | |
| case SEEK_SET: | |
| case SEEK_CUR: | |
| case SEEK_END: | |
| ret = generic_file_llseek(file, offset, whence); | |
| break; | |
| case SEEK_DATA: | |
| ret = bch2_seek_data(file, offset); | |
| break; | |
| case SEEK_HOLE: | |
| ret = bch2_seek_hole(file, offset); | |
| break; | |
| default: | |
| ret = -EINVAL; | |
| break; | |
| } | |
| return bch2_err_class(ret); | |
| } | |
| void bch2_fs_fsio_exit(struct bch_fs *c) | |
| { | |
| bioset_exit(&c->nocow_flush_bioset); | |
| } | |
| int bch2_fs_fsio_init(struct bch_fs *c) | |
| { | |
| if (bioset_init(&c->nocow_flush_bioset, | |
| 1, offsetof(struct nocow_flush, bio), 0)) | |
| return -BCH_ERR_ENOMEM_nocow_flush_bioset_init; | |
| return 0; | |
| } | |
| #endif /* NO_BCACHEFS_FS */ |