Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2230337/?format=api
{ "id": 2230337, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2230337/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-ext4/patch/177747214662.4107473.16481793405885139306.stgit@frogsfrogsfrogs/", "project": { "id": 8, "url": "http://patchwork.ozlabs.org/api/1.1/projects/8/?format=api", "name": "Linux ext4 filesystem development", "link_name": "linux-ext4", "list_id": "linux-ext4.vger.kernel.org", "list_email": "linux-ext4@vger.kernel.org", "web_url": null, "scm_url": null, "webscm_url": null }, "msgid": "<177747214662.4107473.16481793405885139306.stgit@frogsfrogsfrogs>", "date": "2026-04-29T14:54:27", "name": "[07/19] fuse2fs: implement direct write support", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "16fc3838b10cde34fc12233b1d4fbc4d7eecb9d0", "submitter": { "id": 77032, "url": "http://patchwork.ozlabs.org/api/1.1/people/77032/?format=api", "name": "Darrick J. Wong", "email": "djwong@kernel.org" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/linux-ext4/patch/177747214662.4107473.16481793405885139306.stgit@frogsfrogsfrogs/mbox/", "series": [ { "id": 502087, "url": "http://patchwork.ozlabs.org/api/1.1/series/502087/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-ext4/list/?series=502087", "date": "2026-04-29T14:53:09", "name": "[01/19] fuse2fs: implement bare minimum iomap for file mapping reporting", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/502087/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2230337/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2230337/checks/", "tags": {}, "headers": { "Return-Path": "\n <SRS0=ee83=C4=vger.kernel.org=linux-ext4+bounces-16196-patchwork-incoming=ozlabs.org@ozlabs.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "linux-ext4@vger.kernel.org" ], "Delivered-To": [ "patchwork-incoming@legolas.ozlabs.org", "patchwork-incoming@ozlabs.org" ], "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=QTWrMxko;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=ozlabs.org\n (client-ip=150.107.74.76; helo=mail.ozlabs.org;\n envelope-from=srs0=ee83=c4=vger.kernel.org=linux-ext4+bounces-16196-patchwork-incoming=ozlabs.org@ozlabs.org;\n receiver=patchwork.ozlabs.org)", "gandalf.ozlabs.org;\n arc=pass smtp.remote-ip=\"2600:3c15:e001:75::12fc:5321\"\n arc.chain=subspace.kernel.org", "gandalf.ozlabs.org;\n dmarc=pass (p=quarantine dis=none) header.from=kernel.org", "gandalf.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=QTWrMxko;\n\tdkim-atps=neutral", "gandalf.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c15:e001:75::12fc:5321; helo=sin.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16196-patchwork-incoming=ozlabs.org@vger.kernel.org;\n receiver=ozlabs.org)", "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=\"QTWrMxko\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=10.30.226.201" ], "Received": [ "from mail.ozlabs.org (gandalf.ozlabs.org [150.107.74.76])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g5LJ26Sswz1xqf\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 30 Apr 2026 01:07:14 +1000 (AEST)", "from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3])\n\tby gandalf.ozlabs.org (Postfix) with ESMTP id 4g5LJ218XKz4wLM\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 30 Apr 2026 01:07:14 +1000 (AEST)", "by gandalf.ozlabs.org (Postfix)\n\tid 4g5LJ214hJz4wM1; Thu, 30 Apr 2026 01:07:14 +1000 (AEST)", "from sin.lore.kernel.org (sin.lore.kernel.org\n [IPv6:2600:3c15:e001:75::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby gandalf.ozlabs.org (Postfix) with ESMTPS id 4g5LHy3sGfz4wLM\n\tfor <patchwork-incoming@ozlabs.org>; Thu, 30 Apr 2026 01:07:10 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sin.lore.kernel.org (Postfix) with ESMTP id 60E7F3039DD8\n\tfor <patchwork-incoming@ozlabs.org>; Wed, 29 Apr 2026 14:54:30 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 9C6AD342CB0;\n\tWed, 29 Apr 2026 14:54:28 +0000 (UTC)", "from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org\n [10.30.226.201])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 5C02631ED80;\n\tWed, 29 Apr 2026 14:54:28 +0000 (UTC)", "by smtp.kernel.org (Postfix) with ESMTPSA id EB01EC19425;\n\tWed, 29 Apr 2026 14:54:27 +0000 (UTC)" ], "ARC-Seal": [ "i=2; a=rsa-sha256; d=ozlabs.org; s=201707; t=1777475234; cv=pass;\n\tb=b7PwlMg/i4oV6J6zo+SnnaTOZd3KBN779fnJ18xtROGZHxuLlZ7NRsKSvyuov1pw/8GvoVLO8x/kVAfk75DNQf9cyDShHHFcmEK+BC7QKb3vVept96ax5JiqbrmRMV3rRt+XyA5KYvRbFJKgBIUK7RLo/zyt5n/E/mtTe7bntb9ZhSH16gB8b4qGUW9OKJDplC1PEhGJzM7b75kkPblwbjZS7vjRXNcs0FmBCI4lmZqTA9uqyYLGlB2qrdCygnxerIKH1R8xu9NHf4Yv3oLvczVrqGsEHe4CsArHC4nbUonvsaYGNbGLEwewYAxkV4ptUeQoDxIwOYn7r/2TNB/dfw==", "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777474468; cv=none;\n b=eNitgtfjREo7Jy5xyPRCgcEplsYsJ5bIfIFy/wQYBTmlAWCFssZg3uYSwa3Dhnvmyks1u+IeR72magfUYcnPDNManADpcF7Uz1xvRnTf6z6FRihDtIKBKfNJNFiz9QpzgIiJx9kfNuMC/4cxKkZfX7exLUwmlBLwPe69sNyOaeA=" ], "ARC-Message-Signature": [ "i=2; a=rsa-sha256; d=ozlabs.org; s=201707;\n\tt=1777475234; c=relaxed/relaxed;\n\tbh=79AL7Ht2lI08c2BmKgYY6vT1gRt8RElXewmUtki5uwo=;\n\th=Date:Subject:From:To:Cc:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=p1dy1l+oKonvlao9a1XM93PLCGTYa3xyfk0yunFJa2MpwIrkWxjvz52kUdNZLYSopNsIFgSTwrKwyitmcSsy0fvP2mF9SwbbVSmCXHGvxYXzgqkYm7SSQJJswzXHE1ar35ascq7YegRWZG1K7YwoOtwsF1cNlor6J/MgBW8sqL22BunyikjD8zfEg4CM5nk3G4/blJnDYUWweJE5QZWPBzRLAVZBKRRNpPxKDnAntlq8mFYBnPu4yz1ltN0FaRl6m0pwf1mlRqBgSh4sNuqyshX/+NRSrOeT4NB1ydQVNzCNKpzj3In9eZg5yAF4IHMVrGjLwVp8aL4V5UMDCiI6kg==", "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777474468; c=relaxed/simple;\n\tbh=hAsYX9150pVhdkDddUYuWJi2BTaeKUdYNMqR0im0kQs=;\n\th=Date:Subject:From:To:Cc:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=NqUmsLTMZgYdku4K289VqDUGgyy8Su/j35lqlqy1xtfZN/Rr3IyH02I+Nbdpn5omFgtodbilvhlApvPRlH2F/LaCAVj8ulu0rK/wyIh2+upejo5e0s7NuSp4/kGZpnvkWwih68TsKqrZcGh/jSt6tswYKB1mo+QWOxTqQ1wDu7Y=" ], "ARC-Authentication-Results": [ "i=2; gandalf.ozlabs.org;\n dmarc=pass (p=quarantine dis=none) header.from=kernel.org;\n dkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=QTWrMxko; dkim-atps=neutral;\n spf=pass (client-ip=2600:3c15:e001:75::12fc:5321; helo=sin.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16196-patchwork-incoming=ozlabs.org@vger.kernel.org;\n receiver=ozlabs.org) smtp.mailfrom=vger.kernel.org", "i=1; smtp.subspace.kernel.org;\n dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=QTWrMxko; arc=none smtp.client-ip=10.30.226.201" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;\n\ts=k20201202; t=1777474468;\n\tbh=hAsYX9150pVhdkDddUYuWJi2BTaeKUdYNMqR0im0kQs=;\n\th=Date:Subject:From:To:Cc:In-Reply-To:References:From;\n\tb=QTWrMxkoOiBbc2h2V4VfqgSEDpY/SvUMKuzbG+6cj5n7zJTTH0xL/gGEeT36MlDKx\n\t jReUjDR/YofuVMUUa7kGgSQyJVavDgnnn+aI0bozAwZ618XqGqklsTndg3f3UY7RQZ\n\t gS2DqHQFuo9ZE8fmaCenFaJ6Biq3opoGIyBDnGmU4ifDK7a5AyxEvRmTqrlO4b/w4x\n\t 3qHaBrwx2w4dvU+DTxCbtJUyndvHfMHKthD8/n3lZkKIm1uyIFoisAkAbazRH7qoH5\n\t KpTPju8DLa6XWXndZ6m79CTaHDGFkM1c5dt2Gi5FKmgpmZNYapIvu1uJSlr5CFygdQ\n\t OIwU1rCtVBWug==", "Date": "Wed, 29 Apr 2026 07:54:27 -0700", "Subject": "[PATCH 07/19] fuse2fs: implement direct write support", "From": "\"Darrick J. Wong\" <djwong@kernel.org>", "To": "tytso@mit.edu", "Cc": "bernd@bsbernd.com, miklos@szeredi.hu, linux-ext4@vger.kernel.org,\n neal@gompa.dev, linux-fsdevel@vger.kernel.org, fuse-devel@lists.linux.dev,\n joannelkoong@gmail.com", "Message-ID": "<177747214662.4107473.16481793405885139306.stgit@frogsfrogsfrogs>", "In-Reply-To": "<177747214459.4107473.9520724883867588970.stgit@frogsfrogsfrogs>", "References": "<177747214459.4107473.9520724883867588970.stgit@frogsfrogsfrogs>", "Precedence": "bulk", "X-Mailing-List": "linux-ext4@vger.kernel.org", "List-Id": "<linux-ext4.vger.kernel.org>", "List-Subscribe": "<mailto:linux-ext4+subscribe@vger.kernel.org>", "List-Unsubscribe": "<mailto:linux-ext4+unsubscribe@vger.kernel.org>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "7bit", "X-Spam-Status": "No, score=-1.2 required=5.0 tests=ARC_SIGNED,ARC_VALID,\n\tDKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DMARC_PASS,\n\tMAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=disabled\n\tversion=4.0.1", "X-Spam-Checker-Version": "SpamAssassin 4.0.1 (2024-03-25) on gandalf.ozlabs.org" }, "content": "From: Darrick J. Wong <djwong@kernel.org>\n\nWire up an iomap_begin method that can allocate into holes so that we\ncan do directio writes.\n\nSigned-off-by: \"Darrick J. Wong\" <djwong@kernel.org>\n---\n fuse4fs/fuse4fs.c | 477 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n misc/fuse2fs.c | 471 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 942 insertions(+), 6 deletions(-)", "diff": "diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c\nindex 1489be2104f2b2..8b508de5b8cb65 100644\n--- a/fuse4fs/fuse4fs.c\n+++ b/fuse4fs/fuse4fs.c\n@@ -6453,12 +6453,106 @@ static int fuse4fs_iomap_begin_read(struct fuse4fs *ff, ext2_ino_t ino,\n \t\t\t\t\t opflags, read);\n }\n \n+static int fuse4fs_iomap_write_allocate(struct fuse4fs *ff, ext2_ino_t ino,\n+\t\t\t\t\tstruct ext2_inode_large *inode,\n+\t\t\t\t\toff_t pos, uint64_t count,\n+\t\t\t\t\tuint32_t opflags,\n+\t\t\t\t\tstruct fuse_file_iomap *read,\n+\t\t\t\t\tbool *dirty)\n+{\n+\text2_filsys fs = ff->fs;\n+\tblk64_t startoff = FUSE4FS_B_TO_FSBT(ff, pos);\n+\tblk64_t stopoff = FUSE4FS_B_TO_FSB(ff, pos + count);\n+\tblk64_t old_iblocks;\n+\terrcode_t err;\n+\tint ret;\n+\n+\tdbg_printf(ff,\n+ \"%s: ino=%d startoff 0x%llx blockcount 0x%llx\\n\",\n+\t\t __func__, ino, startoff, stopoff - startoff);\n+\n+\tif (!fuse4fs_can_allocate(ff, stopoff - startoff))\n+\t\treturn -ENOSPC;\n+\n+\told_iblocks = ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));\n+\terr = ext2fs_fallocate(fs, EXT2_FALLOCATE_FORCE_UNINIT, ino,\n+\t\t\t EXT2_INODE(inode), ~0ULL, startoff,\n+\t\t\t stopoff - startoff);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/*\n+\t * New allocations for file data blocks on indirect mapped files are\n+\t * zeroed through the IO manager so we have to flush it to disk.\n+\t */\n+\tif (!(inode->i_flags & EXT4_EXTENTS_FL) &&\n+\t old_iblocks != ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode))) {\n+\t\terr = io_channel_flush(fs->io);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t/* pick up the newly allocated mapping */\n+\tret = fuse4fs_iomap_begin_read(ff, ino, inode, pos, count, opflags,\n+\t\t\t\t read);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tread->flags |= FUSE_IOMAP_F_DIRTY;\n+\t*dirty = true;\n+\treturn 0;\n+}\n+\n+static off_t fuse4fs_max_file_size(const struct fuse4fs *ff,\n+\t\t\t\t const struct ext2_inode_large *inode)\n+{\n+\text2_filsys fs = ff->fs;\n+\tblk64_t addr_per_block, max_map_block;\n+\n+\tif (inode->i_flags & EXT4_EXTENTS_FL) {\n+\t\tmax_map_block = (1ULL << 32) - 1;\n+\t} else {\n+\t\taddr_per_block = fs->blocksize >> 2;\n+\t\tmax_map_block = addr_per_block;\n+\t\tmax_map_block += addr_per_block * addr_per_block;\n+\t\tmax_map_block += addr_per_block * addr_per_block * addr_per_block;\n+\t\tmax_map_block += 12;\n+\t}\n+\n+\treturn FUSE4FS_FSB_TO_B(ff, max_map_block) + (fs->blocksize - 1);\n+}\n+\n static int fuse4fs_iomap_begin_write(struct fuse4fs *ff, ext2_ino_t ino,\n \t\t\t\t struct ext2_inode_large *inode, off_t pos,\n \t\t\t\t uint64_t count, uint32_t opflags,\n-\t\t\t\t struct fuse_file_iomap *read)\n+\t\t\t\t struct fuse_file_iomap *read,\n+\t\t\t\t bool *dirty)\n {\n-\treturn -ENOSYS;\n+\toff_t max_size = fuse4fs_max_file_size(ff, inode);\n+\tint ret;\n+\n+\tif (!(opflags & FUSE_IOMAP_OP_DIRECT))\n+\t\treturn -ENOSYS;\n+\n+\tif (pos >= max_size)\n+\t\treturn -EFBIG;\n+\n+\tif (pos >= max_size - count)\n+\t\tcount = max_size - pos;\n+\n+\tret = fuse4fs_iomap_begin_read(ff, ino, inode, pos, count, opflags,\n+\t\t\t\t read);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (fuse_iomap_need_write_allocate(opflags, read)) {\n+\t\tret = fuse4fs_iomap_write_allocate(ff, ino, inode, pos, count,\n+\t\t\t\t\t\t opflags, read, dirty);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n }\n \n static void op_iomap_begin(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare,\n@@ -6470,6 +6564,7 @@ static void op_iomap_begin(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare,\n \text2_filsys fs;\n \text2_ino_t ino;\n \terrcode_t err;\n+\tbool dirty = false;\n \tint ret = 0;\n \n \tFUSE4FS_CHECK_CONTEXT(req);\n@@ -6493,7 +6588,7 @@ static void op_iomap_begin(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare,\n \t\t\t\t\t\t opflags, &read);\n \telse if (fuse_iomap_is_write(opflags))\n \t\tret = fuse4fs_iomap_begin_write(ff, ino, &inode, pos, count,\n-\t\t\t\t\t\topflags, &read);\n+\t\t\t\t\t\topflags, &read, &dirty);\n \telse\n \t\tret = fuse4fs_iomap_begin_read(ff, ino, &inode, pos, count,\n \t\t\t\t\t opflags, &read);\n@@ -6518,6 +6613,14 @@ static void op_iomap_begin(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare,\n \t\tgoto out_unlock;\n \t}\n \n+\tif (dirty) {\n+\t\terr = fuse4fs_write_inode(fs, ino, &inode);\n+\t\tif (err) {\n+\t\t\tret = translate_error(fs, ino, err);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\t}\n+\n out_unlock:\n \tfuse4fs_finish(ff, ret);\n \tif (ret)\n@@ -6667,6 +6770,373 @@ static void op_iomap_config(fuse_req_t req,\n \telse\n \t\tfuse_reply_iomap_config(req, &cfg);\n }\n+\n+static inline bool fuse4fs_can_merge_mappings(const struct ext2fs_extent *left,\n+\t\t\t\t\t const struct ext2fs_extent *right)\n+{\n+\tuint64_t max_len = (left->e_flags & EXT2_EXTENT_FLAGS_UNINIT) ?\n+\t\t\t\tEXT_UNINIT_MAX_LEN : EXT_INIT_MAX_LEN;\n+\n+\treturn left->e_lblk + left->e_len == right->e_lblk &&\n+\t left->e_pblk + left->e_len == right->e_pblk &&\n+\t (left->e_flags & EXT2_EXTENT_FLAGS_UNINIT) ==\n+\t (right->e_flags & EXT2_EXTENT_FLAGS_UNINIT) &&\n+\t (uint64_t)left->e_len + right->e_len <= max_len;\n+}\n+\n+static int fuse4fs_try_merge_mappings(struct fuse4fs *ff, ext2_ino_t ino,\n+\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t blk64_t startoff)\n+{\n+\text2_filsys fs = ff->fs;\n+\tstruct ext2fs_extent left, right;\n+\terrcode_t err;\n+\n+\t/* Look up the mappings before startoff */\n+\terr = fuse4fs_get_mapping_at(ff, handle, startoff - 1, &left);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND)\n+\t\treturn 0;\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Look up the mapping at startoff */\n+\terr = fuse4fs_get_mapping_at(ff, handle, startoff, &right);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND)\n+\t\treturn 0;\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Can we combine them? */\n+\tif (!fuse4fs_can_merge_mappings(&left, &right))\n+\t\treturn 0;\n+\n+\t/*\n+\t * Delete the mapping after startoff because libext2fs cannot handle\n+\t * overlapping mappings.\n+\t */\n+\terr = ext2fs_extent_delete(handle, 0);\n+\tDUMP_EXTENT(ff, \"remover\", startoff, err, &right);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\terr = ext2fs_extent_fix_parents(handle);\n+\tDUMP_EXTENT(ff, \"fixremover\", startoff, err, &right);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Move back and lengthen the mapping before startoff */\n+\terr = ext2fs_extent_goto(handle, left.e_lblk);\n+\tDUMP_EXTENT(ff, \"movel\", startoff - 1, err, &left);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\tleft.e_len += right.e_len;\n+\terr = ext2fs_extent_replace(handle, 0, &left);\n+\tDUMP_EXTENT(ff, \"replacel\", startoff - 1, err, &left);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\terr = ext2fs_extent_fix_parents(handle);\n+\tDUMP_EXTENT(ff, \"fixreplacel\", startoff - 1, err, &left);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\treturn 0;\n+}\n+\n+static int fuse4fs_convert_unwritten_mapping(struct fuse4fs *ff,\n+\t\t\t\t\t ext2_ino_t ino,\n+\t\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t\t blk64_t *cursor, blk64_t stopoff)\n+{\n+\text2_filsys fs = ff->fs;\n+\tstruct ext2fs_extent extent;\n+\tblk64_t startoff = *cursor;\n+\terrcode_t err;\n+\n+\t/*\n+\t * Find the mapping at startoff. Note that we can find holes because\n+\t * the mapping data can change due to racing writes.\n+\t */\n+\terr = fuse4fs_get_mapping_at(ff, handle, startoff, &extent);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND) {\n+\t\t/*\n+\t\t * If we didn't find any mappings at all then the file is\n+\t\t * completely sparse. There's nothing to convert.\n+\t\t */\n+\t\t*cursor = stopoff;\n+\t\treturn 0;\n+\t}\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/*\n+\t * The mapping is completely to the left of the range that we want.\n+\t * Let's see what's in the next extent, if there is one.\n+\t */\n+\tif (startoff >= extent.e_lblk + extent.e_len) {\n+\t\t/*\n+\t\t * Mapping ends to the left of the current position. Try to\n+\t\t * find the next mapping. If there is no next mapping, then\n+\t\t * we're done.\n+\t\t */\n+\t\terr = fuse4fs_get_next_mapping(ff, handle, startoff, &extent);\n+\t\tif (err == EXT2_ET_EXTENT_NOT_FOUND) {\n+\t\t\t*cursor = stopoff;\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t/*\n+\t * The mapping is completely to the right of the range that we want,\n+\t * so we're done.\n+\t */\n+\tif (extent.e_lblk >= stopoff) {\n+\t\t*cursor = stopoff;\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * At this point, we have a mapping that overlaps (startoff, stopoff].\n+\t * If the mapping is already written, move on to the next one.\n+\t */\n+\tif (!(extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT))\n+\t\tgoto next;\n+\n+\tif (startoff > extent.e_lblk) {\n+\t\tstruct ext2fs_extent newex = extent;\n+\n+\t\t/*\n+\t\t * Unwritten mapping starts before startoff. Shorten\n+\t\t * the previous mapping...\n+\t\t */\n+\t\tnewex.e_len = startoff - extent.e_lblk;\n+\t\terr = ext2fs_extent_replace(handle, 0, &newex);\n+\t\tDUMP_EXTENT(ff, \"shortenp\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixshortenp\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\t/* ...and create new written mapping at startoff. */\n+\t\textent.e_len -= newex.e_len;\n+\t\textent.e_lblk += newex.e_len;\n+\t\textent.e_pblk += newex.e_len;\n+\t\textent.e_flags = newex.e_flags & ~EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_insert(handle,\n+\t\t\t\t\t EXT2_EXTENT_INSERT_AFTER,\n+\t\t\t\t\t &extent);\n+\t\tDUMP_EXTENT(ff, \"insertx\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixinsertx\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\tif (extent.e_lblk + extent.e_len > stopoff) {\n+\t\tstruct ext2fs_extent newex = extent;\n+\n+\t\t/*\n+\t\t * Unwritten mapping ends after stopoff. Shorten the current\n+\t\t * mapping...\n+\t\t */\n+\t\textent.e_len = stopoff - extent.e_lblk;\n+\t\textent.e_flags &= ~EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_replace(handle, 0, &extent);\n+\t\tDUMP_EXTENT(ff, \"shortenn\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixshortenn\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\t/* ..and create a new unwritten mapping at stopoff. */\n+\t\tnewex.e_pblk += extent.e_len;\n+\t\tnewex.e_lblk += extent.e_len;\n+\t\tnewex.e_len -= extent.e_len;\n+\t\tnewex.e_flags |= EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_insert(handle,\n+\t\t\t\t\t EXT2_EXTENT_INSERT_AFTER,\n+\t\t\t\t\t &newex);\n+\t\tDUMP_EXTENT(ff, \"insertn\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixinsertn\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t/* Still unwritten? Update the state. */\n+\tif (extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT) {\n+\t\textent.e_flags &= ~EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_replace(handle, 0, &extent);\n+\t\tDUMP_EXTENT(ff, \"replacex\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixreplacex\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+next:\n+\t/* Try to merge with the previous extent */\n+\tif (startoff > 0) {\n+\t\terr = fuse4fs_try_merge_mappings(ff, ino, handle, startoff);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t*cursor = extent.e_lblk + extent.e_len;\n+\treturn 0;\n+}\n+\n+static int fuse4fs_convert_unwritten_mappings(struct fuse4fs *ff,\n+\t\t\t\t\t ext2_ino_t ino,\n+\t\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t\t off_t pos, size_t written)\n+{\n+\text2_extent_handle_t handle;\n+\text2_filsys fs = ff->fs;\n+\tblk64_t startoff = FUSE4FS_B_TO_FSBT(ff, pos);\n+\tconst blk64_t stopoff = FUSE4FS_B_TO_FSB(ff, pos + written);\n+\terrcode_t err;\n+\tint ret;\n+\n+\terr = ext2fs_extent_open2(fs, ino, EXT2_INODE(inode), &handle);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Walk every mapping in the range, converting them. */\n+\twhile (startoff < stopoff) {\n+\t\tblk64_t old_startoff = startoff;\n+\n+\t\tret = fuse4fs_convert_unwritten_mapping(ff, ino, inode, handle,\n+\t\t\t\t\t\t\t&startoff, stopoff);\n+\t\tif (ret)\n+\t\t\tgoto out_handle;\n+\t\tif (startoff <= old_startoff) {\n+\t\t\t/* Do not go backwards. */\n+\t\t\tret = translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);\n+\t\t\tgoto out_handle;\n+\t\t}\n+\t}\n+\n+\t/* Try to merge the right edge */\n+\tret = fuse4fs_try_merge_mappings(ff, ino, handle, stopoff);\n+out_handle:\n+\text2fs_extent_free(handle);\n+\treturn ret;\n+}\n+\n+static void op_iomap_ioend(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare,\n+\t\t\t off_t pos, size_t written, uint32_t ioendflags,\n+\t\t\t int error, uint32_t dev, uint64_t new_addr)\n+{\n+\tstruct fuse4fs *ff = fuse4fs_get(req);\n+\tstruct ext2_inode_large inode;\n+\text2_filsys fs;\n+\text2_ino_t ino;\n+\text2_off64_t isize;\n+\terrcode_t err;\n+\tbool dirty = false;\n+\toff_t newsize = -1;\n+\tint ret = 0;\n+\n+\tFUSE4FS_CHECK_CONTEXT(req);\n+\tFUSE4FS_CONVERT_FINO(req, &ino, fino);\n+\n+\tdbg_printf(ff,\n+ \"%s: ino=%d pos=0x%llx written=0x%zx ioendflags=0x%x error=%d dev=%u new_addr=0x%llx\\n\",\n+\t\t __func__, ino,\n+\t\t (unsigned long long)pos,\n+\t\t written,\n+\t\t ioendflags,\n+\t\t error,\n+\t\t dev,\n+\t\t (unsigned long long)new_addr);\n+\n+\tif (error) {\n+\t\tfuse_reply_err(req, -error);\n+\t\treturn;\n+\t}\n+\n+\tfs = fuse4fs_start(ff);\n+\n+\t/* should never see these ioend types */\n+\tif (ioendflags & FUSE_IOMAP_IOEND_SHARED) {\n+\t\tret = translate_error(fs, ino, EXT2_ET_FILESYSTEM_CORRUPTED);\n+\t\tgoto out_unlock;\n+\t}\n+\n+\terr = fuse4fs_read_inode(fs, ino, &inode);\n+\tif (err) {\n+\t\tret = translate_error(fs, ino, err);\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tif (ioendflags & FUSE_IOMAP_IOEND_UNWRITTEN) {\n+\t\t/* unwritten extents are only supported on extents files */\n+\t\tif (!(inode.i_flags & EXT4_EXTENTS_FL)) {\n+\t\t\tret = translate_error(fs, ino,\n+\t\t\t\t\t EXT2_ET_FILESYSTEM_CORRUPTED);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\n+\t\tret = fuse4fs_convert_unwritten_mappings(ff, ino, &inode,\n+\t\t\t\t\t\t\t pos, written);\n+\t\tif (ret)\n+\t\t\tgoto out_unlock;\n+\n+\t\tdirty = true;\n+\t}\n+\n+\tisize = EXT2_I_SIZE(&inode);\n+\tif (pos + written > isize) {\n+\t\terr = ext2fs_inode_size_set(fs, EXT2_INODE(&inode),\n+\t\t\t\t\t pos + written);\n+\t\tif (err) {\n+\t\t\tret = translate_error(fs, ino, err);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\n+\t\tdirty = true;\n+\t}\n+\n+\tif (dirty) {\n+\t\terr = fuse4fs_write_inode(fs, ino, &inode);\n+\t\tif (err) {\n+\t\t\tret = translate_error(fs, ino, err);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\t}\n+\n+\tnewsize = EXT2_I_SIZE(&inode);\n+out_unlock:\n+\tfuse4fs_finish(ff, ret);\n+\tif (ret)\n+\t\tfuse_reply_err(req, -ret);\n+\telse\n+\t\tfuse_reply_iomap_ioend(req, newsize);\n+}\n #endif /* HAVE_FUSE_IOMAP */\n \n static struct fuse_lowlevel_ops fs_ops = {\n@@ -6716,6 +7186,7 @@ static struct fuse_lowlevel_ops fs_ops = {\n \t.iomap_begin = op_iomap_begin,\n \t.iomap_end = op_iomap_end,\n \t.iomap_config = op_iomap_config,\n+\t.iomap_ioend = op_iomap_ioend,\n #endif /* HAVE_FUSE_IOMAP */\n };\n \ndiff --git a/misc/fuse2fs.c b/misc/fuse2fs.c\nindex 4b37bcde63d0f2..67a5bc4c5cc986 100644\n--- a/misc/fuse2fs.c\n+++ b/misc/fuse2fs.c\n@@ -5688,12 +5688,103 @@ static int fuse2fs_iomap_begin_read(struct fuse2fs *ff, ext2_ino_t ino,\n \t\t\t\t\t opflags, read);\n }\n \n+static int fuse2fs_iomap_write_allocate(struct fuse2fs *ff, ext2_ino_t ino,\n+\t\t\t\t struct ext2_inode_large *inode, off_t pos,\n+\t\t\t\t uint64_t count, uint32_t opflags,\n+\t\t\t\t struct fuse_file_iomap *read, bool *dirty)\n+{\n+\text2_filsys fs = ff->fs;\n+\tblk64_t startoff = FUSE2FS_B_TO_FSBT(ff, pos);\n+\tblk64_t stopoff = FUSE2FS_B_TO_FSB(ff, pos + count);\n+\tblk64_t old_iblocks;\n+\terrcode_t err;\n+\tint ret;\n+\n+\tdbg_printf(ff, \"%s: write_alloc ino=%u startoff 0x%llx blockcount 0x%llx\\n\",\n+\t\t __func__, ino, startoff, stopoff - startoff);\n+\n+\tif (!fs_can_allocate(ff, stopoff - startoff))\n+\t\treturn -ENOSPC;\n+\n+\told_iblocks = ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));\n+\terr = ext2fs_fallocate(fs, EXT2_FALLOCATE_FORCE_UNINIT, ino,\n+\t\t\t EXT2_INODE(inode), ~0ULL, startoff,\n+\t\t\t stopoff - startoff);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/*\n+\t * New allocations for file data blocks on indirect mapped files are\n+\t * zeroed through the IO manager so we have to flush it to disk.\n+\t */\n+\tif (!(inode->i_flags & EXT4_EXTENTS_FL) &&\n+\t old_iblocks != ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode))) {\n+\t\terr = io_channel_flush(fs->io);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t/* pick up the newly allocated mapping */\n+\tret = fuse2fs_iomap_begin_read(ff, ino, inode, pos, count, opflags,\n+\t\t\t\t read);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tread->flags |= FUSE_IOMAP_F_DIRTY;\n+\t*dirty = true;\n+\treturn 0;\n+}\n+\n+static off_t fuse2fs_max_file_size(const struct fuse2fs *ff,\n+\t\t\t\t const struct ext2_inode_large *inode)\n+{\n+\text2_filsys fs = ff->fs;\n+\tblk64_t addr_per_block, max_map_block;\n+\n+\tif (inode->i_flags & EXT4_EXTENTS_FL) {\n+\t\tmax_map_block = (1ULL << 32) - 1;\n+\t} else {\n+\t\taddr_per_block = fs->blocksize >> 2;\n+\t\tmax_map_block = addr_per_block;\n+\t\tmax_map_block += addr_per_block * addr_per_block;\n+\t\tmax_map_block += addr_per_block * addr_per_block * addr_per_block;\n+\t\tmax_map_block += 12;\n+\t}\n+\n+\treturn FUSE2FS_FSB_TO_B(ff, max_map_block) + (fs->blocksize - 1);\n+}\n+\n static int fuse2fs_iomap_begin_write(struct fuse2fs *ff, ext2_ino_t ino,\n \t\t\t\t struct ext2_inode_large *inode, off_t pos,\n \t\t\t\t uint64_t count, uint32_t opflags,\n-\t\t\t\t struct fuse_file_iomap *read)\n+\t\t\t\t struct fuse_file_iomap *read,\n+\t\t\t\t bool *dirty)\n {\n-\treturn -ENOSYS;\n+\toff_t max_size = fuse2fs_max_file_size(ff, inode);\n+\tint ret;\n+\n+\tif (!(opflags & FUSE_IOMAP_OP_DIRECT))\n+\t\treturn -ENOSYS;\n+\n+\tif (pos >= max_size)\n+\t\treturn -EFBIG;\n+\n+\tif (pos >= max_size - count)\n+\t\tcount = max_size - pos;\n+\n+\tret = fuse2fs_iomap_begin_read(ff, ino, inode, pos, count, opflags,\n+\t\t\t\t read);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (fuse_iomap_need_write_allocate(opflags, read)) {\n+\t\tret = fuse2fs_iomap_write_allocate(ff, ino, inode, pos, count,\n+\t\t\t\t\t\t opflags, read, dirty);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n }\n \n static int op_iomap_begin(const char *path, uint64_t nodeid, uint64_t attr_ino,\n@@ -5705,6 +5796,7 @@ static int op_iomap_begin(const char *path, uint64_t nodeid, uint64_t attr_ino,\n \tstruct ext2_inode_large inode;\n \text2_filsys fs;\n \terrcode_t err;\n+\tbool dirty = false;\n \tint ret = 0;\n \n \tFUSE2FS_CHECK_CONTEXT(ff);\n@@ -5730,7 +5822,7 @@ static int op_iomap_begin(const char *path, uint64_t nodeid, uint64_t attr_ino,\n \t\t\t\t\t\t count, opflags, read);\n \telse if (fuse_iomap_is_write(opflags))\n \t\tret = fuse2fs_iomap_begin_write(ff, attr_ino, &inode, pos,\n-\t\t\t\t\t\tcount, opflags, read);\n+\t\t\t\t\t\tcount, opflags, read, &dirty);\n \telse\n \t\tret = fuse2fs_iomap_begin_read(ff, attr_ino, &inode, pos,\n \t\t\t\t\t count, opflags, read);\n@@ -5755,6 +5847,14 @@ static int op_iomap_begin(const char *path, uint64_t nodeid, uint64_t attr_ino,\n \t\tgoto out_unlock;\n \t}\n \n+\tif (dirty) {\n+\t\terr = fuse2fs_write_inode(fs, attr_ino, &inode);\n+\t\tif (err) {\n+\t\t\tret = translate_error(fs, attr_ino, err);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\t}\n+\n out_unlock:\n \tfuse2fs_finish(ff, ret);\n \treturn ret;\n@@ -5896,6 +5996,370 @@ static int op_iomap_config(const struct fuse_iomap_config_params *p,\n \tfuse2fs_finish(ff, ret);\n \treturn ret;\n }\n+\n+static inline bool fuse2fs_can_merge_mappings(const struct ext2fs_extent *left,\n+\t\t\t\t\t const struct ext2fs_extent *right)\n+{\n+\tuint64_t max_len = (left->e_flags & EXT2_EXTENT_FLAGS_UNINIT) ?\n+\t\t\t\tEXT_UNINIT_MAX_LEN : EXT_INIT_MAX_LEN;\n+\n+\treturn left->e_lblk + left->e_len == right->e_lblk &&\n+\t left->e_pblk + left->e_len == right->e_pblk &&\n+\t (left->e_flags & EXT2_EXTENT_FLAGS_UNINIT) ==\n+\t (right->e_flags & EXT2_EXTENT_FLAGS_UNINIT) &&\n+\t (uint64_t)left->e_len + right->e_len <= max_len;\n+}\n+\n+static int fuse2fs_try_merge_mappings(struct fuse2fs *ff, ext2_ino_t ino,\n+\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t blk64_t startoff)\n+{\n+\text2_filsys fs = ff->fs;\n+\tstruct ext2fs_extent left, right;\n+\terrcode_t err;\n+\n+\t/* Look up the mappings before startoff */\n+\terr = fuse2fs_get_mapping_at(ff, handle, startoff - 1, &left);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND)\n+\t\treturn 0;\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Look up the mapping at startoff */\n+\terr = fuse2fs_get_mapping_at(ff, handle, startoff, &right);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND)\n+\t\treturn 0;\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Can we combine them? */\n+\tif (!fuse2fs_can_merge_mappings(&left, &right))\n+\t\treturn 0;\n+\n+\t/*\n+\t * Delete the mapping after startoff because libext2fs cannot handle\n+\t * overlapping mappings.\n+\t */\n+\terr = ext2fs_extent_delete(handle, 0);\n+\tDUMP_EXTENT(ff, \"remover\", startoff, err, &right);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\terr = ext2fs_extent_fix_parents(handle);\n+\tDUMP_EXTENT(ff, \"fixremover\", startoff, err, &right);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Move back and lengthen the mapping before startoff */\n+\terr = ext2fs_extent_goto(handle, left.e_lblk);\n+\tDUMP_EXTENT(ff, \"movel\", startoff - 1, err, &left);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\tleft.e_len += right.e_len;\n+\terr = ext2fs_extent_replace(handle, 0, &left);\n+\tDUMP_EXTENT(ff, \"replacel\", startoff - 1, err, &left);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\terr = ext2fs_extent_fix_parents(handle);\n+\tDUMP_EXTENT(ff, \"fixreplacel\", startoff - 1, err, &left);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\treturn 0;\n+}\n+\n+static int fuse2fs_convert_unwritten_mapping(struct fuse2fs *ff,\n+\t\t\t\t\t ext2_ino_t ino,\n+\t\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t\t blk64_t *cursor, blk64_t stopoff)\n+{\n+\text2_filsys fs = ff->fs;\n+\tstruct ext2fs_extent extent;\n+\tblk64_t startoff = *cursor;\n+\terrcode_t err;\n+\n+\t/*\n+\t * Find the mapping at startoff. Note that we can find holes because\n+\t * the mapping data can change due to racing writes.\n+\t */\n+\terr = fuse2fs_get_mapping_at(ff, handle, startoff, &extent);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND) {\n+\t\t/*\n+\t\t * If we didn't find any mappings at all then the file is\n+\t\t * completely sparse. There's nothing to convert.\n+\t\t */\n+\t\t*cursor = stopoff;\n+\t\treturn 0;\n+\t}\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/*\n+\t * The mapping is completely to the left of the range that we want.\n+\t * Let's see what's in the next extent, if there is one.\n+\t */\n+\tif (startoff >= extent.e_lblk + extent.e_len) {\n+\t\t/*\n+\t\t * Mapping ends to the left of the current position. Try to\n+\t\t * find the next mapping. If there is no next mapping, then\n+\t\t * we're done.\n+\t\t */\n+\t\terr = fuse2fs_get_next_mapping(ff, handle, startoff, &extent);\n+\t\tif (err == EXT2_ET_EXTENT_NOT_FOUND) {\n+\t\t\t*cursor = stopoff;\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t/*\n+\t * The mapping is completely to the right of the range that we want,\n+\t * so we're done.\n+\t */\n+\tif (extent.e_lblk >= stopoff) {\n+\t\t*cursor = stopoff;\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * At this point, we have a mapping that overlaps (startoff, stopoff].\n+\t * If the mapping is already written, move on to the next one.\n+\t */\n+\tif (!(extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT))\n+\t\tgoto next;\n+\n+\tif (startoff > extent.e_lblk) {\n+\t\tstruct ext2fs_extent newex = extent;\n+\n+\t\t/*\n+\t\t * Unwritten mapping starts before startoff. Shorten\n+\t\t * the previous mapping...\n+\t\t */\n+\t\tnewex.e_len = startoff - extent.e_lblk;\n+\t\terr = ext2fs_extent_replace(handle, 0, &newex);\n+\t\tDUMP_EXTENT(ff, \"shortenp\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixshortenp\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\t/* ...and create new written mapping at startoff. */\n+\t\textent.e_len -= newex.e_len;\n+\t\textent.e_lblk += newex.e_len;\n+\t\textent.e_pblk += newex.e_len;\n+\t\textent.e_flags = newex.e_flags & ~EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_insert(handle,\n+\t\t\t\t\t EXT2_EXTENT_INSERT_AFTER,\n+\t\t\t\t\t &extent);\n+\t\tDUMP_EXTENT(ff, \"insertx\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixinsertx\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\tif (extent.e_lblk + extent.e_len > stopoff) {\n+\t\tstruct ext2fs_extent newex = extent;\n+\n+\t\t/*\n+\t\t * Unwritten mapping ends after stopoff. Shorten the current\n+\t\t * mapping...\n+\t\t */\n+\t\textent.e_len = stopoff - extent.e_lblk;\n+\t\textent.e_flags &= ~EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_replace(handle, 0, &extent);\n+\t\tDUMP_EXTENT(ff, \"shortenn\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixshortenn\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\t/* ..and create a new unwritten mapping at stopoff. */\n+\t\tnewex.e_pblk += extent.e_len;\n+\t\tnewex.e_lblk += extent.e_len;\n+\t\tnewex.e_len -= extent.e_len;\n+\t\tnewex.e_flags |= EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_insert(handle,\n+\t\t\t\t\t EXT2_EXTENT_INSERT_AFTER,\n+\t\t\t\t\t &newex);\n+\t\tDUMP_EXTENT(ff, \"insertn\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixinsertn\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t/* Still unwritten? Update the state. */\n+\tif (extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT) {\n+\t\textent.e_flags &= ~EXT2_EXTENT_FLAGS_UNINIT;\n+\n+\t\terr = ext2fs_extent_replace(handle, 0, &extent);\n+\t\tDUMP_EXTENT(ff, \"replacex\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\n+\t\terr = ext2fs_extent_fix_parents(handle);\n+\t\tDUMP_EXTENT(ff, \"fixreplacex\", startoff, err, &extent);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+next:\n+\t/* Try to merge with the previous extent */\n+\tif (startoff > 0) {\n+\t\terr = fuse2fs_try_merge_mappings(ff, ino, handle, startoff);\n+\t\tif (err)\n+\t\t\treturn translate_error(fs, ino, err);\n+\t}\n+\n+\t*cursor = extent.e_lblk + extent.e_len;\n+\treturn 0;\n+}\n+\n+static int fuse2fs_convert_unwritten_mappings(struct fuse2fs *ff,\n+\t\t\t\t\t ext2_ino_t ino,\n+\t\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t\t off_t pos, size_t written)\n+{\n+\text2_extent_handle_t handle;\n+\text2_filsys fs = ff->fs;\n+\tblk64_t startoff = FUSE2FS_B_TO_FSBT(ff, pos);\n+\tconst blk64_t stopoff = FUSE2FS_B_TO_FSB(ff, pos + written);\n+\terrcode_t err;\n+\tint ret;\n+\n+\terr = ext2fs_extent_open2(fs, ino, EXT2_INODE(inode), &handle);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\t/* Walk every mapping in the range, converting them. */\n+\twhile (startoff < stopoff) {\n+\t\tblk64_t old_startoff = startoff;\n+\n+\t\tret = fuse2fs_convert_unwritten_mapping(ff, ino, inode, handle,\n+\t\t\t\t\t\t\t&startoff, stopoff);\n+\t\tif (ret)\n+\t\t\tgoto out_handle;\n+\t\tif (startoff <= old_startoff) {\n+\t\t\t/* Do not go backwards. */\n+\t\t\tret = translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);\n+\t\t\tgoto out_handle;\n+\t\t}\n+\t}\n+\n+\t/* Try to merge the right edge */\n+\tret = fuse2fs_try_merge_mappings(ff, ino, handle, stopoff);\n+out_handle:\n+\text2fs_extent_free(handle);\n+\treturn ret;\n+}\n+\n+static int op_iomap_ioend(const char *path, uint64_t nodeid, uint64_t attr_ino,\n+\t\t\t off_t pos, size_t written, uint32_t ioendflags,\n+\t\t\t int error, uint32_t dev, uint64_t new_addr,\n+\t\t\t off_t *newsize)\n+{\n+\tstruct fuse2fs *ff = fuse2fs_get();\n+\tstruct ext2_inode_large inode;\n+\text2_filsys fs;\n+\terrcode_t err;\n+\text2_off64_t isize;\n+\tbool dirty = false;\n+\tint ret = 0;\n+\n+\tFUSE2FS_CHECK_CONTEXT(ff);\n+\n+\tdbg_printf(ff,\n+ \"%s: path=%s nodeid=%llu attr_ino=%llu pos=0x%llx written=0x%zx ioendflags=0x%x error=%d dev=%u new_addr=%llu\\n\",\n+\t\t __func__, path,\n+\t\t (unsigned long long)nodeid,\n+\t\t (unsigned long long)attr_ino,\n+\t\t (unsigned long long)pos,\n+\t\t written,\n+\t\t ioendflags,\n+\t\t error,\n+\t\t dev,\n+\t\t (unsigned long long)new_addr);\n+\n+\tfs = fuse2fs_start(ff);\n+\tif (error) {\n+\t\tret = error;\n+\t\tgoto out_unlock;\n+\t}\n+\n+\t/* should never see these ioend types */\n+\tif (ioendflags & FUSE_IOMAP_IOEND_SHARED) {\n+\t\tret = translate_error(fs, attr_ino,\n+\t\t\t\t EXT2_ET_FILESYSTEM_CORRUPTED);\n+\t\tgoto out_unlock;\n+\t}\n+\n+\terr = fuse2fs_read_inode(fs, attr_ino, &inode);\n+\tif (err) {\n+\t\tret = translate_error(fs, attr_ino, err);\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tif (ioendflags & FUSE_IOMAP_IOEND_UNWRITTEN) {\n+\t\t/* unwritten extents are only supported on extents files */\n+\t\tif (!(inode.i_flags & EXT4_EXTENTS_FL)) {\n+\t\t\tret = translate_error(fs, attr_ino,\n+\t\t\t\t\t EXT2_ET_FILESYSTEM_CORRUPTED);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\n+\t\tret = fuse2fs_convert_unwritten_mappings(ff, attr_ino, &inode,\n+\t\t\t\t\t\t\t pos, written);\n+\t\tif (ret)\n+\t\t\tgoto out_unlock;\n+\n+\t\tdirty = true;\n+\t}\n+\n+\tisize = EXT2_I_SIZE(&inode);\n+\tif (pos + written > isize) {\n+\t\terr = ext2fs_inode_size_set(fs, EXT2_INODE(&inode),\n+\t\t\t\t\t pos + written);\n+\t\tif (err) {\n+\t\t\tret = translate_error(fs, attr_ino, err);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\n+\t\tdirty = true;\n+\t}\n+\n+\tif (dirty) {\n+\t\terr = fuse2fs_write_inode(fs, attr_ino, &inode);\n+\t\tif (err) {\n+\t\t\tret = translate_error(fs, attr_ino, err);\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\t}\n+\n+\t*newsize = EXT2_I_SIZE(&inode);\n+out_unlock:\n+\tfuse2fs_finish(ff, ret);\n+\treturn ret;\n+}\n #endif /* HAVE_FUSE_IOMAP */\n \n static struct fuse_operations fs_ops = {\n@@ -5943,6 +6407,7 @@ static struct fuse_operations fs_ops = {\n \t.iomap_begin = op_iomap_begin,\n \t.iomap_end = op_iomap_end,\n \t.iomap_config = op_iomap_config,\n+\t.iomap_ioend = op_iomap_ioend,\n #endif /* HAVE_FUSE_IOMAP */\n };\n \n", "prefixes": [ "07/19" ] }