{"id":2231580,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2231580/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-ext4/patch/177758363730.1314717.8907760223995213134.stgit@frogsfrogsfrogs/","project":{"id":8,"url":"http://patchwork.ozlabs.org/api/1.1/projects/8/?format=json","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":"<177758363730.1314717.8907760223995213134.stgit@frogsfrogsfrogs>","date":"2026-04-30T21:17:53","name":"[11/13] example/service_ll: create a sample systemd service fuse server","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"24be1162c8393177886ab8b30b97ff9913423114","submitter":{"id":77032,"url":"http://patchwork.ozlabs.org/api/1.1/people/77032/?format=json","name":"Darrick J. Wong","email":"djwong@kernel.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/linux-ext4/patch/177758363730.1314717.8907760223995213134.stgit@frogsfrogsfrogs/mbox/","series":[{"id":502386,"url":"http://patchwork.ozlabs.org/api/1.1/series/502386/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-ext4/list/?series=502386","date":"2026-04-30T21:15:17","name":"[01/13] Refactor mount code / move common functions to mount_util.c","version":1,"mbox":"http://patchwork.ozlabs.org/series/502386/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2231580/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2231580/checks/","tags":{},"headers":{"Return-Path":"\n <SRS0=yIxy=C5=vger.kernel.org=linux-ext4+bounces-16263-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=P/PXpoZO;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=ozlabs.org\n (client-ip=2404:9400:2221:ea00::3; helo=mail.ozlabs.org;\n envelope-from=srs0=yixy=c5=vger.kernel.org=linux-ext4+bounces-16263-patchwork-incoming=ozlabs.org@ozlabs.org;\n receiver=patchwork.ozlabs.org)","gandalf.ozlabs.org;\n arc=pass smtp.remote-ip=\"2600:3c04:e001:36c::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=P/PXpoZO;\n\tdkim-atps=neutral","gandalf.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c04:e001:36c::12fc:5321; helo=tor.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16263-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=\"P/PXpoZO\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=10.30.226.201"],"Received":["from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3])\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 4g66XC0Gv3z1yGq\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 01 May 2026 07:20:27 +1000 (AEST)","from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3])\n\tby gandalf.ozlabs.org (Postfix) with ESMTP id 4g66XB6vWjz4wLX\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 01 May 2026 07:20:26 +1000 (AEST)","by gandalf.ozlabs.org (Postfix)\n\tid 4g66XB6hNsz4wTZ; Fri, 01 May 2026 07:20:26 +1000 (AEST)","from tor.lore.kernel.org (tor.lore.kernel.org\n [IPv6:2600:3c04:e001:36c::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 4g66X71cY2z4wLX\n\tfor <patchwork-incoming@ozlabs.org>; Fri, 01 May 2026 07:20:23 +1000 (AEST)","from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby tor.lore.kernel.org (Postfix) with ESMTP id 83CE53045B27\n\tfor <patchwork-incoming@ozlabs.org>; Thu, 30 Apr 2026 21:17:58 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id C85CB3AB276;\n\tThu, 30 Apr 2026 21:17:54 +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 89BF7376BD6;\n\tThu, 30 Apr 2026 21:17:54 +0000 (UTC)","by smtp.kernel.org (Postfix) with ESMTPSA id 18953C2BCB3;\n\tThu, 30 Apr 2026 21:17:54 +0000 (UTC)"],"ARC-Seal":["i=2; a=rsa-sha256; d=ozlabs.org; s=201707; t=1777584026; cv=pass;\n\tb=DcQ1QkB9aliy3o4zoQToHI/2aYLcp+QjA5JzSayENgeU5J1Hb6vC3BBwMOHql/ws25Cy+8CXwMQ6q7fQAspJ1yrnP5JL7rsB6zk+wiGMe5D7P6IlCU4eWvt5RskV+NwlbzIHt1u9GF2+TVKbTlLI1/k6DtkHBNDu/YjpAPaO7OsBuExzk37CCjCU2yFB0Mq04KlngswVHTSb2g/MfJXtPvsxCMu35HTeydPzJo43fJRTmyP1fIg7+a/qIYNBws+VboDAcCLPih8DwvkOBqNy1LDGd7DsmKuFPJ1p6EyyMdUxCfmaKRjKCM7KjXAsgxx8dgf0TBPPW9C+cj+QmcVgVg==","i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777583874; cv=none;\n b=GAMs4kwM1NDLAoEOKNwyAAcaxV1T+1QIrrN2MPNbwzbWchW4uQSaxVOvRrftE7C10YiaY/w8JXqpcPuHStnQOB8loDIJr+6ccqMByD1UhjMa16GABKSFLqNLMgDvaRRpxqUXtOHd8QPAu1iNVWr75FZn5lFMofXjgWXzSM91+0I="],"ARC-Message-Signature":["i=2; a=rsa-sha256; d=ozlabs.org; s=201707;\n\tt=1777584026; c=relaxed/relaxed;\n\tbh=jXVUFAGquUGfjiiGEle+AlWJ96+wxBRk2wMkpQZlW0k=;\n\th=Date:Subject:From:To:Cc:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=EBrlRP9nNfksQNiH450Rb3aJ1VEb/lIeAe9PlIdBJmhi+4hbz9hS2J3YFR2H2QMIguZfARCA4idNQ3j6+846R2efxWgxHSqULMiomud8eMbstlavdYDPcfo+zLN24190M5D82qbQsbQlUg30gSNi9IUxrvZ11Ckacsei0gTmp5lot0wj+DQ3ZcNYlv/Hu8kzp45d2Dvs2sbPWExYC8LklVb31TgI8sLAvpq/3Ykfg7L1CTblVZFifm/D5hCiVM+BuXtHw4ApIPEEW11nZgAQs4uCMPKCebAXNjD1uwFSyFTGWGvLOqIEnlPUOCXWmr6rCR215KS5aEfE6lRJR+0s8w==","i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777583874; c=relaxed/simple;\n\tbh=Wxb1Gx/3wlcBgqP0tCE88te9objIdI85j4CXgs9Ces4=;\n\th=Date:Subject:From:To:Cc:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=gVnaqWONKDGY/fpIBY03AQivdTHr/Mr/2hOCA1CpyeYxqQO3yZIhBjA2lIpDfqnk2lZc5ACdYA4Yxh5P5C/GWe6Cpxm0cM1PxNEdIwdr0GJZ0gl73uSfajTzHVJeKChJgs1EwmZvLOhziBEsaT5IhNrT8pun065O53DEkNQZxzs="],"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=P/PXpoZO; dkim-atps=neutral;\n spf=pass (client-ip=2600:3c04:e001:36c::12fc:5321; helo=tor.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16263-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=P/PXpoZO; 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=1777583874;\n\tbh=Wxb1Gx/3wlcBgqP0tCE88te9objIdI85j4CXgs9Ces4=;\n\th=Date:Subject:From:To:Cc:In-Reply-To:References:From;\n\tb=P/PXpoZOzdLrRBqdcA40t5UL3ju8eawTVZXH0eCafzlll02nOsm8F/sPt4sI+tif3\n\t 5sHb8iw3Qo5fj3wx6vFMzIVCYwlmFH2NJ9vVyTLPKiBc2re0LHz3KXvrMq+Oe/OHvO\n\t sQM7xLDgVgEEuKQuCAZp6/8lPjsKU6syxl2Vnvhk7wSgbnCu3BOo6YxMt0gSEfzvQm\n\t NgXjflFo8fWmyza5d5DMdjigrC/vdq5JWRYDSrztLwasWl/ZRr6BFCKSoKHPHuP9Ax\n\t LwEoOZoEbsVs/oifYAP8fye0DDJ/1zzguyUNlBiSo2dVDCvHeY8ndjn2E+8zjICDSj\n\t qDm8kqwt+h+fg==","Date":"Thu, 30 Apr 2026 14:17:53 -0700","Subject":"[PATCH 11/13] example/service_ll: create a sample systemd service\n fuse server","From":"\"Darrick J. Wong\" <djwong@kernel.org>","To":"bernd@bsbernd.com, djwong@kernel.org","Cc":"linux-fsdevel@vger.kernel.org, fuse-devel@lists.linux.dev,\n linux-ext4@vger.kernel.org, miklos@szeredi.hu, neal@gompa.dev,\n joannelkoong@gmail.com","Message-ID":"<177758363730.1314717.8907760223995213134.stgit@frogsfrogsfrogs>","In-Reply-To":"<177758363484.1314717.11777978893472254088.stgit@frogsfrogsfrogs>","References":"<177758363484.1314717.11777978893472254088.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\nCreate a simple fuse server that can be run as a systemd service.\nI plan to create some more single-file fuse server examples, so most of\nthe boilerplate code goes in a separate file.\n\nSigned-off-by: \"Darrick J. Wong\" <djwong@kernel.org>\n---\n example/single_file.h        |  157 +++++++++\n lib/util.h                   |   35 ++\n example/meson.build          |   14 +\n example/service_ll.c         |  328 ++++++++++++++++++\n example/service_ll.socket.in |   15 +\n example/service_ll@.service  |  102 ++++++\n example/single_file.c        |  746 ++++++++++++++++++++++++++++++++++++++++++\n meson.build                  |    1 \n 8 files changed, 1398 insertions(+)\n create mode 100644 example/single_file.h\n create mode 100644 example/service_ll.c\n create mode 100644 example/service_ll.socket.in\n create mode 100644 example/service_ll@.service\n create mode 100644 example/single_file.c","diff":"diff --git a/example/single_file.h b/example/single_file.h\nnew file mode 100644\nindex 00000000000000..6e5d3e7c975385\n--- /dev/null\n+++ b/example/single_file.h\n@@ -0,0 +1,157 @@\n+/*\n+ * FUSE: Filesystem in Userspace\n+ * Copyright (C) 2026 Oracle.\n+ *\n+ * This program can be distributed under the terms of the GNU GPLv2.\n+ * See the file GPL2.txt.\n+ *\n+ * This file is shared library code for example fuse servers that want to\n+ * expose a single regular file that wraps another file in a manner that goes\n+ * beyond simple passthrough.  It is not itself a fuse server.\n+ */\n+#ifndef FUSE_SINGLE_FILE_H_\n+#define FUSE_SINGLE_FILE_H_\n+\n+static inline uint64_t round_up(uint64_t b, unsigned int align)\n+{\n+\tunsigned int m;\n+\n+\tif (align == 0)\n+\t\treturn b;\n+\tm = b % align;\n+\tif (m)\n+\t\tb += align - m;\n+\treturn b;\n+}\n+\n+static inline uint64_t round_down(uint64_t b, unsigned int align)\n+{\n+\tunsigned int m;\n+\n+\tif (align == 0)\n+\t\treturn b;\n+\tm = b % align;\n+\treturn b - m;\n+}\n+\n+static inline uint64_t howmany(uint64_t b, unsigned int align)\n+{\n+\tunsigned int m;\n+\n+\tif (align == 0)\n+\t\treturn b;\n+\tm = (b % align) ? 1 : 0;\n+\treturn (b / align) + m;\n+}\n+\n+struct single_file {\n+\tint backing_fd;\n+\n+\tint64_t isize;\n+\tuint64_t blocks;\n+\n+\tmode_t mode;\n+\n+\tbool ro;\n+\tbool allow_dio;\n+\tbool sync;\n+\tbool require_bdev;\n+\n+\tunsigned int blocksize;\n+\n+\tstruct timespec atime;\n+\tstruct timespec mtime;\n+\tstruct timespec ctime;\n+\n+\tpthread_mutex_t lock;\n+};\n+\n+extern struct single_file single_file;\n+\n+static inline uint64_t b_to_fsbt(uint64_t off)\n+{\n+\treturn off / single_file.blocksize;\n+}\n+\n+static inline uint64_t b_to_fsb(uint64_t off)\n+{\n+\treturn (off + single_file.blocksize - 1) / single_file.blocksize;\n+}\n+\n+static inline uint64_t fsb_to_b(uint64_t fsb)\n+{\n+\treturn fsb * single_file.blocksize;\n+}\n+\n+enum single_file_opt_keys {\n+\tSINGLE_FILE_RO = 171717, /* how many options could we possibly have? */\n+\tSINGLE_FILE_RW,\n+\tSINGLE_FILE_REQUIRE_BDEV,\n+\tSINGLE_FILE_DIO,\n+\tSINGLE_FILE_NODIO,\n+\tSINGLE_FILE_SYNC,\n+\tSINGLE_FILE_NOSYNC,\n+\tSINGLE_FILE_SIZE,\n+\tSINGLE_FILE_BLOCKSIZE,\n+\n+\tSINGLE_FILE_NR_KEYS,\n+};\n+\n+#define SINGLE_FILE_OPT_KEYS \\\n+\tFUSE_OPT_KEY(\"ro\",\t\tSINGLE_FILE_RO), \\\n+\tFUSE_OPT_KEY(\"rw\",\t\tSINGLE_FILE_RW), \\\n+\tFUSE_OPT_KEY(\"require_bdev\",\tSINGLE_FILE_REQUIRE_BDEV), \\\n+\tFUSE_OPT_KEY(\"dio\",\t\tSINGLE_FILE_DIO), \\\n+\tFUSE_OPT_KEY(\"nodio\",\t\tSINGLE_FILE_NODIO), \\\n+\tFUSE_OPT_KEY(\"sync\",\t\tSINGLE_FILE_SYNC), \\\n+\tFUSE_OPT_KEY(\"nosync\",\t\tSINGLE_FILE_NOSYNC), \\\n+\tFUSE_OPT_KEY(\"size=%s\",\t\tSINGLE_FILE_SIZE), \\\n+\tFUSE_OPT_KEY(\"blocksize=%s\",\tSINGLE_FILE_BLOCKSIZE)\n+\n+int single_file_opt_proc(void *data, const char *arg, int key,\n+\t\t\t struct fuse_args *outargs);\n+\n+unsigned long long parse_num_blocks(const char *arg, int log_block_size);\n+\n+struct fuse_service;\n+int single_file_service_open(struct fuse_service *sf, const char *path);\n+\n+void single_file_check_read(off_t pos, size_t *count);\n+int single_file_check_write(off_t pos, size_t *count);\n+\n+int single_file_configure(const char *device, const char *filename);\n+int single_file_configure_simple(const char *filename);\n+void single_file_close(void);\n+\n+ssize_t single_file_pwrite(const char *buf, size_t count, off_t pos);\n+ssize_t single_file_pread(char *buf, size_t count, off_t pos);\n+\n+/* low-level fuse operation handlers */\n+\n+bool is_single_file_child(fuse_ino_t parent, const char *name);\n+bool is_single_file_ino(fuse_ino_t ino);\n+\n+void single_file_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,\n+\t\t\t\toff_t off, struct fuse_file_info *fi);\n+\n+void single_file_ll_statfs(fuse_req_t req, fuse_ino_t ino);\n+\n+void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,\n+\t\t       struct fuse_file_info *fi);\n+\n+void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino,\n+\t\t\t    struct fuse_file_info *fi);\n+void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,\n+\t\t\t int to_set, struct fuse_file_info *fi);\n+\n+void single_file_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name);\n+void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,\n+\t\t\t struct fuse_file_info *fi);\n+\n+void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,\n+\t\t\t  struct fuse_file_info *fi);\n+\n+int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,\n+\t\t      off_t off, size_t maxsize);\n+\n+#endif /* FUSE_SINGLE_FILE_H_ */\ndiff --git a/lib/util.h b/lib/util.h\nindex 107a2bfdd6105b..6ec6604fb74caf 100644\n--- a/lib/util.h\n+++ b/lib/util.h\n@@ -4,6 +4,9 @@\n #include <stdint.h>\n #include <stdbool.h>\n \n+#define max(x, y) ((x) > (y) ? (x) : (y))\n+#define min(x, y) ((x) < (y) ? (x) : (y))\n+\n #define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))\n \n #define likely(x) __builtin_expect(!!(x), 1)\n@@ -46,4 +49,36 @@ static inline uint64_t fuse_higher_32_bits(uint64_t nr)\n #define fallthrough do {} while (0)\n #endif\n \n+static inline uint64_t round_up(uint64_t b, unsigned int align)\n+{\n+\tunsigned int m;\n+\n+\tif (align == 0)\n+\t\treturn b;\n+\tm = b % align;\n+\tif (m)\n+\t\tb += align - m;\n+\treturn b;\n+}\n+\n+static inline uint64_t round_down(uint64_t b, unsigned int align)\n+{\n+\tunsigned int m;\n+\n+\tif (align == 0)\n+\t\treturn b;\n+\tm = b % align;\n+\treturn b - m;\n+}\n+\n+static inline uint64_t howmany(uint64_t b, unsigned int align)\n+{\n+\tunsigned int m;\n+\n+\tif (align == 0)\n+\t\treturn b;\n+\tm = (b % align) ? 1 : 0;\n+\treturn (b / align) + m;\n+}\n+\n #endif /* FUSE_UTIL_H_ */\ndiff --git a/example/meson.build b/example/meson.build\nindex 76cf2d96db0349..e948f6ba74fdfa 100644\n--- a/example/meson.build\n+++ b/example/meson.build\n@@ -12,6 +12,15 @@ if not platform.endswith('bsd') and platform != 'dragonfly'\n     examples += [ 'null' ]\n endif\n \n+single_file_examples = [ ]\n+\n+if platform.endswith('linux')\n+    single_file_examples += [ 'service_ll' ]\n+    configure_file(input: 'service_ll.socket.in',\n+                   output: 'service_ll.socket',\n+                   configuration: private_cfg)\n+endif\n+\n threaded_examples = [ 'notify_inval_inode',\n                       'invalidate_path',\n                       'notify_store_retrieve',\n@@ -25,6 +34,11 @@ foreach ex : examples\n                install: false)\n endforeach\n \n+foreach ex : single_file_examples\n+    executable(ex, [ex + '.c', 'single_file.c'],\n+               dependencies: [ libfuse_dep ],\n+               install: false)\n+endforeach\n \n foreach ex : threaded_examples\n     executable(ex, ex + '.c',\ndiff --git a/example/service_ll.c b/example/service_ll.c\nnew file mode 100644\nindex 00000000000000..d045176443d4e3\n--- /dev/null\n+++ b/example/service_ll.c\n@@ -0,0 +1,328 @@\n+/*\n+ * FUSE: Filesystem in Userspace\n+ * Copyright (C) 2026 Oracle.\n+ *\n+ * This program can be distributed under the terms of the GNU GPLv2.\n+ * See the file GPL2.txt.\n+ */\n+\n+/** @file\n+ *\n+ * Minimal example filesystem using low-level API and systemd service API.\n+ *\n+ * - Shows how to build a low level FUSE filesystem server that can be managed\n+ *   by systemd\n+ * - Enables on-demand filesystem mounting via socket activation\n+ * - Demonstrates requesting resources from the mount-caller's environment\n+ * - Allows running FUSE servers with minimal privileges; isolated mount,\n+ *   network, and pid namespaces; and a separate uid/gid (unlike traditional\n+ *   FUSE which needs mount permissions and runs in the caller's environment)\n+ *\n+ * Compile with:\n+ *\n+ *     gcc -Wall single_file.c service_ll.c `pkg-config fuse3 --cflags --libs` -o service_ll\n+ *\n+ * Note: If the pkg-config command fails due to the absence of the fuse3.pc\n+ *     file, you should configure the path to the fuse3.pc file in the\n+ *     PKG_CONFIG_PATH variable.\n+ *\n+ * Change the ExecStart line in service_ll@.service:\n+ *\n+ *     ExecStart=/path/to/service_ll\n+ *\n+ * to point to the actual path of the service_ll binary.\n+ *\n+ * Finally, install the service_ll@.service and service_ll.socket files to the\n+ * systemd service directory, usually /run/systemd/system.  Run these commands\n+ * to activate:\n+ *\n+ *     systemctl daemon-reload\n+ *     systemctl start service_ll.socket\n+ *\n+ * Then mount with:\n+ *\n+ *     mount -t fuse.service_ll /dev/sda /mnt\n+ *\n+ * ## Source code ##\n+ * \\include service_ll.c\n+ * \\include service_ll.socket\n+ * \\include service_ll@.service\n+ * \\include single_file.c\n+ * \\include single_file.h\n+ */\n+\n+#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19)\n+\n+#ifndef _GNU_SOURCE\n+#define _GNU_SOURCE\n+#endif\n+\n+#include <fuse_lowlevel.h>\n+#include <fuse_service.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <errno.h>\n+#include <fcntl.h>\n+#include <unistd.h>\n+#include <assert.h>\n+#include <pthread.h>\n+#include \"single_file.h\"\n+\n+struct service_ll {\n+\tstruct fuse_session *se;\n+\tchar *device;\n+\tstruct fuse_service *service;\n+\n+\t/* really booleans */\n+\tint debug;\n+};\n+\n+static struct service_ll ll = { };\n+\n+static void service_ll_init(void *userdata, struct fuse_conn_info *conn)\n+{\n+\t(void)userdata;\n+\n+\tconn->time_gran = 1;\n+}\n+\n+static void service_ll_read(fuse_req_t req, fuse_ino_t ino, size_t count,\n+\t\t\t    off_t pos, struct fuse_file_info *fi)\n+{\n+\tvoid *buf = NULL;\n+\tssize_t got;\n+\tint ret;\n+\n+\tif (!is_single_file_ino(ino)) {\n+\t\tret = EIO;\n+\t\tgoto out_reply;\n+\t}\n+\n+\tif (ll.debug)\n+\t\tfprintf(stderr, \"%s: pos 0x%llx count 0x%llx\\n\",\n+\t\t\t__func__,\n+\t\t\t(unsigned long long)pos,\n+\t\t\t(unsigned long long)count);\n+\n+\tif (!single_file.allow_dio && fi->direct_io) {\n+\t\tret = ENOSYS;\n+\t\tgoto out_reply;\n+\t}\n+\n+\tsingle_file_check_read(pos, &count);\n+\n+\tif (!count) {\n+\t\tfuse_reply_buf(req, buf, 0);\n+\t\treturn;\n+\t}\n+\n+\tbuf = malloc(count);\n+\tif (!buf) {\n+\t\tret = ENOMEM;\n+\t\tgoto out_reply;\n+\t}\n+\n+\tgot = single_file_pread(buf, count, pos);\n+\tif (got < 0) {\n+\t\tret = -got;\n+\t\tgoto out_reply;\n+\t}\n+\n+\tfuse_reply_buf(req, buf, got);\n+\tgoto out_buf;\n+\n+out_reply:\n+\tfuse_reply_err(req, ret);\n+out_buf:\n+\tfree(buf);\n+}\n+\n+static void service_ll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,\n+\t\t\t     size_t count, off_t pos,\n+\t\t\t     struct fuse_file_info *fi)\n+{\n+\tssize_t got;\n+\tint ret;\n+\n+\tif (!is_single_file_ino(ino)) {\n+\t\tret = EIO;\n+\t\tgoto out_reply;\n+\t}\n+\n+\tif (ll.debug)\n+\t\tfprintf(stderr, \"%s: pos 0x%llx count 0x%llx\\n\",\n+\t\t\t__func__,\n+\t\t\t(unsigned long long)pos,\n+\t\t\t(unsigned long long)count);\n+\n+\tif (!single_file.allow_dio && fi->direct_io) {\n+\t\tret = ENOSYS;\n+\t\tgoto out_reply;\n+\t}\n+\n+\tret = -single_file_check_write(pos, &count);\n+\tif (ret)\n+\t\tgoto out_reply;\n+\n+\tif (!count) {\n+\t\tfuse_reply_write(req, 0);\n+\t\treturn;\n+\t}\n+\n+\tgot = single_file_pwrite(buf, count, pos);\n+\tif (got < 0) {\n+\t\tret = -got;\n+\t\tgoto out_reply;\n+\t}\n+\n+\tfuse_reply_write(req, got);\n+\treturn;\n+\n+out_reply:\n+\tfuse_reply_err(req, ret);\n+}\n+\n+static const struct fuse_lowlevel_ops service_ll_oper = {\n+\t.lookup\t\t= single_file_ll_lookup,\n+\t.getattr\t= single_file_ll_getattr,\n+\t.setattr\t= single_file_ll_setattr,\n+\t.readdir\t= single_file_ll_readdir,\n+\t.open\t\t= single_file_ll_open,\n+\t.statfs\t\t= single_file_ll_statfs,\n+\t.statx\t\t= single_file_ll_statx,\n+\t.fsync\t\t= single_file_ll_fsync,\n+\n+\t.init\t\t= service_ll_init,\n+\t.read\t\t= service_ll_read,\n+\t.write\t\t= service_ll_write,\n+};\n+\n+#define SERVICE_LL_OPT(t, p, v) { t, offsetof(struct service_ll, p), v }\n+\n+static struct fuse_opt service_ll_opts[] = {\n+\tSERVICE_LL_OPT(\"debug\",\t\tdebug,\t\t\t1),\n+\tSINGLE_FILE_OPT_KEYS,\n+\tFUSE_OPT_END\n+};\n+\n+static int service_ll_opt_proc(void *data, const char *arg, int key,\n+\t\t\t\t struct fuse_args *outargs)\n+{\n+\tint ret = single_file_opt_proc(data, arg, key, outargs);\n+\n+\tif (ret < 1)\n+\t\treturn ret;\n+\n+\tswitch (key) {\n+\tcase FUSE_OPT_KEY_NONOPT:\n+\t\tif (!ll.device) {\n+\t\t\tll.device = strdup(arg);\n+\t\t\treturn 0;\n+\t\t}\n+\t\treturn 1;\n+\t}\n+\n+\treturn 1;\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tstruct fuse_args args = FUSE_ARGS_INIT(argc, argv);\n+\tstruct fuse_cmdline_opts opts = { };\n+\tstruct fuse_loop_config *config = NULL;\n+\tint ret = 1;\n+\n+\tif (fuse_service_accept(&ll.service))\n+\t\tgoto err_args;\n+\n+\tif (!fuse_service_accepted(ll.service))\n+\t\tgoto err_args;\n+\n+\tif (fuse_service_append_args(ll.service, &args))\n+\t\tgoto err_service;\n+\n+\tif (fuse_opt_parse(&args, &ll, service_ll_opts, service_ll_opt_proc))\n+\t\tgoto err_service;\n+\n+\tif (fuse_service_parse_cmdline_opts(&args, &opts))\n+\t\tgoto err_service;\n+\n+\tif (opts.show_help) {\n+\t\tprintf(\"usage: %s [options] <device> <mountpoint>\\n\\n\", argv[0]);\n+\t\tfuse_cmdline_help();\n+\t\tfuse_lowlevel_help();\n+\t\tret = 0;\n+\t\tgoto err_service;\n+\t} else if (opts.show_version) {\n+\t\tprintf(\"FUSE library version %s\\n\", fuse_pkgversion());\n+\t\tfuse_lowlevel_version();\n+\t\tret = 0;\n+\t\tgoto err_service;\n+\t}\n+\n+\tif (!opts.mountpoint || !ll.device) {\n+\t\tprintf(\"usage: %s [options] <device> <mountpoint>\\n\", argv[0]);\n+\t\tprintf(\"       %s --help\\n\", argv[0]);\n+\t\tgoto err_service;\n+\t}\n+\n+\tif (single_file_service_open(ll.service, ll.device))\n+\t\tgoto err_service;\n+\n+\tif (fuse_service_finish_file_requests(ll.service))\n+\t\tgoto err_singlefile;\n+\n+\tif (single_file_configure(ll.device, NULL))\n+\t\tgoto err_singlefile;\n+\n+\tll.se = fuse_session_new(&args, &service_ll_oper,\n+\t\t\t\t sizeof(service_ll_oper), NULL);\n+\tif (ll.se == NULL)\n+\t\tgoto err_singlefile;\n+\n+\tif (!opts.singlethread) {\n+\t\tconfig = fuse_loop_cfg_create();\n+\t\tif (!config) {\n+\t\t\tret = 1;\n+\t\t\tgoto err_session;\n+\t\t}\n+\t}\n+\n+\tif (fuse_set_signal_handlers(ll.se))\n+\t\tgoto err_loopcfg;\n+\n+\tif (fuse_service_session_mount(ll.service, ll.se, S_IFDIR, &opts))\n+\t\tgoto err_signals;\n+\n+\t/* Block until ctrl+c or fusermount -u */\n+\tif (opts.singlethread) {\n+\t\tfuse_service_send_goodbye(ll.service, 0);\n+\t\tfuse_service_release(ll.service);\n+\t\tret = fuse_session_loop(ll.se);\n+\t} else {\n+\t\tfuse_loop_cfg_set_clone_fd(config, opts.clone_fd);\n+\t\tfuse_loop_cfg_set_max_threads(config, opts.max_threads);\n+\n+\t\tfuse_service_send_goodbye(ll.service, 0);\n+\t\tfuse_service_release(ll.service);\n+\t\tret = fuse_session_loop_mt(ll.se, config);\n+\t}\n+\n+err_signals:\n+\tfuse_remove_signal_handlers(ll.se);\n+err_loopcfg:\n+\tfuse_loop_cfg_destroy(config);\n+err_session:\n+\tfuse_session_destroy(ll.se);\n+err_singlefile:\n+\tsingle_file_close();\n+err_service:\n+\tfree(opts.mountpoint);\n+\tfree(ll.device);\n+\tfuse_service_send_goodbye(ll.service, ret);\n+\tfuse_service_destroy(&ll.service);\n+err_args:\n+\tfuse_opt_free_args(&args);\n+\treturn fuse_service_exit(ret);\n+}\ndiff --git a/example/service_ll.socket.in b/example/service_ll.socket.in\nnew file mode 100644\nindex 00000000000000..c41c382878a0cd\n--- /dev/null\n+++ b/example/service_ll.socket.in\n@@ -0,0 +1,15 @@\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+#\n+# Copyright (C) 2026 Oracle.  All Rights Reserved.\n+# Author: Darrick J. Wong <djwong@kernel.org>\n+[Unit]\n+Description=Socket for service_ll Service\n+\n+[Socket]\n+ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/service_ll\n+Accept=yes\n+SocketMode=@FUSE_SERVICE_SOCKET_PERMS@\n+RemoveOnStop=yes\n+\n+[Install]\n+WantedBy=sockets.target\ndiff --git a/example/service_ll@.service b/example/service_ll@.service\nnew file mode 100644\nindex 00000000000000..016d839babe3cc\n--- /dev/null\n+++ b/example/service_ll@.service\n@@ -0,0 +1,102 @@\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+#\n+# Copyright (C) 2026 Oracle.  All Rights Reserved.\n+# Author: Darrick J. Wong <djwong@kernel.org>\n+[Unit]\n+Description=service_ll Sample Fuse Service\n+\n+# Don't leave failed units behind, systemd does not clean them up!\n+CollectMode=inactive-or-failed\n+\n+[Service]\n+Type=exec\n+ExecStart=/path/to/service_ll\n+\n+# Try to capture core dumps\n+LimitCORE=infinity\n+\n+SyslogIdentifier=%N\n+\n+# No realtime CPU scheduling\n+RestrictRealtime=true\n+\n+# Don't let us see anything in the regular system, and don't run as root\n+DynamicUser=true\n+ProtectSystem=strict\n+ProtectHome=true\n+PrivateTmp=true\n+PrivateDevices=true\n+PrivateUsers=true\n+\n+# No network access\n+PrivateNetwork=true\n+ProtectHostname=true\n+RestrictAddressFamilies=none\n+IPAddressDeny=any\n+\n+# Don't let the program mess with the kernel configuration at all\n+ProtectKernelLogs=true\n+ProtectKernelModules=true\n+ProtectKernelTunables=true\n+ProtectControlGroups=true\n+ProtectProc=invisible\n+RestrictNamespaces=true\n+RestrictFileSystems=\n+\n+# Hide everything in /proc, even /proc/mounts\n+ProcSubset=pid\n+\n+# Only allow the default personality Linux\n+LockPersonality=true\n+\n+# No writable memory pages\n+MemoryDenyWriteExecute=true\n+\n+# Don't let our mounts leak out to the host\n+PrivateMounts=true\n+\n+# Restrict system calls to the native arch and only enough to get things going\n+SystemCallArchitectures=native\n+SystemCallFilter=@system-service\n+SystemCallFilter=~@privileged\n+SystemCallFilter=~@resources\n+\n+SystemCallFilter=~@clock\n+SystemCallFilter=~@cpu-emulation\n+SystemCallFilter=~@debug\n+SystemCallFilter=~@module\n+SystemCallFilter=~@reboot\n+SystemCallFilter=~@swap\n+\n+SystemCallFilter=~@mount\n+\n+# libfuse io_uring wants to pin cores and memory\n+SystemCallFilter=mbind\n+SystemCallFilter=sched_setaffinity\n+\n+# Leave a breadcrumb if we get whacked by the system call filter\n+SystemCallErrorNumber=EL3RST\n+\n+# Log to the kernel dmesg, just like an in-kernel filesystem driver\n+StandardOutput=append:/dev/ttyprintk\n+StandardError=append:/dev/ttyprintk\n+\n+# Run with no capabilities at all\n+CapabilityBoundingSet=\n+AmbientCapabilities=\n+NoNewPrivileges=true\n+\n+# We don't create files\n+UMask=7777\n+\n+# No access to hardware /dev files at all\n+ProtectClock=true\n+DevicePolicy=closed\n+\n+# Don't mess with set[ug]id anything.\n+RestrictSUIDSGID=true\n+\n+# Don't let OOM kills of processes in this containment group kill the whole\n+# service, because we don't want filesystem drivers to go down.\n+OOMPolicy=continue\n+OOMScoreAdjust=-1000\ndiff --git a/example/single_file.c b/example/single_file.c\nnew file mode 100644\nindex 00000000000000..9b6f76504686b5\n--- /dev/null\n+++ b/example/single_file.c\n@@ -0,0 +1,746 @@\n+/*\n+ * FUSE: Filesystem in Userspace\n+ * Copyright (C) 2026 Oracle.\n+ *\n+ * This program can be distributed under the terms of the GNU GPLv2.\n+ * See the file GPL2.txt.\n+ *\n+ * This file is shared library code for example fuse servers that want to\n+ * expose a single regular file that wraps another file in a manner that goes\n+ * beyond simple passthrough.  It is not itself a fuse server.\n+ */\n+#define _GNU_SOURCE\n+#include <pthread.h>\n+#include <errno.h>\n+#include <stdlib.h>\n+#include <errno.h>\n+#include <stdio.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <sys/ioctl.h>\n+#include <sys/stat.h>\n+#ifdef __linux__\n+#include <linux/fs.h>\n+#include <linux/stat.h>\n+#endif\n+\n+#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))\n+\n+#include \"fuse_lowlevel.h\"\n+#include \"fuse_service.h\"\n+#include \"single_file.h\"\n+\n+#define min(x, y) ((x) < (y) ? (x) : (y))\n+\n+#if __has_attribute(__fallthrough__)\n+#define fallthrough __attribute__((__fallthrough__))\n+#else\n+#define fallthrough do {} while (0)\n+#endif\n+\n+struct dirbuf {\n+\tchar *p;\n+\tsize_t size;\n+};\n+\n+struct single_file_stat {\n+\tstruct fuse_entry_param entry;\n+};\n+\n+#define SINGLE_FILE_INO\t\t(FUSE_ROOT_ID + 1)\n+\n+static const char *single_file_name = \"single_file\";\n+static bool single_file_name_set;\n+static struct timespec startup_time;\n+\n+struct single_file single_file = {\n+\t.backing_fd = -1,\n+\t.allow_dio = true,\n+\t.mode = S_IFREG | 0444,\n+\t.lock = PTHREAD_MUTEX_INITIALIZER,\n+};\n+\n+static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,\n+\t\t       fuse_ino_t ino)\n+{\n+\tstruct stat stbuf;\n+\tsize_t oldsize = b->size;\n+\n+\tb->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);\n+\tb->p = (char *) realloc(b->p, b->size);\n+\tmemset(&stbuf, 0, sizeof(stbuf));\n+\tstbuf.st_ino = ino;\n+\tfuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,\n+\t\t\t  b->size);\n+}\n+\n+int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,\n+\t\t      off_t off, size_t maxsize)\n+{\n+\tif (off < bufsize)\n+\t\treturn fuse_reply_buf(req, buf + off,\n+\t\t\t\t      min(bufsize - off, maxsize));\n+\telse\n+\t\treturn fuse_reply_buf(req, NULL, 0);\n+}\n+\n+bool is_single_file_child(fuse_ino_t parent, const char *name)\n+{\n+\treturn  parent == FUSE_ROOT_ID &&\n+\t\tstrcmp(name, single_file_name) == 0;\n+}\n+\n+bool is_single_file_ino(fuse_ino_t ino)\n+{\n+\treturn ino == SINGLE_FILE_INO;\n+}\n+\n+void single_file_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,\n+\t\t\t    off_t off, struct fuse_file_info *fi)\n+{\n+\tstruct dirbuf b;\n+\n+\t(void) fi;\n+\n+\tswitch (ino) {\n+\tcase FUSE_ROOT_ID:\n+\t\tbreak;\n+\tcase SINGLE_FILE_INO:\n+\t\tfuse_reply_err(req, ENOTDIR);\n+\t\treturn;\n+\tdefault:\n+\t\tfuse_reply_err(req, ENOENT);\n+\t\treturn;\n+\t}\n+\n+\tmemset(&b, 0, sizeof(b));\n+\tdirbuf_add(req, &b, \".\", FUSE_ROOT_ID);\n+\tdirbuf_add(req, &b, \"..\", FUSE_ROOT_ID);\n+\tdirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);\n+\treply_buf_limited(req, b.p, b.size, off, size);\n+\tfree(b.p);\n+}\n+\n+static bool sf_stat(fuse_ino_t ino, struct single_file_stat *llstat)\n+{\n+\tstruct fuse_entry_param *entry = &llstat->entry;\n+\tstruct stat *stbuf = &entry->attr;\n+\n+\tif (ino == FUSE_ROOT_ID) {\n+\t\tstbuf->st_mode = S_IFDIR | 0555;\n+\t\tstbuf->st_nlink = 2;\n+\t\tstbuf->st_atim = startup_time;\n+\t\tstbuf->st_mtim = startup_time;\n+\t\tstbuf->st_ctim = startup_time;\n+\t} else if (ino == SINGLE_FILE_INO) {\n+\t\tstbuf->st_mode = single_file.mode;\n+\t\tstbuf->st_nlink = 1;\n+\t\tstbuf->st_size = single_file.isize;\n+\t\tstbuf->st_blksize = single_file.blocksize;\n+\t\tstbuf->st_blocks = howmany(single_file.isize, 512);\n+\t\tstbuf->st_atim = single_file.atime;\n+\t\tstbuf->st_mtim = single_file.mtime;\n+\t\tstbuf->st_ctim = single_file.ctime;\n+\t} else {\n+\t\treturn false;\n+\t}\n+\tstbuf->st_ino = ino;\n+\n+\tentry->generation = ino + 1;\n+\tentry->attr_timeout = 0.0;\n+\tentry->entry_timeout = 0.0;\n+\tentry->ino = ino;\n+\n+\treturn true;\n+}\n+\n+#if defined(STATX_BASIC_STATS)\n+static inline void sf_set_statx_attr(struct statx *stx,\n+\t\t\t\t     uint64_t statx_flag, int set)\n+{\n+\tif (set)\n+\t\tstx->stx_attributes |= statx_flag;\n+\tstx->stx_attributes_mask |= statx_flag;\n+}\n+\n+static void sf_statx_directio(struct statx *stx)\n+{\n+\tstruct statx devx;\n+\tint ret;\n+\n+\tret = statx(single_file.backing_fd, \"\", AT_EMPTY_PATH, STATX_DIOALIGN,\n+\t\t    &devx);\n+\tif (ret)\n+\t\treturn;\n+\tif (!(devx.stx_mask & STATX_DIOALIGN))\n+\t\treturn;\n+\n+\tstx->stx_mask |= STATX_DIOALIGN;\n+\tstx->stx_dio_mem_align = devx.stx_dio_mem_align;\n+\tstx->stx_dio_offset_align = devx.stx_dio_offset_align;\n+}\n+\n+static bool sf_statx(fuse_ino_t ino, int statx_mask, struct statx *stx)\n+{\n+\t(void)statx_mask;\n+\n+\tif (ino == FUSE_ROOT_ID) {\n+\t\tstx->stx_mask = STATX_BASIC_STATS | STATX_BTIME;\n+\t\tstx->stx_mode = S_IFDIR | 0555;\n+\t\tstx->stx_nlink = 2;\n+\t\tstx->stx_atime.tv_sec = startup_time.tv_sec;\n+\t\tstx->stx_atime.tv_nsec = startup_time.tv_nsec;\n+\t\tstx->stx_mtime.tv_sec = startup_time.tv_sec;\n+\t\tstx->stx_mtime.tv_nsec = startup_time.tv_nsec;\n+\t\tstx->stx_ctime.tv_sec = startup_time.tv_sec;\n+\t\tstx->stx_ctime.tv_nsec = startup_time.tv_nsec;\n+\t\tstx->stx_btime.tv_sec = startup_time.tv_sec;\n+\t\tstx->stx_btime.tv_nsec = startup_time.tv_nsec;\n+\t} else if (ino == SINGLE_FILE_INO) {\n+\t\tstx->stx_mask = STATX_BASIC_STATS | STATX_BTIME;\n+\t\tstx->stx_mode = single_file.mode;\n+\t\tstx->stx_nlink = 1;\n+\t\tstx->stx_size = single_file.isize;\n+\t\tstx->stx_blksize = single_file.blocksize;\n+\t\tstx->stx_blocks = howmany(single_file.isize, 512);\n+\t\tstx->stx_atime.tv_sec = single_file.atime.tv_sec;\n+\t\tstx->stx_atime.tv_nsec = single_file.atime.tv_nsec;\n+\t\tstx->stx_mtime.tv_sec = single_file.mtime.tv_sec;\n+\t\tstx->stx_mtime.tv_nsec = single_file.mtime.tv_nsec;\n+\t\tstx->stx_ctime.tv_sec = single_file.ctime.tv_sec;\n+\t\tstx->stx_ctime.tv_nsec = single_file.ctime.tv_nsec;\n+\t\tstx->stx_btime.tv_sec = startup_time.tv_sec;\n+\t\tstx->stx_btime.tv_nsec = startup_time.tv_nsec;\n+\t} else {\n+\t\treturn false;\n+\t}\n+\tstx->stx_ino = ino;\n+\n+\tsf_set_statx_attr(stx, STATX_ATTR_IMMUTABLE, single_file.ro);\n+\tsf_statx_directio(stx);\n+\n+\treturn true;\n+}\n+\n+void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,\n+\t\t\t  struct fuse_file_info *fi)\n+{\n+\tstruct statx stx = { };\n+\tbool filled;\n+\n+\t(void)fi;\n+\n+\tif ((flags & AT_STATX_FORCE_SYNC) && is_single_file_ino(ino) &&\n+\t    single_file.backing_fd >= 0) {\n+\t\tint ret = fsync(single_file.backing_fd);\n+\n+\t\tif (ret) {\n+\t\t\tfuse_reply_err(req, errno);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\tpthread_mutex_lock(&single_file.lock);\n+\tfilled = sf_statx(ino, mask, &stx);\n+\tpthread_mutex_unlock(&single_file.lock);\n+\tif (!filled)\n+\t\tfuse_reply_err(req, ENOENT);\n+\telse\n+\t\tfuse_reply_statx(req, 0, &stx, 0.0);\n+}\n+#else\n+void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,\n+\t\t\t  struct fuse_file_info *fi)\n+{\n+\tfuse_reply_err(req, ENOSYS);\n+}\n+#endif /* STATX_BASIC_STATS */\n+\n+void single_file_ll_statfs(fuse_req_t req, fuse_ino_t ino)\n+{\n+\tstruct statvfs buf;\n+\n+\t(void)ino;\n+\n+\tpthread_mutex_lock(&single_file.lock);\n+\tbuf.f_bsize = single_file.blocksize;\n+\tbuf.f_frsize = 0;\n+\n+\tbuf.f_blocks = single_file.blocks;\n+\tbuf.f_bfree = 0;\n+\tbuf.f_bavail = 0;\n+\tbuf.f_files = 1;\n+\tbuf.f_ffree = 0;\n+\tbuf.f_favail = 0;\n+\tbuf.f_fsid = 0x50C00L;\n+\tbuf.f_flag = 0;\n+\tif (single_file.ro)\n+\t\tbuf.f_flag |= ST_RDONLY;\n+\tbuf.f_namemax = 255;\n+\tpthread_mutex_unlock(&single_file.lock);\n+\n+\tfuse_reply_statfs(req, &buf);\n+}\n+\n+void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino,\n+\t\t\t    struct fuse_file_info *fi)\n+{\n+\tstruct single_file_stat llstat;\n+\tbool filled;\n+\n+\t(void) fi;\n+\n+\tmemset(&llstat, 0, sizeof(llstat));\n+\tpthread_mutex_lock(&single_file.lock);\n+\tfilled = sf_stat(ino, &llstat);\n+\tpthread_mutex_unlock(&single_file.lock);\n+\tif (!filled)\n+\t\tfuse_reply_err(req, ENOENT);\n+\telse\n+\t\tfuse_reply_attr(req, &llstat.entry.attr,\n+\t\t\t\tllstat.entry.attr_timeout);\n+}\n+\n+static void get_now(struct timespec *now)\n+{\n+#ifdef CLOCK_REALTIME\n+\tif (!clock_gettime(CLOCK_REALTIME, now))\n+\t\treturn;\n+#endif\n+\n+\tnow->tv_sec = time(NULL);\n+\tnow->tv_nsec = 0;\n+}\n+\n+void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,\n+\t\t\t    int to_set, struct fuse_file_info *fi)\n+{\n+\tstruct timespec now;\n+\n+\tif (ino != SINGLE_FILE_INO)\n+\t\tgoto deny;\n+\tif (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID |\n+\t\t      FUSE_SET_ATTR_SIZE))\n+\t\tgoto deny;\n+\tif (single_file.ro)\n+\t\tgoto deny;\n+\n+\tget_now(&now);\n+\n+\tpthread_mutex_lock(&single_file.lock);\n+\tif (to_set & FUSE_SET_ATTR_MODE)\n+\t\tsingle_file.mode = (single_file.mode & S_IFMT) |\n+\t\t\t\t   (attr->st_mode & ~S_IFMT);\n+\tif (to_set & FUSE_SET_ATTR_ATIME) {\n+\t\tif (to_set & FUSE_SET_ATTR_ATIME_NOW)\n+\t\t\tsingle_file.atime = now;\n+\t\telse\n+\t\t\tsingle_file.atime = attr->st_atim;\n+\t}\n+\tif (to_set & FUSE_SET_ATTR_MTIME) {\n+\t\tif (to_set & FUSE_SET_ATTR_MTIME_NOW)\n+\t\t\tsingle_file.mtime = now;\n+\t\telse\n+\t\t\tsingle_file.mtime = attr->st_mtim;\n+\t}\n+\tif (to_set & FUSE_SET_ATTR_CTIME)\n+\t\tsingle_file.ctime = now;\n+\tpthread_mutex_unlock(&single_file.lock);\n+\n+\tsingle_file_ll_getattr(req, ino, fi);\n+\treturn;\n+deny:\n+\tfuse_reply_err(req, EPERM);\n+}\n+\n+void single_file_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)\n+{\n+\tstruct single_file_stat llstat;\n+\tbool filled;\n+\n+\tif (!is_single_file_child(parent, name)) {\n+\t\tfuse_reply_err(req, ENOENT);\n+\t\treturn;\n+\t}\n+\n+\tmemset(&llstat, 0, sizeof(llstat));\n+\tpthread_mutex_lock(&single_file.lock);\n+\tfilled = sf_stat(SINGLE_FILE_INO, &llstat);\n+\tpthread_mutex_unlock(&single_file.lock);\n+\tif (!filled)\n+\t\tfuse_reply_err(req, ENOENT);\n+\telse\n+\t\tfuse_reply_entry(req, &llstat.entry);\n+}\n+\n+void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,\n+\t\t\t struct fuse_file_info *fi)\n+{\n+\tif (ino != SINGLE_FILE_INO)\n+\t\tfuse_reply_err(req, EISDIR);\n+\telse if (single_file.ro && (fi->flags & O_ACCMODE) != O_RDONLY)\n+\t\tfuse_reply_err(req, EROFS);\n+\telse\n+\t\tfuse_reply_open(req, fi);\n+}\n+\n+void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,\n+\t\t\t  struct fuse_file_info *fi)\n+{\n+\tint ret = 0;\n+\n+\t(void)datasync;\n+\t(void)fi;\n+\n+\tif (ino == SINGLE_FILE_INO) {\n+\t\tret = fsync(single_file.backing_fd);\n+\t\tif (ret)\n+\t\t\tret = errno;\n+\t}\n+\n+\tfuse_reply_err(req, ret);\n+}\n+\n+unsigned long long parse_num_blocks(const char *arg, int log_block_size)\n+{\n+\tchar *p;\n+\tunsigned long long num;\n+\n+\tnum = strtoull(arg, &p, 0);\n+\n+\tif (p[0] && p[1])\n+\t\treturn 0;\n+\n+\tswitch (*p) {\n+\tcase 'T': case 't':\n+\t\tnum <<= 10;\n+\t\tfallthrough;\n+\tcase 'G': case 'g':\n+\t\tnum <<= 10;\n+\t\tfallthrough;\n+\tcase 'M': case 'm':\n+\t\tnum <<= 10;\n+\t\tfallthrough;\n+\tcase 'K': case 'k':\n+\t\tif (log_block_size < 0)\n+\t\t\tnum <<= 10;\n+\t\telse\n+\t\t\tnum >>= log_block_size;\n+\t\tbreak;\n+\tcase 's':\n+\t\tif (log_block_size < 0)\n+\t\t\tnum <<= 9;\n+\t\telse\n+\t\t\tnum >>= (1+log_block_size);\n+\t\tbreak;\n+\tcase '\\0':\n+\t\tbreak;\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+\treturn num;\n+}\n+\n+static int single_file_set_blocksize(const char *arg)\n+{\n+\tunsigned long long l = parse_num_blocks(arg, -1);\n+\n+\tif (l < 512 || l > INT32_MAX || (l & (l - 1)) != 0) {\n+\t\tfprintf(stderr, \"%s: block size must be power of two between 512 and 2G.\\n\",\n+\t\t\targ);\n+\t\treturn -1;\n+\t}\n+\n+\t/* do not pass through to libfuse */\n+\tsingle_file.blocksize = l;\n+\treturn 0;\n+}\n+\n+static int single_file_set_size(const char *arg)\n+{\n+\tunsigned long long l = parse_num_blocks(arg, -1);\n+\n+\tif (l < 1 || (l & 511) != 0 || l > INT64_MAX) {\n+\t\tfprintf(stderr, \"%s: size must be multiple of 512 and larger than zero.\\n\",\n+\t\t\targ);\n+\t\treturn -1;\n+\t}\n+\n+\t/* do not pass through to libfuse */\n+\tsingle_file.isize = l;\n+\treturn 0;\n+}\n+\n+int single_file_opt_proc(void *data, const char *arg, int key,\n+\t\t\t struct fuse_args *outargs)\n+{\n+\t(void)data;\n+\t(void)outargs;\n+\n+\tswitch (key) {\n+\tcase SINGLE_FILE_RO:\n+\t\t/* pass through to libfuse */\n+\t\tsingle_file.ro = true;\n+\t\treturn 1;\n+\tcase SINGLE_FILE_RW:\n+\t\t/* pass through to libfuse */\n+\t\tsingle_file.ro = false;\n+\t\treturn 1;\n+\tcase SINGLE_FILE_REQUIRE_BDEV:\n+\t\tsingle_file.require_bdev = true;\n+\t\treturn 0;\n+\tcase SINGLE_FILE_DIO:\n+\t\tsingle_file.allow_dio = true;\n+\t\treturn 0;\n+\tcase SINGLE_FILE_NODIO:\n+\t\tsingle_file.allow_dio = false;\n+\t\treturn 0;\n+\tcase SINGLE_FILE_SYNC:\n+\t\tsingle_file.sync = true;\n+\t\treturn 0;\n+\tcase SINGLE_FILE_NOSYNC:\n+\t\tsingle_file.sync = false;\n+\t\treturn 0;\n+\tcase SINGLE_FILE_BLOCKSIZE:\n+\t\treturn single_file_set_blocksize(arg + 10);\n+\tcase SINGLE_FILE_SIZE:\n+\t\treturn single_file_set_size(arg + 5);\n+\t}\n+\n+\treturn 1;\n+}\n+\n+int single_file_service_open(struct fuse_service *sf, const char *path)\n+{\n+\tint open_flags = single_file.ro ? O_RDONLY : O_RDWR;\n+\tint fd;\n+\tint ret;\n+\n+again:\n+\tif (single_file.require_bdev)\n+\t\tret = fuse_service_request_blockdev(sf, path,\n+\t\t\t\t\t\t    open_flags | O_EXCL, 0, 0,\n+\t\t\t\t\t\t    single_file.blocksize);\n+\telse\n+\t\tret = fuse_service_request_file(sf, path, open_flags | O_EXCL,\n+\t\t\t\t\t\t0, 0);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (!single_file.ro && open_flags == O_RDONLY)\n+\t\tsingle_file.ro = true;\n+\n+\tret = fuse_service_receive_file(sf, path, &fd);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* downgrade from rw to ro if necessary */\n+\tif ((fd == -EPERM || fd == -EACCES || fd == -EROFS) &&\n+\t    open_flags == O_RDWR) {\n+\t\topen_flags = O_RDONLY;\n+\t\tgoto again;\n+\t}\n+\n+\tif (fd < 0) {\n+\t\tfprintf(stderr, \"%s: opening file: %s.\\n\",\n+\t\t\tpath, strerror(-fd));\n+\t\treturn -1;\n+\t}\n+\n+\tsingle_file.backing_fd = fd;\n+\treturn 0;\n+}\n+\n+int single_file_check_write(off_t pos, size_t *count)\n+{\n+\tif (pos >= single_file.isize)\n+\t\treturn -EFBIG;\n+\n+\tif (*count > single_file.isize)\n+\t\t*count = single_file.isize;\n+\tif (pos >= single_file.isize - *count)\n+\t\t*count = single_file.isize - pos;\n+\n+\treturn 0;\n+}\n+\n+void single_file_check_read(off_t pos, size_t *count)\n+{\n+\tint ret = single_file_check_write(pos, count);\n+\n+\tif (ret)\n+\t\t*count = 0;\n+}\n+\n+ssize_t single_file_pwrite(const char *buf, size_t count, off_t pos)\n+{\n+\tssize_t processed = 0;\n+\tssize_t got;\n+\n+\twhile ((got = pwrite(single_file.backing_fd, buf, count, pos)) > 0) {\n+\t\tprocessed += got;\n+\t\tpos += got;\n+\t\tbuf += got;\n+\t\tcount -= got;\n+\t}\n+\n+\tif (processed > 0) {\n+\t\tstruct timespec now;\n+\n+\t\tif (single_file.sync) {\n+\t\t\tint ret = fsync(single_file.backing_fd);\n+\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn -errno;\n+\t\t}\n+\n+\t\tget_now(&now);\n+\n+\t\tpthread_mutex_lock(&single_file.lock);\n+\t\tsingle_file.mtime = now;\n+\t\tsingle_file.ctime = now;\n+\t\tpthread_mutex_unlock(&single_file.lock);\n+\n+\t\treturn processed;\n+\t}\n+\n+\tif (got < 0)\n+\t\treturn -errno;\n+\treturn 0;\n+}\n+\n+ssize_t single_file_pread(char *buf, size_t count, off_t pos)\n+{\n+\tssize_t processed = 0;\n+\tssize_t got;\n+\n+\twhile ((got = pread(single_file.backing_fd, buf, count, pos)) > 0) {\n+\t\tprocessed += got;\n+\t\tpos += got;\n+\t\tbuf += got;\n+\t\tcount -= got;\n+\t}\n+\n+\tif (processed)\n+\t\treturn processed;\n+\tif (got < 0)\n+\t\treturn -errno;\n+\treturn 0;\n+}\n+\n+int single_file_configure(const char *device, const char *filename)\n+{\n+\tstruct stat stbuf;\n+\tunsigned long long backing_size;\n+\tunsigned int proposed_blocksize;\n+\tint lbasize;\n+\tint ret;\n+\n+\tret = fstat(single_file.backing_fd, &stbuf);\n+\tif (ret) {\n+\t\tperror(device);\n+\t\treturn -1;\n+\t}\n+\tlbasize = stbuf.st_blksize;\n+\tbacking_size = stbuf.st_size;\n+\n+\tif (S_ISBLK(stbuf.st_mode)) {\n+#ifdef BLKSSZGET\n+\t\tret = ioctl(single_file.backing_fd, BLKSSZGET, &lbasize);\n+\t\tif (ret) {\n+\t\t\tperror(device);\n+\t\t\treturn -1;\n+\t\t}\n+#endif\n+\n+#ifdef BLKGETSIZE64\n+\t\tret = ioctl(single_file.backing_fd, BLKGETSIZE64, &backing_size);\n+\t\tif (ret) {\n+\t\t\tperror(device);\n+\t\t\treturn -1;\n+\t\t}\n+#endif\n+\t}\n+\n+\tif (backing_size == 0) {\n+\t\tfprintf(stderr, \"%s: backing file size zero?\\n\", device);\n+\t\treturn -1;\n+\t}\n+\n+\tif (lbasize == 0) {\n+\t\tfprintf(stderr, \"%s: blocksize zero?\\n\", device);\n+\t\treturn -1;\n+\t}\n+\n+\tproposed_blocksize = single_file.blocksize ? single_file.blocksize :\n+\t\t\t\t\t\t     sysconf(_SC_PAGESIZE);\n+\tif (lbasize > proposed_blocksize) {\n+\t\tfprintf(stderr, \"%s: lba size %d smaller than blocksize %u\\n\",\n+\t\t\tdevice, lbasize, proposed_blocksize);\n+\t\treturn -1;\n+\t}\n+\n+\tif (single_file.isize % proposed_blocksize > 0) {\n+\t\tfprintf(stderr, \"%s: size parameter %llu not congruent with blocksize %u\\n\",\n+\t\t\tdevice, (unsigned long long)single_file.isize,\n+\t\t\tproposed_blocksize);\n+\t\treturn -1;\n+\t}\n+\n+\tif (single_file.isize > backing_size) {\n+\t\tfprintf(stderr, \"%s: file size %llu smaller than size param %llu\\n\",\n+\t\t\tdevice, backing_size,\n+\t\t\t(unsigned long long)single_file.isize);\n+\t\treturn -1;\n+\t}\n+\n+\tif (!single_file.blocksize)\n+\t\tsingle_file.blocksize = proposed_blocksize;\n+\tif (!single_file.isize)\n+\t\tsingle_file.isize = backing_size;\n+\n+\tsingle_file.isize = round_down(single_file.isize, single_file.blocksize);\n+\tsingle_file.blocks = single_file.isize / single_file.blocksize;\n+\n+\treturn single_file_configure_simple(filename);\n+}\n+\n+int single_file_configure_simple(const char *filename)\n+{\n+\tif (!single_file.blocksize)\n+\t\tsingle_file.blocksize = sysconf(_SC_PAGESIZE);\n+\n+\tif (filename) {\n+\t\tchar *n = strdup(filename);\n+\n+\t\tif (!n) {\n+\t\t\tperror(filename);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (single_file_name_set)\n+\t\t\tfree((void *)single_file_name);\n+\t\tsingle_file_name = n;\n+\t\tsingle_file_name_set = true;\n+\t}\n+\n+\tget_now(&startup_time);\n+\tsingle_file.atime = startup_time;\n+\tsingle_file.mtime = startup_time;\n+\tsingle_file.ctime = startup_time;\n+\n+\tif (!single_file.ro)\n+\t\tsingle_file.mode |= 0220;\n+\n+\treturn 0;\n+}\n+\n+void single_file_close(void)\n+{\n+\tclose(single_file.backing_fd);\n+\tsingle_file.backing_fd = -1;\n+\n+\tif (single_file_name_set)\n+\t\tfree((void *)single_file_name);\n+\tsingle_file_name_set = false;\n+}\ndiff --git a/meson.build b/meson.build\nindex 827ec45ad3ad75..de038df8d92071 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -77,6 +77,7 @@ endif\n if service_socket_perms == ''\n   service_socket_perms = '0220'\n endif\n+private_cfg.set('FUSE_SERVICE_SOCKET_DIR_RAW', service_socket_dir)\n private_cfg.set_quoted('FUSE_SERVICE_SOCKET_DIR', service_socket_dir)\n private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms)\n \n","prefixes":["11/13"]}