Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2230310/?format=api
{ "id": 2230310, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2230310/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-ext4/patch/177747214553.4107473.14649364719289463803.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": "<177747214553.4107473.14649364719289463803.stgit@frogsfrogsfrogs>", "date": "2026-04-29T14:52:53", "name": "[01/19] fuse2fs: implement bare minimum iomap for file mapping reporting", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "9d88b6a915e1e5368c5aa39099b34744ad483244", "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/177747214553.4107473.14649364719289463803.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/2230310/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2230310/checks/", "tags": {}, "headers": { "Return-Path": "\n <SRS0=7kA0=C4=vger.kernel.org=linux-ext4+bounces-16190-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=deBT9WNk;\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=7ka0=c4=vger.kernel.org=linux-ext4+bounces-16190-patchwork-incoming=ozlabs.org@ozlabs.org;\n receiver=patchwork.ozlabs.org)", "gandalf.ozlabs.org;\n arc=pass smtp.remote-ip=\"2600:3c0a:e001:db::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=deBT9WNk;\n\tdkim-atps=neutral", "gandalf.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c0a:e001:db::12fc:5321; helo=sea.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16190-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=\"deBT9WNk\"", "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 4g5L574PqDz1yHZ\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 30 Apr 2026 00:57:47 +1000 (AEST)", "from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3])\n\tby gandalf.ozlabs.org (Postfix) with ESMTP id 4g5L573v9cz4wLR\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 30 Apr 2026 00:57:47 +1000 (AEST)", "by gandalf.ozlabs.org (Postfix)\n\tid 4g5L573ptDz4wM1; Thu, 30 Apr 2026 00:57:47 +1000 (AEST)", "from sea.lore.kernel.org (sea.lore.kernel.org\n [IPv6:2600:3c0a:e001:db::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 4g5L535bTZz4wLR\n\tfor <patchwork-incoming@ozlabs.org>; Thu, 30 Apr 2026 00:57:43 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id 3437430CD2BE\n\tfor <patchwork-incoming@ozlabs.org>; Wed, 29 Apr 2026 14:52:57 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id A50593630B3;\n\tWed, 29 Apr 2026 14:52:55 +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 8F28F33B6DA;\n\tWed, 29 Apr 2026 14:52:54 +0000 (UTC)", "by smtp.kernel.org (Postfix) with ESMTPSA id D1887C19425;\n\tWed, 29 Apr 2026 14:52:53 +0000 (UTC)" ], "ARC-Seal": [ "i=2; a=rsa-sha256; d=ozlabs.org; s=201707; t=1777474667; cv=pass;\n\tb=SVLjYORBKQ7Ysdv21gxG9Q7AmSZt4oP4eOgAqwZ1Hbyhgz3Qv20yXvqG7SI/1dN06w/7fKnR9pgs0QTbrpWjyh63SHMchXUA6U+EVO+RxIGhcY7D4l38+3zeaYaOduAXKDSgNesTxGql7I1JKRFA0Oti7zHsEUz8WqPt11ByDOix2k/qnD08I88R7c6lrCfNHzfHrZH1vT84vRj5RrgepMJmtzpuRVF8ds5fY3xbgN5kSLTpkUhfoSWbjyyYSf9BlgDFhEzsZ/0ifWqFP6ek0q5K7UhsHZD3db/MOeCmpaOfw4eoSjpDQFfZSXqLGEJ3vM4e+JuLyaC2AGA5fh/WIQ==", "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777474374; cv=none;\n b=RzkbDEp2o4yU32UXzD3mxO90jBg5LYdkEdzmBLwuACrTwdK6UCeKgvUq0ZsD+0q/9h9emd5EGee6UzW3l5HNm9upEnKNn3Ym3xsfbTsK5IrhKq5pmKYyc8cuiwyecyvhf8z8vbgxBbWFqQRTPAXpLl/rGOsVD69++2V72enF2DI=" ], "ARC-Message-Signature": [ "i=2; a=rsa-sha256; d=ozlabs.org; s=201707;\n\tt=1777474667; c=relaxed/relaxed;\n\tbh=JogJdq51ag+VfY0puRk/h7AThNgEzfEhpQZTbvVfDeI=;\n\th=Date:Subject:From:To:Cc:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=JX1lK6wlSS2hhNqj13h1ihiBMHjCts9A4v8kaw2RtXnSj0/ZFj6n/18onaj57uVjhdf1y9xTFLzdDWwq9x9Zh/JaL7oNQwNO7azFAezw8lOGU370gRa+RZX2pTCleiCV2HDoWIIqCDJsTzqedShrIbPPg+/ctxNq3oQte2oJ6KsFu8lvkSTK2Zx4xXKfU4orOTtJOJbpaB//BtY+MaNvrbZzHd6d3v74RiT7rhqQ0Ru9Fxd4ef4AoJOyrXizR89MyIsu2AGeEY5bPRsqp9cZXCvorbk15XKBTbyLFItgHT5fjH+I/duGbwjxmj3r9S4Z3MQDkUjMbraZPdEV2Rbncg==", "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777474374; c=relaxed/simple;\n\tbh=ZjGK99nqYDP89lTKz6Kx5DKGSQk4fxQWhyXcUS+LYPo=;\n\th=Date:Subject:From:To:Cc:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=pxxAh6MWQjPy9H/FkPEVvuBr8TDOU+PAqgLZG+2ZlR5SmxnpxShDGha34nhj0ZX1DzboWoL9wqHlmLUfl/RVyqcsCiEpbf82EvQK0F3+HkTebtiS4yS0qhpav92GFBugCZqMWm7Av1eUsu2htkQZxNKDO7RX7l3DyncCx6D8PF4=" ], "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=deBT9WNk; dkim-atps=neutral;\n spf=pass (client-ip=2600:3c0a:e001:db::12fc:5321; helo=sea.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16190-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=deBT9WNk; 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=1777474373;\n\tbh=ZjGK99nqYDP89lTKz6Kx5DKGSQk4fxQWhyXcUS+LYPo=;\n\th=Date:Subject:From:To:Cc:In-Reply-To:References:From;\n\tb=deBT9WNkSlKM8CP6zY5ndQEfJP+MZ76jvHZ8qNfqDBurZ/6uIjpWShxCEOT3899w6\n\t 7xCYnzhyLN3Ezt992OltUJTuzQp3xHjIs8vZ0xQozJU8cMS3ZQajhq76j7AgmEfnFg\n\t cghYy+HwuRJGYyBkJTiO2hnJjQw+gTRLNdYiSl16Btt0RVMojSeMjNWiGiiy/m+D6B\n\t I3TpIaGgj1Or23OroEyoK1tTmuW0vmQqX4+YWZUUFTflVlFvkkJvfHGzrYVBhxKsxf\n\t +mYRXrO0MmX5dyZRDb418hEfFj+JCi3cwGCpLUMZ2NT/HhXKiMuJAhg1+X7+1fJyCY\n\t Yzo+NqDjlPA0A==", "Date": "Wed, 29 Apr 2026 07:52:53 -0700", "Subject": "[PATCH 01/19] fuse2fs: implement bare minimum iomap for file mapping\n reporting", "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": "<177747214553.4107473.14649364719289463803.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\nAdd enough of an iomap implementation that we can do FIEMAP and\nSEEK_DATA and SEEK_HOLE.\n\nSigned-off-by: \"Darrick J. Wong\" <djwong@kernel.org>\n---\n configure | 49 +++++\n configure.ac | 31 +++\n fuse4fs/fuse4fs.c | 540 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n lib/config.h.in | 3 \n misc/fuse2fs.c | 540 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 1161 insertions(+), 2 deletions(-)", "diff": "diff --git a/configure b/configure\nindex 80aad505da550c..344c7af2ee48f8 100755\n--- a/configure\n+++ b/configure\n@@ -14608,6 +14608,7 @@ printf \"%s\\n\" \"yes\" >&6; }\n fi\n \n \n+have_fuse_iomap=\n if test -n \"$FUSE_LIB\"\n then\n \tFUSE_USE_VERSION=319\n@@ -14634,12 +14635,60 @@ esac\n fi\n \n done\n+\n+\t\t\t\t\t{ printf \"%s\\n\" \"$as_me:${as_lineno-$LINENO}: checking for iomap_begin in libfuse\" >&5\n+printf %s \"checking for iomap_begin in libfuse... \" >&6; }\n+\tcat confdefs.h - <<_ACEOF >conftest.$ac_ext\n+/* end confdefs.h. */\n+\n+\t#define _GNU_SOURCE\n+\t#define _FILE_OFFSET_BITS\t64\n+\t#define FUSE_USE_VERSION\t399\n+\t#include <fuse.h>\n+\n+int\n+main (void)\n+{\n+\n+\tstruct fuse_operations fs_ops = {\n+\t\t.iomap_begin = NULL,\n+\t\t.iomap_end = NULL,\n+\t};\n+\tstruct fuse_file_iomap narf = { };\n+\n+ ;\n+ return 0;\n+}\n+\n+_ACEOF\n+if ac_fn_c_try_link \"$LINENO\"\n+then :\n+ have_fuse_iomap=yes\n+\t { printf \"%s\\n\" \"$as_me:${as_lineno-$LINENO}: result: yes\" >&5\n+printf \"%s\\n\" \"yes\" >&6; }\n+else case e in #(\n+ e) { printf \"%s\\n\" \"$as_me:${as_lineno-$LINENO}: result: no\" >&5\n+printf \"%s\\n\" \"no\" >&6; } ;;\n+esac\n+fi\n+rm -f core conftest.err conftest.$ac_objext conftest.beam \\\n+ conftest$ac_exeext conftest.$ac_ext\n+\tif test \"$have_fuse_iomap\" = yes\n+\tthen\n+\t\tFUSE_USE_VERSION=399\n+\tfi\n fi\n if test -n \"$FUSE_USE_VERSION\"\n then\n \n printf \"%s\\n\" \"#define FUSE_USE_VERSION $FUSE_USE_VERSION\" >>confdefs.h\n \n+fi\n+if test -n \"$have_fuse_iomap\"\n+then\n+\n+printf \"%s\\n\" \"#define HAVE_FUSE_IOMAP 1\" >>confdefs.h\n+\n fi\n \n have_fuse_lowlevel=\ndiff --git a/configure.ac b/configure.ac\nindex 63a5cd697a6dde..8d85e9966877ea 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -1385,6 +1385,7 @@ dnl\n dnl Set FUSE_USE_VERSION, which is how fuse servers build against a particular\n dnl libfuse ABI. Currently we link against the libfuse 3.19 ABI (hence 319)\n dnl\n+have_fuse_iomap=\n if test -n \"$FUSE_LIB\"\n then\n \tFUSE_USE_VERSION=319\n@@ -1394,12 +1395,42 @@ then\n \t\t[AC_MSG_FAILURE([Cannot build against fuse3 headers])],\n [#define _FILE_OFFSET_BITS\t64\n #define FUSE_USE_VERSION\t319])\n+\n+\tdnl\n+\tdnl Check if the fuse library supports iomap, which requires a higher\n+\tdnl FUSE_USE_VERSION ABI version (3.99)\n+\tdnl\n+\tAC_MSG_CHECKING(for iomap_begin in libfuse)\n+\tAC_LINK_IFELSE(\n+\t[\tAC_LANG_PROGRAM([[\n+\t#define _GNU_SOURCE\n+\t#define _FILE_OFFSET_BITS\t64\n+\t#define FUSE_USE_VERSION\t399\n+\t#include <fuse.h>\n+\t\t]], [[\n+\tstruct fuse_operations fs_ops = {\n+\t\t.iomap_begin = NULL,\n+\t\t.iomap_end = NULL,\n+\t};\n+\tstruct fuse_file_iomap narf = { };\n+\t\t]])\n+\t], have_fuse_iomap=yes\n+\t AC_MSG_RESULT(yes),\n+\t AC_MSG_RESULT(no))\n+\tif test \"$have_fuse_iomap\" = yes\n+\tthen\n+\t\tFUSE_USE_VERSION=399\n+\tfi\n fi\n if test -n \"$FUSE_USE_VERSION\"\n then\n \tAC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,\n \t\t[Define to the version of FUSE to use])\n fi\n+if test -n \"$have_fuse_iomap\"\n+then\n+\tAC_DEFINE(HAVE_FUSE_IOMAP, 1, [Define to 1 if fuse supports iomap])\n+fi\n \n dnl\n dnl Check if the FUSE lowlevel library is supported\ndiff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c\nindex dc5a0ede9f5072..a159024f778ba2 100644\n--- a/fuse4fs/fuse4fs.c\n+++ b/fuse4fs/fuse4fs.c\n@@ -155,6 +155,9 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)\n \treturn b - m;\n }\n \n+#define max(a, b)\t((a) > (b) ? (a) : (b))\n+#define min(a, b)\t((a) < (b) ? (a) : (b))\n+\n #define dbg_printf(fuse4fs, format, ...) \\\n \twhile ((fuse4fs)->debug) { \\\n \t\tprintf(\"FUSE4FS (%s): tid=%llu \" format, (fuse4fs)->shortdev, get_thread_id(), ##__VA_ARGS__); \\\n@@ -233,6 +236,14 @@ enum fuse4fs_opstate {\n \tF4OP_SHUTDOWN,\n };\n \n+#ifdef HAVE_FUSE_IOMAP\n+enum fuse4fs_iomap_state {\n+\tIOMAP_DISABLED,\n+\tIOMAP_UNKNOWN,\n+\tIOMAP_ENABLED,\n+};\n+#endif\n+\n /* Main program context */\n #define FUSE4FS_MAGIC\t\t(0xEF53DEADUL)\n struct fuse4fs {\n@@ -260,6 +271,9 @@ struct fuse4fs {\n \tint logfd;\n \tint blocklog;\n \tint oom_score_adj;\n+#ifdef HAVE_FUSE_IOMAP\n+\tenum fuse4fs_iomap_state iomap_state;\n+#endif\n \tunsigned int blockmask;\n \tunsigned long offset;\n \tunsigned int next_generation;\n@@ -882,6 +896,15 @@ fuse4fs_set_handle(struct fuse_file_info *fp, struct fuse4fs_file_handle *fh)\n \tfp->keep_cache = 1;\n }\n \n+#ifdef HAVE_FUSE_IOMAP\n+static inline int fuse4fs_iomap_enabled(const struct fuse4fs *ff)\n+{\n+\treturn ff->iomap_state >= IOMAP_ENABLED;\n+}\n+#else\n+# define fuse4fs_iomap_enabled(...)\t(0)\n+#endif\n+\n static void get_now(struct timespec *now)\n {\n #ifdef CLOCK_REALTIME\n@@ -1514,7 +1537,7 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff)\n \tchar options[128];\n \tdouble deadline;\n \tint flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_RW |\n-\t\t EXT2_FLAG_EXCLUSIVE;\n+\t\t EXT2_FLAG_EXCLUSIVE | EXT2_FLAG_WRITE_FULL_SUPER;\n \terrcode_t err;\n \n \tif (ff->lockfile) {\n@@ -1808,6 +1831,15 @@ static void op_destroy(void *userdata)\n \t\t\t\t(stats->cache_hits + stats->cache_misses));\n \t}\n \n+\t/*\n+\t * If we're mounting in iomap mode, we need to unmount in op_destroy so\n+\t * that the block device will be released before umount(2) returns.\n+\t */\n+\tif (ff->iomap_state == IOMAP_ENABLED) {\n+\t\tfuse4fs_mmp_cancel(ff);\n+\t\tfuse4fs_unmount(ff);\n+\t}\n+\n \tfuse4fs_finish(ff, 0);\n }\n \n@@ -1948,6 +1980,44 @@ static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,\n }\n #endif\n \n+#ifdef HAVE_FUSE_IOMAP\n+static inline bool fuse4fs_wants_iomap(struct fuse4fs *ff)\n+{\n+\tif (ff->iomap_state == IOMAP_DISABLED)\n+\t\treturn false;\n+\n+\t/* iomap only works with block devices */\n+\tif (!(ff->fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE))\n+\t\treturn false;\n+\n+\t/*\n+\t * iomap addrs must be aligned to the bdev lba size; we use fs\n+\t * blocksize as a proxy here\n+\t */\n+\tif (ff->offset % ff->fs->blocksize > 0)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static void fuse4fs_iomap_enable(struct fuse_conn_info *conn,\n+\t\t\t\t struct fuse4fs *ff)\n+{\n+\t/* Don't let anyone touch iomap until the end of the patchset. */\n+\tff->iomap_state = IOMAP_DISABLED;\n+\treturn;\n+\n+\tif (fuse4fs_wants_iomap(ff) &&\n+\t fuse_set_feature_flag(conn, FUSE_CAP_IOMAP))\n+\t\tff->iomap_state = IOMAP_ENABLED;\n+\n+\tif (ff->iomap_state == IOMAP_UNKNOWN)\n+\t\tff->iomap_state = IOMAP_DISABLED;\n+}\n+#else\n+# define fuse4fs_iomap_enable(...)\t((void)0)\n+#endif\n+\n static void op_init(void *userdata, struct fuse_conn_info *conn)\n {\n \tstruct fuse4fs *ff = userdata;\n@@ -1970,6 +2040,7 @@ static void op_init(void *userdata, struct fuse_conn_info *conn)\n #ifdef FUSE_CAP_NO_EXPORT_SUPPORT\n \tfuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);\n #endif\n+\tfuse4fs_iomap_enable(conn, ff);\n \tconn->time_gran = 1;\n \n \tif (ff->opstate == F4OP_WRITABLE)\n@@ -5917,6 +5988,466 @@ static void op_fallocate(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),\n }\n #endif /* SUPPORT_FALLOCATE */\n \n+#ifdef HAVE_FUSE_IOMAP\n+static void fuse4fs_iomap_hole(struct fuse4fs *ff, struct fuse_file_iomap *iomap,\n+\t\t\t off_t pos, uint64_t count)\n+{\n+\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\tiomap->addr = FUSE_IOMAP_NULL_ADDR;\n+\tiomap->offset = pos;\n+\tiomap->length = count;\n+\tiomap->type = FUSE_IOMAP_TYPE_HOLE;\n+}\n+\n+static void fuse4fs_iomap_hole_to_eof(struct fuse4fs *ff,\n+\t\t\t\t struct fuse_file_iomap *iomap, off_t pos,\n+\t\t\t\t off_t count,\n+\t\t\t\t const struct ext2_inode_large *inode)\n+{\n+\text2_filsys fs = ff->fs;\n+\tuint64_t isize = EXT2_I_SIZE(inode);\n+\n+\t/*\n+\t * We have to be careful about handling a hole to the right of the\n+\t * entire mapping tree. First, the mapping must start and end on a\n+\t * block boundary because they must be aligned to at least an LBA for\n+\t * the block layer; and to the fsblock for smoother operation.\n+\t *\n+\t * As for the length -- we could return a mapping all the way to\n+\t * i_size, but i_size could be less than pos/count if we're zeroing the\n+\t * EOF block in anticipation of a truncate operation. Similarly, we\n+\t * don't want to end the mapping at pos+count because we know there's\n+\t * nothing mapped beyond here.\n+\t */\n+\tuint64_t startoff = round_down(pos, fs->blocksize);\n+\tuint64_t eofoff = round_up(max(pos + count, isize), fs->blocksize);\n+\n+\tdbg_printf(ff,\n+ \"pos=0x%llx count=0x%llx isize=0x%llx startoff=0x%llx eofoff=0x%llx\\n\",\n+\t\t (unsigned long long)pos,\n+\t\t (unsigned long long)count,\n+\t\t (unsigned long long)isize,\n+\t\t (unsigned long long)startoff,\n+\t\t (unsigned long long)eofoff);\n+\n+\tfuse4fs_iomap_hole(ff, iomap, startoff, eofoff - startoff);\n+}\n+\n+#define DEBUG_IOMAP\n+#ifdef DEBUG_IOMAP\n+# define __DUMP_EXTENT(ff, func, tag, startoff, err, extent) \\\n+\tdo { \\\n+\t\tdbg_printf((ff), \\\n+ \"%s: %s startoff 0x%llx err %ld lblk 0x%llx pblk 0x%llx len 0x%x flags 0x%x\\n\", \\\n+\t\t\t (func), (tag), (startoff), (err), (extent)->e_lblk, \\\n+\t\t\t (extent)->e_pblk, (extent)->e_len, \\\n+\t\t\t (extent)->e_flags & EXT2_EXTENT_FLAGS_UNINIT); \\\n+\t} while(0)\n+# define DUMP_EXTENT(ff, tag, startoff, err, extent) \\\n+\t__DUMP_EXTENT((ff), __func__, (tag), (startoff), (err), (extent))\n+\n+# define __DUMP_INFO(ff, func, tag, startoff, err, info) \\\n+\tdo { \\\n+\t\tdbg_printf((ff), \\\n+ \"%s: %s startoff 0x%llx err %ld entry %d/%d/%d level %d/%d\\n\", \\\n+\t\t\t (func), (tag), (startoff), (err), \\\n+\t\t\t (info)->curr_entry, (info)->num_entries, \\\n+\t\t\t (info)->max_entries, (info)->curr_level, \\\n+\t\t\t (info)->max_depth); \\\n+\t} while(0)\n+# define DUMP_INFO(ff, tag, startoff, err, info) \\\n+\t__DUMP_INFO((ff), __func__, (tag), (startoff), (err), (info))\n+#else\n+# define __DUMP_EXTENT(...)\t((void)0)\n+# define DUMP_EXTENT(...)\t((void)0)\n+# define DUMP_INFO(...)\t\t((void)0)\n+#endif\n+\n+static inline errcode_t __fuse4fs_get_mapping_at(struct fuse4fs *ff,\n+\t\t\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t\t\t blk64_t startoff,\n+\t\t\t\t\t\t struct ext2fs_extent *bmap,\n+\t\t\t\t\t\t const char *func)\n+{\n+\terrcode_t err;\n+\n+\t/*\n+\t * Find the file mapping at startoff. We don't check the return value\n+\t * of _goto because _get will error out if _goto failed. There's a\n+\t * subtlety to the outcome of _goto when startoff falls in a sparse\n+\t * hole however:\n+\t *\n+\t * Most of the time, _goto points the cursor at the mapping whose lblk\n+\t * is just to the left of startoff. The mapping may or may not overlap\n+\t * startoff; this is ok. In other words, the tree lookup behaves as if\n+\t * we asked it to use a less than or equals comparison.\n+\t *\n+\t * However, if startoff is to the left of the first mapping in the\n+\t * extent tree, _goto points the cursor at that first mapping because\n+\t * it doesn't know how to deal with this situation. In this case,\n+\t * the tree lookup behaves as if we asked it to use a greater than\n+\t * or equals comparison.\n+\t *\n+\t * Note: If _get() returns 'no current node', that means that there\n+\t * aren't any mappings at all.\n+\t */\n+\text2fs_extent_goto(handle, startoff);\n+\terr = ext2fs_extent_get(handle, EXT2_EXTENT_CURRENT, bmap);\n+\t__DUMP_EXTENT(ff, func, \"lookup\", startoff, err, bmap);\n+\tif (err == EXT2_ET_NO_CURRENT_NODE)\n+\t\terr = EXT2_ET_EXTENT_NOT_FOUND;\n+\treturn err;\n+}\n+\n+static inline errcode_t __fuse4fs_get_next_mapping(struct fuse4fs *ff,\n+\t\t\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t\t\t blk64_t startoff,\n+\t\t\t\t\t\t struct ext2fs_extent *bmap,\n+\t\t\t\t\t\t const char *func)\n+{\n+\tstruct ext2fs_extent newex;\n+\tstruct ext2_extent_info info;\n+\terrcode_t err;\n+\n+\t/*\n+\t * The extent tree code has this (probably broken) behavior that if\n+\t * more than two of the highest levels of the cursor point at the\n+\t * rightmost edge of an extent tree block, a _NEXT_LEAF movement fails\n+\t * to move the cursor position of any of the lower levels. IOWs, if\n+\t * leaf level N is at the right edge, it will only advance level N-1\n+\t * to the right. If N-1 was at the right edge, the cursor resets to\n+\t * record 0 of that level and goes down to the wrong leaf.\n+\t *\n+\t * Work around this by walking up (towards root level 0) the extent\n+\t * tree until we find a level where we're not already at the rightmost\n+\t * edge. The _NEXT_LEAF movement will walk down the tree to find the\n+\t * leaves.\n+\t */\n+\terr = ext2fs_extent_get_info(handle, &info);\n+\tDUMP_INFO(ff, \"UP?\", startoff, err, &info);\n+\tif (err)\n+\t\treturn err;\n+\n+\twhile (info.curr_entry == info.num_entries && info.curr_level > 0) {\n+\t\terr = ext2fs_extent_get(handle, EXT2_EXTENT_UP, &newex);\n+\t\tDUMP_EXTENT(ff, \"UP\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t\terr = ext2fs_extent_get_info(handle, &info);\n+\t\tDUMP_INFO(ff, \"UP\", startoff, err, &info);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\t/*\n+\t * If we're at the root and there are no more entries, there's nothing\n+\t * else to be found.\n+\t */\n+\tif (info.curr_level == 0 && info.curr_entry == info.num_entries)\n+\t\treturn EXT2_ET_EXTENT_NOT_FOUND;\n+\n+\t/* Otherwise grab this next leaf and return it. */\n+\terr = ext2fs_extent_get(handle, EXT2_EXTENT_NEXT_LEAF, &newex);\n+\tDUMP_EXTENT(ff, \"NEXT\", startoff, err, &newex);\n+\tif (err)\n+\t\treturn err;\n+\n+\t*bmap = newex;\n+\treturn 0;\n+}\n+\n+#define fuse4fs_get_mapping_at(ff, handle, startoff, bmap) \\\n+\t__fuse4fs_get_mapping_at((ff), (handle), (startoff), (bmap), __func__)\n+#define fuse4fs_get_next_mapping(ff, handle, startoff, bmap) \\\n+\t__fuse4fs_get_next_mapping((ff), (handle), (startoff), (bmap), __func__)\n+\n+static errcode_t fuse4fs_iomap_begin_extent(struct fuse4fs *ff, uint64_t ino,\n+\t\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t\t off_t pos, uint64_t count,\n+\t\t\t\t\t uint32_t opflags,\n+\t\t\t\t\t struct fuse_file_iomap *iomap)\n+{\n+\text2_extent_handle_t handle;\n+\tstruct ext2fs_extent extent = { };\n+\text2_filsys fs = ff->fs;\n+\tconst blk64_t startoff = FUSE4FS_B_TO_FSBT(ff, pos);\n+\terrcode_t err;\n+\tint ret = 0;\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+\terr = fuse4fs_get_mapping_at(ff, handle, startoff, &extent);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND) {\n+\t\t/* No mappings at all; the whole range is a hole. */\n+\t\tfuse4fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\t\tgoto out_handle;\n+\t}\n+\tif (err) {\n+\t\tret = translate_error(fs, ino, err);\n+\t\tgoto out_handle;\n+\t}\n+\n+\tif (startoff < extent.e_lblk) {\n+\t\t/*\n+\t\t * Mapping starts to the right of the current position.\n+\t\t * Synthesize a hole going to that next extent.\n+\t\t */\n+\t\tfuse4fs_iomap_hole(ff, iomap, FUSE4FS_FSB_TO_B(ff, startoff),\n+\t\t\t\tFUSE4FS_FSB_TO_B(ff, extent.e_lblk - startoff));\n+\t\tgoto out_handle;\n+\t}\n+\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, the\n+\t\t * whole range is in a hole.\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\tfuse4fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\t\t\tgoto out_handle;\n+\t\t}\n+\n+\t\t/*\n+\t\t * If the new mapping starts to the right of startoff, there's\n+\t\t * a hole from startoff to the start of the new mapping.\n+\t\t */\n+\t\tif (startoff < extent.e_lblk) {\n+\t\t\tfuse4fs_iomap_hole(ff, iomap,\n+\t\t\t\tFUSE4FS_FSB_TO_B(ff, startoff),\n+\t\t\t\tFUSE4FS_FSB_TO_B(ff, extent.e_lblk - startoff));\n+\t\t\tgoto out_handle;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The new mapping starts at startoff. Something weird\n+\t\t * happened in the extent tree lookup, but we found a valid\n+\t\t * mapping so we'll run with it.\n+\t\t */\n+\t}\n+\n+\t/* Mapping overlaps startoff, report this. */\n+\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\tiomap->addr = FUSE4FS_FSB_TO_B(ff, extent.e_pblk) + ff->offset;\n+\tiomap->offset = FUSE4FS_FSB_TO_B(ff, extent.e_lblk);\n+\tiomap->length = FUSE4FS_FSB_TO_B(ff, extent.e_len);\n+\tif (extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT)\n+\t\tiomap->type = FUSE_IOMAP_TYPE_UNWRITTEN;\n+\telse\n+\t\tiomap->type = FUSE_IOMAP_TYPE_MAPPED;\n+\n+out_handle:\n+\text2fs_extent_free(handle);\n+\treturn ret;\n+}\n+\n+static int fuse4fs_iomap_begin_indirect(struct fuse4fs *ff, uint64_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 *iomap)\n+{\n+\text2_filsys fs = ff->fs;\n+\tblk64_t startoff = FUSE4FS_B_TO_FSBT(ff, pos);\n+\tuint64_t isize = EXT2_I_SIZE(inode);\n+\tuint64_t real_count = min(count, 131072);\n+\tconst blk64_t endoff = FUSE4FS_B_TO_FSB(ff, pos + real_count);\n+\tblk64_t startblock;\n+\terrcode_t err;\n+\n+\terr = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), NULL, 0, startoff, NULL,\n+\t\t\t &startblock);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\tiomap->offset = FUSE4FS_FSB_TO_B(ff, startoff);\n+\tiomap->flags |= FUSE_IOMAP_F_MERGED;\n+\tif (startblock) {\n+\t\tiomap->addr = FUSE4FS_FSB_TO_B(ff, startblock) + ff->offset;\n+\t\tiomap->type = FUSE_IOMAP_TYPE_MAPPED;\n+\t} else {\n+\t\tiomap->addr = FUSE_IOMAP_NULL_ADDR;\n+\t\tiomap->type = FUSE_IOMAP_TYPE_HOLE;\n+\t}\n+\tiomap->length = fs->blocksize;\n+\n+\t/* See how long the mapping goes for. */\n+\tfor (startoff++; startoff < endoff; startoff++) {\n+\t\tblk64_t prev_startblock = startblock;\n+\n+\t\terr = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), NULL, 0,\n+\t\t\t\t startoff, NULL, &startblock);\n+\t\tif (err)\n+\t\t\tbreak;\n+\n+\t\tif (iomap->type == FUSE_IOMAP_TYPE_MAPPED) {\n+\t\t\tif (startblock == prev_startblock + 1)\n+\t\t\t\tiomap->length += fs->blocksize;\n+\t\t\telse\n+\t\t\t\tbreak;\n+\t\t} else {\n+\t\t\tif (startblock == 0)\n+\t\t\t\tiomap->length += fs->blocksize;\n+\t\t\telse\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * If this is a hole that goes beyond EOF, report this as a hole to the\n+\t * end of the range queried so that FIEMAP doesn't go mad.\n+\t */\n+\tif (iomap->type == FUSE_IOMAP_TYPE_HOLE &&\n+\t iomap->offset + iomap->length >= isize)\n+\t\tfuse4fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\n+\treturn 0;\n+}\n+\n+static int fuse4fs_iomap_begin_inline(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, struct fuse_file_iomap *iomap)\n+{\n+\tuint64_t one_fsb = FUSE4FS_FSB_TO_B(ff, 1);\n+\n+\tif (pos >= one_fsb) {\n+\t\tfuse4fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\t} else {\n+\t\t/* ext4 only supports inline data files up to 1 fsb */\n+\t\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\t\tiomap->addr = FUSE_IOMAP_NULL_ADDR;\n+\t\tiomap->offset = 0;\n+\t\tiomap->length = one_fsb;\n+\t\tiomap->type = FUSE_IOMAP_TYPE_INLINE;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int fuse4fs_iomap_begin_report(struct fuse4fs *ff, ext2_ino_t ino,\n+\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t off_t pos, uint64_t count,\n+\t\t\t\t uint32_t opflags,\n+\t\t\t\t struct fuse_file_iomap *read)\n+{\n+\tif (inode->i_flags & EXT4_INLINE_DATA_FL)\n+\t\treturn fuse4fs_iomap_begin_inline(ff, ino, inode, pos, count,\n+\t\t\t\t\t\t read);\n+\n+\tif (inode->i_flags & EXT4_EXTENTS_FL)\n+\t\treturn fuse4fs_iomap_begin_extent(ff, ino, inode, pos, count,\n+\t\t\t\t\t\t opflags, read);\n+\n+\treturn fuse4fs_iomap_begin_indirect(ff, ino, inode, pos, count,\n+\t\t\t\t\t opflags, read);\n+}\n+\n+static int fuse4fs_iomap_begin_read(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+{\n+\treturn -ENOSYS;\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+{\n+\treturn -ENOSYS;\n+}\n+\n+static void op_iomap_begin(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare,\n+\t\t\t off_t pos, uint64_t count, uint32_t opflags)\n+{\n+\tstruct fuse4fs *ff = fuse4fs_get(req);\n+\tstruct ext2_inode_large inode;\n+\tstruct fuse_file_iomap read = { };\n+\text2_filsys fs;\n+\text2_ino_t ino;\n+\terrcode_t err;\n+\tint ret = 0;\n+\n+\tFUSE4FS_CHECK_CONTEXT(req);\n+\tFUSE4FS_CONVERT_FINO(req, &ino, fino);\n+\n+\tdbg_printf(ff, \"%s: ino=%d pos=0x%llx count=0x%llx opflags=0x%x\\n\",\n+\t\t __func__, ino,\n+\t\t (unsigned long long)pos,\n+\t\t (unsigned long long)count,\n+\t\t opflags);\n+\n+\tfs = fuse4fs_start(ff);\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 (opflags & FUSE_IOMAP_OP_REPORT)\n+\t\tret = fuse4fs_iomap_begin_report(ff, ino, &inode, pos, count,\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+\telse\n+\t\tret = fuse4fs_iomap_begin_read(ff, ino, &inode, pos, count,\n+\t\t\t\t\t opflags, &read);\n+\tif (ret)\n+\t\tgoto out_unlock;\n+\n+\tdbg_printf(ff,\n+ \"%s: ino=%d pos=0x%llx -> addr=0x%llx offset=0x%llx length=0x%llx type=%u flags=0x%x\\n\",\n+\t\t __func__, ino,\n+\t\t (unsigned long long)pos,\n+\t\t (unsigned long long)read.addr,\n+\t\t (unsigned long long)read.offset,\n+\t\t (unsigned long long)read.length,\n+\t\t read.type,\n+\t\t read.flags);\n+\n+\t/* Not filling even the first byte will make the kernel unhappy. */\n+\tif (read.offset > pos || read.offset + read.length <= pos) {\n+\t\tret = translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);\n+\t\tgoto out_unlock;\n+\t}\n+\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_begin(req, &read, NULL);\n+}\n+\n+static void op_iomap_end(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare,\n+\t\t\t off_t pos, uint64_t count, uint32_t opflags,\n+\t\t\t ssize_t written, const struct fuse_file_iomap *iomap)\n+{\n+\tstruct fuse4fs *ff = fuse4fs_get(req);\n+\text2_ino_t ino;\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 count=0x%llx opflags=0x%x written=0x%zx mapflags=0x%x\\n\",\n+\t\t __func__, ino,\n+\t\t (unsigned long long)pos,\n+\t\t (unsigned long long)count,\n+\t\t opflags,\n+\t\t written,\n+\t\t iomap->flags);\n+\n+\tfuse_reply_err(req, 0);\n+}\n+#endif /* HAVE_FUSE_IOMAP */\n+\n static struct fuse_lowlevel_ops fs_ops = {\n \t.lookup = op_lookup,\n \t.setattr = op_setattr,\n@@ -5960,6 +6491,10 @@ static struct fuse_lowlevel_ops fs_ops = {\n #ifdef SUPPORT_FALLOCATE\n \t.fallocate = op_fallocate,\n #endif\n+#ifdef HAVE_FUSE_IOMAP\n+\t.iomap_begin = op_iomap_begin,\n+\t.iomap_end = op_iomap_end,\n+#endif /* HAVE_FUSE_IOMAP */\n };\n \n static int get_random_bytes(void *p, size_t sz)\n@@ -6413,6 +6948,9 @@ int main(int argc, char *argv[])\n \t\t.opstate = F4OP_WRITABLE,\n #ifdef HAVE_FUSE4FS_SERVICE\n \t\t.bdev_fd = -1,\n+#endif\n+#ifdef HAVE_FUSE_IOMAP\n+\t\t.iomap_state = IOMAP_UNKNOWN,\n #endif\n \t};\n \terrcode_t err;\ndiff --git a/lib/config.h.in b/lib/config.h.in\nindex 2c25632188e4f3..58338cc926590e 100644\n--- a/lib/config.h.in\n+++ b/lib/config.h.in\n@@ -148,6 +148,9 @@\n /* Define to 1 if you have the <fuse.h> header file. */\n #undef HAVE_FUSE_H\n \n+/* Define to 1 if fuse supports iomap */\n+#undef HAVE_FUSE_IOMAP\n+\n /* Define to 1 if fuse supports lowlevel API */\n #undef HAVE_FUSE_LOWLEVEL\n \ndiff --git a/misc/fuse2fs.c b/misc/fuse2fs.c\nindex 0f4781bc49f18f..63c9b59e54fb04 100644\n--- a/misc/fuse2fs.c\n+++ b/misc/fuse2fs.c\n@@ -138,6 +138,9 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)\n \treturn b - m;\n }\n \n+#define max(a, b)\t((a) > (b) ? (a) : (b))\n+#define min(a, b)\t((a) < (b) ? (a) : (b))\n+\n #define dbg_printf(fuse2fs, format, ...) \\\n \twhile ((fuse2fs)->debug) { \\\n \t\tprintf(\"FUSE2FS (%s): tid=%llu \" format, (fuse2fs)->shortdev, get_thread_id(), ##__VA_ARGS__); \\\n@@ -215,6 +218,14 @@ enum fuse2fs_opstate {\n \tF2OP_SHUTDOWN,\n };\n \n+#ifdef HAVE_FUSE_IOMAP\n+enum fuse2fs_iomap_state {\n+\tIOMAP_DISABLED,\n+\tIOMAP_UNKNOWN,\n+\tIOMAP_ENABLED,\n+};\n+#endif\n+\n /* Main program context */\n #define FUSE2FS_MAGIC\t\t(0xEF53DEADUL)\n struct fuse2fs {\n@@ -242,6 +253,9 @@ struct fuse2fs {\n \tint logfd;\n \tint blocklog;\n \tint oom_score_adj;\n+#ifdef HAVE_FUSE_IOMAP\n+\tenum fuse2fs_iomap_state iomap_state;\n+#endif\n \tunsigned int blockmask;\n \tunsigned long offset;\n \tunsigned int next_generation;\n@@ -693,6 +707,15 @@ fuse2fs_set_handle(struct fuse_file_info *fp, struct fuse2fs_file_handle *fh)\n \tfp->fh = (uintptr_t)fh;\n }\n \n+#ifdef HAVE_FUSE_IOMAP\n+static inline int fuse2fs_iomap_enabled(const struct fuse2fs *ff)\n+{\n+\treturn ff->iomap_state >= IOMAP_ENABLED;\n+}\n+#else\n+# define fuse2fs_iomap_enabled(...)\t(0)\n+#endif\n+\n static void get_now(struct timespec *now)\n {\n #ifdef CLOCK_REALTIME\n@@ -1122,7 +1145,7 @@ static errcode_t fuse2fs_open(struct fuse2fs *ff)\n \tchar options[128];\n \tdouble deadline;\n \tint flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_RW |\n-\t\t EXT2_FLAG_EXCLUSIVE;\n+\t\t EXT2_FLAG_EXCLUSIVE | EXT2_FLAG_WRITE_FULL_SUPER;\n \terrcode_t err;\n \n \tif (ff->lockfile) {\n@@ -1409,6 +1432,15 @@ static void op_destroy(void *p EXT2FS_ATTR((unused)))\n \t\t\t\t(stats->cache_hits + stats->cache_misses));\n \t}\n \n+\t/*\n+\t * If we're mounting in iomap mode, we need to unmount in op_destroy so\n+\t * that the block device will be released before umount(2) returns.\n+\t */\n+\tif (ff->iomap_state == IOMAP_ENABLED) {\n+\t\tfuse2fs_mmp_cancel(ff);\n+\t\tfuse2fs_unmount(ff);\n+\t}\n+\n \tfuse2fs_finish(ff, 0);\n }\n \n@@ -1545,6 +1577,44 @@ static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,\n }\n #endif\n \n+#ifdef HAVE_FUSE_IOMAP\n+static inline bool fuse2fs_wants_iomap(struct fuse2fs *ff)\n+{\n+\tif (ff->iomap_state == IOMAP_DISABLED)\n+\t\treturn false;\n+\n+\t/* iomap only works with block devices */\n+\tif (!(ff->fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE))\n+\t\treturn false;\n+\n+\t/*\n+\t * iomap addrs must be aligned to the bdev lba size; we use fs\n+\t * blocksize as a proxy here\n+\t */\n+\tif (ff->offset % ff->fs->blocksize > 0)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static void fuse2fs_iomap_enable(struct fuse_conn_info *conn,\n+\t\t\t\t struct fuse2fs *ff)\n+{\n+\t/* Don't let anyone touch iomap until the end of the patchset. */\n+\tff->iomap_state = IOMAP_DISABLED;\n+\treturn;\n+\n+\tif (fuse2fs_wants_iomap(ff) &&\n+\t fuse_set_feature_flag(conn, FUSE_CAP_IOMAP))\n+\t\tff->iomap_state = IOMAP_ENABLED;\n+\n+\tif (ff->iomap_state == IOMAP_UNKNOWN)\n+\t\tff->iomap_state = IOMAP_DISABLED;\n+}\n+#else\n+# define fuse2fs_iomap_enable(...)\t((void)0)\n+#endif\n+\n static void *op_init(struct fuse_conn_info *conn,\n \t\t struct fuse_config *cfg EXT2FS_ATTR((unused)))\n {\n@@ -1578,6 +1648,8 @@ static void *op_init(struct fuse_conn_info *conn,\n #ifdef FUSE_CAP_NO_EXPORT_SUPPORT\n \tfuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);\n #endif\n+\tfuse2fs_iomap_enable(conn, ff);\n+\n \tconn->time_gran = 1;\n \tcfg->use_ino = 1;\n \tif (ff->debug)\n@@ -5150,6 +5222,465 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,\n }\n #endif /* SUPPORT_FALLOCATE */\n \n+#ifdef HAVE_FUSE_IOMAP\n+static void fuse2fs_iomap_hole(struct fuse2fs *ff, struct fuse_file_iomap *iomap,\n+\t\t\t off_t pos, uint64_t count)\n+{\n+\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\tiomap->addr = FUSE_IOMAP_NULL_ADDR;\n+\tiomap->offset = pos;\n+\tiomap->length = count;\n+\tiomap->type = FUSE_IOMAP_TYPE_HOLE;\n+}\n+\n+static void fuse2fs_iomap_hole_to_eof(struct fuse2fs *ff,\n+\t\t\t\t struct fuse_file_iomap *iomap, off_t pos,\n+\t\t\t\t off_t count,\n+\t\t\t\t const struct ext2_inode_large *inode)\n+{\n+\text2_filsys fs = ff->fs;\n+\tuint64_t isize = EXT2_I_SIZE(inode);\n+\n+\t/*\n+\t * We have to be careful about handling a hole to the right of the\n+\t * entire mapping tree. First, the mapping must start and end on a\n+\t * block boundary because they must be aligned to at least an LBA for\n+\t * the block layer; and to the fsblock for smoother operation.\n+\t *\n+\t * As for the length -- we could return a mapping all the way to\n+\t * i_size, but i_size could be less than pos/count if we're zeroing the\n+\t * EOF block in anticipation of a truncate operation. Similarly, we\n+\t * don't want to end the mapping at pos+count because we know there's\n+\t * nothing mapped beyond here.\n+\t */\n+\tuint64_t startoff = round_down(pos, fs->blocksize);\n+\tuint64_t eofoff = round_up(max(pos + count, isize), fs->blocksize);\n+\n+\tdbg_printf(ff,\n+ \"pos=0x%llx count=0x%llx isize=0x%llx startoff=0x%llx eofoff=0x%llx\\n\",\n+\t\t (unsigned long long)pos,\n+\t\t (unsigned long long)count,\n+\t\t (unsigned long long)isize,\n+\t\t (unsigned long long)startoff,\n+\t\t (unsigned long long)eofoff);\n+\n+\tfuse2fs_iomap_hole(ff, iomap, startoff, eofoff - startoff);\n+}\n+\n+#define DEBUG_IOMAP\n+#ifdef DEBUG_IOMAP\n+# define __DUMP_EXTENT(ff, func, tag, startoff, err, extent) \\\n+\tdo { \\\n+\t\tdbg_printf((ff), \\\n+ \"%s: %s startoff 0x%llx err %ld lblk 0x%llx pblk 0x%llx len 0x%x flags 0x%x\\n\", \\\n+\t\t\t (func), (tag), (startoff), (err), (extent)->e_lblk, \\\n+\t\t\t (extent)->e_pblk, (extent)->e_len, \\\n+\t\t\t (extent)->e_flags & EXT2_EXTENT_FLAGS_UNINIT); \\\n+\t} while(0)\n+# define DUMP_EXTENT(ff, tag, startoff, err, extent) \\\n+\t__DUMP_EXTENT((ff), __func__, (tag), (startoff), (err), (extent))\n+\n+# define __DUMP_INFO(ff, func, tag, startoff, err, info) \\\n+\tdo { \\\n+\t\tdbg_printf((ff), \\\n+ \"%s: %s startoff 0x%llx err %ld entry %d/%d/%d level %d/%d\\n\", \\\n+\t\t\t (func), (tag), (startoff), (err), \\\n+\t\t\t (info)->curr_entry, (info)->num_entries, \\\n+\t\t\t (info)->max_entries, (info)->curr_level, \\\n+\t\t\t (info)->max_depth); \\\n+\t} while(0)\n+# define DUMP_INFO(ff, tag, startoff, err, info) \\\n+\t__DUMP_INFO((ff), __func__, (tag), (startoff), (err), (info))\n+#else\n+# define __DUMP_EXTENT(...)\t((void)0)\n+# define DUMP_EXTENT(...)\t((void)0)\n+# define DUMP_INFO(...)\t\t((void)0)\n+#endif\n+\n+static inline errcode_t __fuse2fs_get_mapping_at(struct fuse2fs *ff,\n+\t\t\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t\t\t blk64_t startoff,\n+\t\t\t\t\t\t struct ext2fs_extent *bmap,\n+\t\t\t\t\t\t const char *func)\n+{\n+\terrcode_t err;\n+\n+\t/*\n+\t * Find the file mapping at startoff. We don't check the return value\n+\t * of _goto because _get will error out if _goto failed. There's a\n+\t * subtlety to the outcome of _goto when startoff falls in a sparse\n+\t * hole however:\n+\t *\n+\t * Most of the time, _goto points the cursor at the mapping whose lblk\n+\t * is just to the left of startoff. The mapping may or may not overlap\n+\t * startoff; this is ok. In other words, the tree lookup behaves as if\n+\t * we asked it to use a less than or equals comparison.\n+\t *\n+\t * However, if startoff is to the left of the first mapping in the\n+\t * extent tree, _goto points the cursor at that first mapping because\n+\t * it doesn't know how to deal with this situation. In this case,\n+\t * the tree lookup behaves as if we asked it to use a greater than\n+\t * or equals comparison.\n+\t *\n+\t * Note: If _get() returns 'no current node', that means that there\n+\t * aren't any mappings at all.\n+\t */\n+\text2fs_extent_goto(handle, startoff);\n+\terr = ext2fs_extent_get(handle, EXT2_EXTENT_CURRENT, bmap);\n+\t__DUMP_EXTENT(ff, func, \"lookup\", startoff, err, bmap);\n+\tif (err == EXT2_ET_NO_CURRENT_NODE)\n+\t\terr = EXT2_ET_EXTENT_NOT_FOUND;\n+\treturn err;\n+}\n+\n+static inline errcode_t __fuse2fs_get_next_mapping(struct fuse2fs *ff,\n+\t\t\t\t\t\t ext2_extent_handle_t handle,\n+\t\t\t\t\t\t blk64_t startoff,\n+\t\t\t\t\t\t struct ext2fs_extent *bmap,\n+\t\t\t\t\t\t const char *func)\n+{\n+\tstruct ext2fs_extent newex;\n+\tstruct ext2_extent_info info;\n+\terrcode_t err;\n+\n+\t/*\n+\t * The extent tree code has this (probably broken) behavior that if\n+\t * more than two of the highest levels of the cursor point at the\n+\t * rightmost edge of an extent tree block, a _NEXT_LEAF movement fails\n+\t * to move the cursor position of any of the lower levels. IOWs, if\n+\t * leaf level N is at the right edge, it will only advance level N-1\n+\t * to the right. If N-1 was at the right edge, the cursor resets to\n+\t * record 0 of that level and goes down to the wrong leaf.\n+\t *\n+\t * Work around this by walking up (towards root level 0) the extent\n+\t * tree until we find a level where we're not already at the rightmost\n+\t * edge. The _NEXT_LEAF movement will walk down the tree to find the\n+\t * leaves.\n+\t */\n+\terr = ext2fs_extent_get_info(handle, &info);\n+\tDUMP_INFO(ff, \"UP?\", startoff, err, &info);\n+\tif (err)\n+\t\treturn err;\n+\n+\twhile (info.curr_entry == info.num_entries && info.curr_level > 0) {\n+\t\terr = ext2fs_extent_get(handle, EXT2_EXTENT_UP, &newex);\n+\t\tDUMP_EXTENT(ff, \"UP\", startoff, err, &newex);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t\terr = ext2fs_extent_get_info(handle, &info);\n+\t\tDUMP_INFO(ff, \"UP\", startoff, err, &info);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\t/*\n+\t * If we're at the root and there are no more entries, there's nothing\n+\t * else to be found.\n+\t */\n+\tif (info.curr_level == 0 && info.curr_entry == info.num_entries)\n+\t\treturn EXT2_ET_EXTENT_NOT_FOUND;\n+\n+\t/* Otherwise grab this next leaf and return it. */\n+\terr = ext2fs_extent_get(handle, EXT2_EXTENT_NEXT_LEAF, &newex);\n+\tDUMP_EXTENT(ff, \"NEXT\", startoff, err, &newex);\n+\tif (err)\n+\t\treturn err;\n+\n+\t*bmap = newex;\n+\treturn 0;\n+}\n+\n+#define fuse2fs_get_mapping_at(ff, handle, startoff, bmap) \\\n+\t__fuse2fs_get_mapping_at((ff), (handle), (startoff), (bmap), __func__)\n+#define fuse2fs_get_next_mapping(ff, handle, startoff, bmap) \\\n+\t__fuse2fs_get_next_mapping((ff), (handle), (startoff), (bmap), __func__)\n+\n+static errcode_t fuse2fs_iomap_begin_extent(struct fuse2fs *ff, uint64_t ino,\n+\t\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t\t off_t pos, uint64_t count,\n+\t\t\t\t\t uint32_t opflags,\n+\t\t\t\t\t struct fuse_file_iomap *iomap)\n+{\n+\text2_extent_handle_t handle;\n+\tstruct ext2fs_extent extent = { };\n+\text2_filsys fs = ff->fs;\n+\tconst blk64_t startoff = FUSE2FS_B_TO_FSBT(ff, pos);\n+\terrcode_t err;\n+\tint ret = 0;\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+\terr = fuse2fs_get_mapping_at(ff, handle, startoff, &extent);\n+\tif (err == EXT2_ET_EXTENT_NOT_FOUND) {\n+\t\t/* No mappings at all; the whole range is a hole. */\n+\t\tfuse2fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\t\tgoto out_handle;\n+\t}\n+\tif (err) {\n+\t\tret = translate_error(fs, ino, err);\n+\t\tgoto out_handle;\n+\t}\n+\n+\tif (startoff < extent.e_lblk) {\n+\t\t/*\n+\t\t * Mapping starts to the right of the current position.\n+\t\t * Synthesize a hole going to that next extent.\n+\t\t */\n+\t\tfuse2fs_iomap_hole(ff, iomap, FUSE2FS_FSB_TO_B(ff, startoff),\n+\t\t\t\tFUSE2FS_FSB_TO_B(ff, extent.e_lblk - startoff));\n+\t\tgoto out_handle;\n+\t}\n+\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, the\n+\t\t * whole range is in a hole.\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\tfuse2fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\t\t\tgoto out_handle;\n+\t\t}\n+\n+\t\t/*\n+\t\t * If the new mapping starts to the right of startoff, there's\n+\t\t * a hole from startoff to the start of the new mapping.\n+\t\t */\n+\t\tif (startoff < extent.e_lblk) {\n+\t\t\tfuse2fs_iomap_hole(ff, iomap,\n+\t\t\t\tFUSE2FS_FSB_TO_B(ff, startoff),\n+\t\t\t\tFUSE2FS_FSB_TO_B(ff, extent.e_lblk - startoff));\n+\t\t\tgoto out_handle;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The new mapping starts at startoff. Something weird\n+\t\t * happened in the extent tree lookup, but we found a valid\n+\t\t * mapping so we'll run with it.\n+\t\t */\n+\t}\n+\n+\t/* Mapping overlaps startoff, report this. */\n+\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\tiomap->addr = FUSE2FS_FSB_TO_B(ff, extent.e_pblk) + ff->offset;\n+\tiomap->offset = FUSE2FS_FSB_TO_B(ff, extent.e_lblk);\n+\tiomap->length = FUSE2FS_FSB_TO_B(ff, extent.e_len);\n+\tif (extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT)\n+\t\tiomap->type = FUSE_IOMAP_TYPE_UNWRITTEN;\n+\telse\n+\t\tiomap->type = FUSE_IOMAP_TYPE_MAPPED;\n+\n+out_handle:\n+\text2fs_extent_free(handle);\n+\treturn ret;\n+}\n+\n+static int fuse2fs_iomap_begin_indirect(struct fuse2fs *ff, uint64_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 *iomap)\n+{\n+\text2_filsys fs = ff->fs;\n+\tblk64_t startoff = FUSE2FS_B_TO_FSBT(ff, pos);\n+\tuint64_t isize = EXT2_I_SIZE(inode);\n+\tuint64_t real_count = min(count, 131072);\n+\tconst blk64_t endoff = FUSE2FS_B_TO_FSB(ff, pos + real_count);\n+\tblk64_t startblock;\n+\terrcode_t err;\n+\n+\terr = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), NULL, 0, startoff, NULL,\n+\t\t\t &startblock);\n+\tif (err)\n+\t\treturn translate_error(fs, ino, err);\n+\n+\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\tiomap->offset = FUSE2FS_FSB_TO_B(ff, startoff);\n+\tiomap->flags |= FUSE_IOMAP_F_MERGED;\n+\tif (startblock) {\n+\t\tiomap->addr = FUSE2FS_FSB_TO_B(ff, startblock) + ff->offset;\n+\t\tiomap->type = FUSE_IOMAP_TYPE_MAPPED;\n+\t} else {\n+\t\tiomap->addr = FUSE_IOMAP_NULL_ADDR;\n+\t\tiomap->type = FUSE_IOMAP_TYPE_HOLE;\n+\t}\n+\tiomap->length = fs->blocksize;\n+\n+\t/* See how long the mapping goes for. */\n+\tfor (startoff++; startoff < endoff; startoff++) {\n+\t\tblk64_t prev_startblock = startblock;\n+\n+\t\terr = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), NULL, 0,\n+\t\t\t\t startoff, NULL, &startblock);\n+\t\tif (err)\n+\t\t\tbreak;\n+\n+\t\tif (iomap->type == FUSE_IOMAP_TYPE_MAPPED) {\n+\t\t\tif (startblock == prev_startblock + 1)\n+\t\t\t\tiomap->length += fs->blocksize;\n+\t\t\telse\n+\t\t\t\tbreak;\n+\t\t} else {\n+\t\t\tif (startblock == 0)\n+\t\t\t\tiomap->length += fs->blocksize;\n+\t\t\telse\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * If this is a hole that goes beyond EOF, report this as a hole to the\n+\t * end of the range queried so that FIEMAP doesn't go mad.\n+\t */\n+\tif (iomap->type == FUSE_IOMAP_TYPE_HOLE &&\n+\t iomap->offset + iomap->length >= isize)\n+\t\tfuse2fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\n+\treturn 0;\n+}\n+\n+static int fuse2fs_iomap_begin_inline(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, struct fuse_file_iomap *iomap)\n+{\n+\tuint64_t one_fsb = FUSE2FS_FSB_TO_B(ff, 1);\n+\n+\tif (pos >= one_fsb) {\n+\t\tfuse2fs_iomap_hole_to_eof(ff, iomap, pos, count, inode);\n+\t} else {\n+\t\t/* ext4 only supports inline data files up to 1 fsb */\n+\t\tiomap->dev = FUSE_IOMAP_DEV_NULL;\n+\t\tiomap->addr = FUSE_IOMAP_NULL_ADDR;\n+\t\tiomap->offset = 0;\n+\t\tiomap->length = one_fsb;\n+\t\tiomap->type = FUSE_IOMAP_TYPE_INLINE;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int fuse2fs_iomap_begin_report(struct fuse2fs *ff, ext2_ino_t ino,\n+\t\t\t\t struct ext2_inode_large *inode,\n+\t\t\t\t off_t pos, uint64_t count,\n+\t\t\t\t uint32_t opflags,\n+\t\t\t\t struct fuse_file_iomap *read)\n+{\n+\tif (inode->i_flags & EXT4_INLINE_DATA_FL)\n+\t\treturn fuse2fs_iomap_begin_inline(ff, ino, inode, pos, count,\n+\t\t\t\t\t\t read);\n+\n+\tif (inode->i_flags & EXT4_EXTENTS_FL)\n+\t\treturn fuse2fs_iomap_begin_extent(ff, ino, inode, pos, count,\n+\t\t\t\t\t\t opflags, read);\n+\n+\treturn fuse2fs_iomap_begin_indirect(ff, ino, inode, pos, count,\n+\t\t\t\t\t opflags, read);\n+}\n+\n+static int fuse2fs_iomap_begin_read(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+{\n+\treturn -ENOSYS;\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+{\n+\treturn -ENOSYS;\n+}\n+\n+static int op_iomap_begin(const char *path, uint64_t nodeid, uint64_t attr_ino,\n+\t\t\t off_t pos, uint64_t count, uint32_t opflags,\n+\t\t\t struct fuse_file_iomap *read,\n+\t\t\t struct fuse_file_iomap *write)\n+{\n+\tstruct fuse2fs *ff = fuse2fs_get();\n+\tstruct ext2_inode_large inode;\n+\text2_filsys fs;\n+\terrcode_t err;\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 count=0x%llx opflags=0x%x\\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 (unsigned long long)count,\n+\t\t opflags);\n+\n+\tfs = fuse2fs_start(ff);\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 (opflags & FUSE_IOMAP_OP_REPORT)\n+\t\tret = fuse2fs_iomap_begin_report(ff, attr_ino, &inode, pos,\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+\telse\n+\t\tret = fuse2fs_iomap_begin_read(ff, attr_ino, &inode, pos,\n+\t\t\t\t\t count, opflags, read);\n+\tif (ret)\n+\t\tgoto out_unlock;\n+\n+\tdbg_printf(ff, \"%s: nodeid=%llu attr_ino=%llu pos=0x%llx -> addr=0x%llx offset=0x%llx length=0x%llx type=%u\\n\",\n+\t\t __func__,\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 (unsigned long long)read->addr,\n+\t\t (unsigned long long)read->offset,\n+\t\t (unsigned long long)read->length,\n+\t\t read->type);\n+\n+\t/* Not filling even the first byte will make the kernel unhappy. */\n+\tif (read->offset > pos || read->offset + read->length <= pos) {\n+\t\tret = translate_error(fs, attr_ino, EXT2_ET_INODE_CORRUPTED);\n+\t\tgoto out_unlock;\n+\t}\n+\n+out_unlock:\n+\tfuse2fs_finish(ff, ret);\n+\treturn ret;\n+}\n+\n+static int op_iomap_end(const char *path, uint64_t nodeid, uint64_t attr_ino,\n+\t\t\toff_t pos, uint64_t count, uint32_t opflags,\n+\t\t\tssize_t written, const struct fuse_file_iomap *iomap)\n+{\n+\tstruct fuse2fs *ff = fuse2fs_get();\n+\n+\tFUSE2FS_CHECK_CONTEXT(ff);\n+\n+\tdbg_printf(ff,\n+ \"%s: path=%s nodeid=%llu attr_ino=%llu pos=0x%llx count=0x%llx opflags=0x%x written=0x%zx mapflags=0x%x\\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 (unsigned long long)count,\n+\t\t opflags,\n+\t\t written,\n+\t\t iomap->flags);\n+\n+\treturn 0;\n+}\n+#endif /* HAVE_FUSE_IOMAP */\n+\n static struct fuse_operations fs_ops = {\n \t.init = op_init,\n \t.destroy = op_destroy,\n@@ -5191,6 +5722,10 @@ static struct fuse_operations fs_ops = {\n #ifdef SUPPORT_FALLOCATE\n \t.fallocate = op_fallocate,\n #endif\n+#ifdef HAVE_FUSE_IOMAP\n+\t.iomap_begin = op_iomap_begin,\n+\t.iomap_end = op_iomap_end,\n+#endif /* HAVE_FUSE_IOMAP */\n };\n \n static int get_random_bytes(void *p, size_t sz)\n@@ -5477,6 +6012,9 @@ int main(int argc, char *argv[])\n \t\t.bfl = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER,\n \t\t.oom_score_adj = -500,\n \t\t.opstate = F2OP_WRITABLE,\n+#ifdef HAVE_FUSE_IOMAP\n+\t\t.iomap_state = IOMAP_UNKNOWN,\n+#endif\n \t};\n \terrcode_t err;\n \tFILE *orig_stderr = stderr;\n", "prefixes": [ "01/19" ] }