├── AUTHORS ├── NEWS ├── ChangeLog ├── README ├── Makefile.am ├── fuseuring_main.h ├── .github └── workflows │ └── c-cpp.yml ├── bench.sh ├── configure.ac ├── main.cpp ├── config.h ├── m4 ├── m4-ax_check_compile_flag.m4 └── ax_pthread.m4 ├── fuse_io_context.cpp ├── COPYING ├── readme.md ├── fuse_io_context.h ├── fuse_kernel.h └── fuseuring_main.cpp /AUTHORS: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Please see readme.md. -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | bin_PROGRAMS = fuseuring 3 | 4 | fuseuring_SOURCES = fuse_io_context.cpp main.cpp fuseuring_main.cpp 5 | 6 | fuseuring_LDADD = $(PTHREAD_LIBS) -luring 7 | fuseuring_CXXFLAGS = $(PTHREAD_CFLAGS) -std=c++2a -D_FILE_OFFSET_BITS=64 8 | if WITH_FCOROUTINES 9 | fuseuring_CXXFLAGS += -fcoroutines 10 | endif 11 | 12 | noinst_HEADERS = fuse_io_context.h fuseuring_main.h fuse_kernel.h 13 | -------------------------------------------------------------------------------- /fuseuring_main.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | // Copyright (C) Martin Raiber 3 | #pragma once 4 | #include 5 | 6 | int fuseuring_main(int backing_fd, const std::string& mountpoint, int max_fuse_ios, 7 | int max_background, int congestion_threshold, size_t n_threads); 8 | 9 | struct fuse_uring; 10 | int fuseuring_run(int max_fuse_ios, size_t max_write, int backing_fd, 11 | int fuse_fd, struct io_uring* fuse_uring, int uring_wq_fd); -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-20.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Install liburing 17 | run: sudo add-apt-repository ppa:jacob/virtualisation && sudo apt update && sudo apt install liburing-dev 18 | - name: autoconf 19 | run: autoreconf --install 20 | - name: configure 21 | run: ./configure CXX=g++-10 22 | - name: make 23 | run: make 24 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | FMNT=/media/test 6 | BMNT=/media/bench 7 | mkdir -p "$FMNT" 8 | mkdir -p "$BMNT" 9 | ./fuseuring /tmp/backing_file.img "$FMNT" $((500*1024*1024)) 1000 5000 1 & 10 | while ! test -e "$FMNT/volume"; do sleep 1; done 11 | LODEV=$(losetup --find --show "$FMNT/volume" --direct-io=on) 12 | mkfs.ext4 -F $LODEV 13 | mount $LODEV "$BMNT" 14 | losetup -d $LODEV 15 | 16 | cp /usr/share/doc/fio/examples/ssd-test.fio ./ 17 | sed -i 's/iodepth=4/iodepth=2048/g' ssd-test.fio 18 | sed -i 's/size=10g/size=400m/g' ssd-test.fio 19 | sed -i 's/libaio/io_uring/g' ssd-test.fio 20 | sed -i "s@directory=/mount-point-of-ssd@directory=$BMNT@g" ssd-test.fio 21 | fio ssd-test.fio 22 | 23 | umount "$BMNT" 24 | umount "$FMNT" 25 | 26 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ(2.61) 5 | AC_INIT([fuseuring], [0.1.0.0], [martin@urbackup.org]) 6 | AC_CONFIG_SRCDIR([main.cpp]) 7 | AC_CONFIG_HEADER([config.h]) 8 | AC_CONFIG_MACRO_DIR([m4]) 9 | AC_CANONICAL_SYSTEM 10 | AM_INIT_AUTOMAKE([subdir-objects tar-ustar]) 11 | 12 | AC_ARG_ENABLE([fcoroutines], 13 | AS_HELP_STRING([--disable-fcoroutines], [Disable compiling with -fcoroutines if available])) 14 | AM_CONDITIONAL(WITH_FCOROUTINES, test "x$enable_fcoroutines" != xno) 15 | 16 | # Checks for programs. 17 | AC_PROG_CXX 18 | AC_PROG_CC 19 | AM_PROG_CC_C_O 20 | 21 | AX_PTHREAD 22 | if !($HAVE_PTHREAD) 23 | then 24 | echo "Sorry, your system needs the pthread library." 25 | echo "Either install it or give up." 26 | exit 1 27 | fi 28 | 29 | AC_LANG([C++]) 30 | 31 | # Checks for libraries. 32 | 33 | # Checks for header files. 34 | AC_CHECK_HEADERS([pthread.h]) 35 | 36 | AX_CHECK_COMPILE_FLAG(-fcoroutines, 37 | [], [AM_CONDITIONAL(WITH_FCOROUTINES, test xyes = xno)]) 38 | 39 | # Checks for library functions. 40 | 41 | AC_SEARCH_LIBS([clock_gettime], [rt posix4], 42 | [test "$ac_cv_search_clock_gettime" = "none required" || LIBS="$LIBS $ac_cv_search_clock_gettime"], 43 | [AC_MSG_FAILURE([No library for clock_gettime found])] ) 44 | 45 | AC_CONFIG_FILES([Makefile]) 46 | AC_OUTPUT 47 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | // Copyright (C) Martin Raiber 3 | #include "fuseuring_main.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef PR_SET_IO_FLUSHER 16 | #define PR_SET_IO_FLUSHER 57 17 | #endif 18 | 19 | int main(int argc, char* argv[]) 20 | { 21 | if(argc<6) 22 | { 23 | std::cerr << "Not enough arguments ./fuseuring [backing file path] [fuse mount path] [backing file size] [fuse max ios] [fuse max_background] [number of threads]" << std::endl; 24 | return 101; 25 | } 26 | 27 | int backing_fd = open(argv[1], O_CLOEXEC|O_CREAT|O_RDWR, S_IRWXU); 28 | //int backing_fd = memfd_create("backing_file", MFD_CLOEXEC); 29 | 30 | if(backing_fd==-1) 31 | { 32 | perror("Error opening backing file"); 33 | return 1; 34 | } 35 | 36 | int64_t backing_file_size = atoll(argv[3]); 37 | 38 | int rc = posix_fallocate(backing_fd, 0, backing_file_size); 39 | if(rc!=0) 40 | { 41 | std::cerr << "Error allocating 1GB for backing file rc: " << rc << std::endl; 42 | return 1; 43 | } 44 | 45 | rc = prctl(PR_SET_IO_FLUSHER, 1, 0, 0, 0); 46 | 47 | if(rc!=0) 48 | { 49 | perror("Error setting PR_SET_IO_FLUSHER"); 50 | } 51 | 52 | struct rlimit rlimit; 53 | rlimit.rlim_cur = RLIM_INFINITY; 54 | rlimit.rlim_max = RLIM_INFINITY; 55 | rc = setrlimit(RLIMIT_MEMLOCK, &rlimit); 56 | if(rc!=0) 57 | { 58 | perror("Error increasing RLIMIT_MEMLOCK"); 59 | } 60 | 61 | int fuse_max_ios = atoi(argv[4]); 62 | int fuse_max_background = atoi(argv[5]); 63 | size_t n_threads = static_cast(atoi(argv[6])); 64 | 65 | rc = fuseuring_main(backing_fd, argv[2], fuse_max_ios, 66 | fuse_max_background, fuse_max_background+1000, n_threads); 67 | 68 | close(backing_fd); 69 | 70 | return rc; 71 | } 72 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* config.h. Generated from config.h.in by configure. */ 2 | /* config.h.in. Generated from configure.ac by autoheader. */ 3 | 4 | /* Define to 1 if you have the header file. */ 5 | #define HAVE_INTTYPES_H 1 6 | 7 | /* Define to 1 if you have the header file. */ 8 | #define HAVE_MEMORY_H 1 9 | 10 | /* Define if you have POSIX threads libraries and header files. */ 11 | #define HAVE_PTHREAD 1 12 | 13 | /* Define to 1 if you have the header file. */ 14 | #define HAVE_PTHREAD_H 1 15 | 16 | /* pthread has GNU extension thread_setname_np */ 17 | #define HAVE_PTHREAD_SETNAME_NP 1 18 | 19 | /* Define to 1 if you have the header file. */ 20 | #define HAVE_STDINT_H 1 21 | 22 | /* Define to 1 if you have the header file. */ 23 | #define HAVE_STDLIB_H 1 24 | 25 | /* Define to 1 if you have the header file. */ 26 | #define HAVE_STRINGS_H 1 27 | 28 | /* Define to 1 if you have the header file. */ 29 | #define HAVE_STRING_H 1 30 | 31 | /* Define to 1 if you have the header file. */ 32 | #define HAVE_SYS_STAT_H 1 33 | 34 | /* Define to 1 if you have the header file. */ 35 | #define HAVE_SYS_TYPES_H 1 36 | 37 | /* Define to 1 if you have the header file. */ 38 | #define HAVE_UNISTD_H 1 39 | 40 | /* Name of package */ 41 | #define PACKAGE "fuseuring" 42 | 43 | /* Define to the address where bug reports for this package should be sent. */ 44 | #define PACKAGE_BUGREPORT "martin@urbackup.org" 45 | 46 | /* Define to the full name of this package. */ 47 | #define PACKAGE_NAME "fuseuring" 48 | 49 | /* Define to the full name and version of this package. */ 50 | #define PACKAGE_STRING "fuseuring 0.1.0.0" 51 | 52 | /* Define to the one symbol short name of this package. */ 53 | #define PACKAGE_TARNAME "fuseuring" 54 | 55 | /* Define to the home page for this package. */ 56 | #define PACKAGE_URL "" 57 | 58 | /* Define to the version of this package. */ 59 | #define PACKAGE_VERSION "0.1.0.0" 60 | 61 | /* Define to necessary symbol if this constant uses a non-standard name on 62 | your system. */ 63 | /* #undef PTHREAD_CREATE_JOINABLE */ 64 | 65 | /* Define to 1 if you have the ANSI C header files. */ 66 | #define STDC_HEADERS 1 67 | 68 | /* Version number of package */ 69 | #define VERSION "0.1.0.0" 70 | -------------------------------------------------------------------------------- /m4/m4-ax_check_compile_flag.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether the given FLAG works with the current language's compiler 12 | # or gives an error. (Warnings, however, are ignored) 13 | # 14 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on 15 | # success/failure. 16 | # 17 | # If EXTRA-FLAGS is defined, it is added to the current language's default 18 | # flags (e.g. CFLAGS) when the check is done. The check is thus made with 19 | # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to 20 | # force the compiler to issue an error when a bad flag is given. 21 | # 22 | # INPUT gives an alternative input source to AC_COMPILE_IFELSE. 23 | # 24 | # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this 25 | # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. 26 | # 27 | # LICENSE 28 | # 29 | # Copyright (c) 2008 Guido U. Draheim 30 | # Copyright (c) 2011 Maarten Bosmans 31 | # 32 | # This program is free software: you can redistribute it and/or modify it 33 | # under the terms of the GNU General Public License as published by the 34 | # Free Software Foundation, either version 3 of the License, or (at your 35 | # option) any later version. 36 | # 37 | # This program is distributed in the hope that it will be useful, but 38 | # WITHOUT ANY WARRANTY; without even the implied warranty of 39 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 40 | # Public License for more details. 41 | # 42 | # You should have received a copy of the GNU General Public License along 43 | # with this program. If not, see . 44 | # 45 | # As a special exception, the respective Autoconf Macro's copyright owner 46 | # gives unlimited permission to copy, distribute and modify the configure 47 | # scripts that are the output of Autoconf when processing the Macro. You 48 | # need not follow the terms of the GNU General Public License when using 49 | # or distributing such scripts, even though portions of the text of the 50 | # Macro appear in them. The GNU General Public License (GPL) does govern 51 | # all other use of the material that constitutes the Autoconf Macro. 52 | # 53 | # This special exception to the GPL applies to versions of the Autoconf 54 | # Macro released by the Autoconf Archive. When you make and distribute a 55 | # modified version of the Autoconf Macro, you may extend this special 56 | # exception to the GPL to apply to your modified version as well. 57 | 58 | #serial 4 59 | 60 | AC_DEFUN([AX_CHECK_COMPILE_FLAG], 61 | [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF 62 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl 63 | AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ 64 | ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS 65 | _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" 66 | AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], 67 | [AS_VAR_SET(CACHEVAR,[yes])], 68 | [AS_VAR_SET(CACHEVAR,[no])]) 69 | _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) 70 | AS_VAR_IF(CACHEVAR,yes, 71 | [m4_default([$2], :)], 72 | [m4_default([$3], :)]) 73 | AS_VAR_POPDEF([CACHEVAR])dnl 74 | ])dnl AX_CHECK_COMPILE_FLAGS 75 | -------------------------------------------------------------------------------- /fuse_io_context.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | // Copyright (C) Martin Raiber 3 | #include "fuse_io_context.h" 4 | #include 5 | #include 6 | 7 | thread_local fuse_io_context::MallocItem* fuse_io_context::malloc_cache_head = nullptr; 8 | 9 | fuse_io_context::fuse_io_context(FuseRing fuse_ring) 10 | : fuse_ring(std::move(fuse_ring)), last_rc(0) 11 | { 12 | } 13 | 14 | int fuse_io_context::fuseuring_handle_cqe(struct io_uring_cqe *cqe) 15 | { 16 | if(cqe->user_data==0) 17 | { 18 | DBG_PRINT(std::cerr << "Cqe no user_data" << std::endl); 19 | return 0; 20 | } 21 | 22 | IoUringAwaiter::IoUringAwaiterRes* res = reinterpret_cast::IoUringAwaiterRes*>(cqe->user_data); 23 | res->res = cqe->res; 24 | DBG_PRINT(std::cout << "Cqe res "<< cqe->res << std::endl); 25 | --res->gres->tocomplete; 26 | if(res->gres->tocomplete==0) 27 | { 28 | DBG_PRINT(std::cerr << "Resume cqe..." << std::endl); 29 | res->gres->awaiter.resume(); 30 | } 31 | 32 | return 0; 33 | } 34 | 35 | int fuse_io_context::fuseuring_submit(bool block) 36 | { 37 | if(fuse_ring.ring_submit) 38 | { 39 | while(true) 40 | { 41 | int rc; 42 | if(block) 43 | rc = io_uring_submit_and_wait(fuse_ring.ring, 1); 44 | else 45 | rc = io_uring_submit(fuse_ring.ring); 46 | 47 | if(rc<0 && errno!=EBUSY) 48 | { 49 | perror("Error submitting to fuse io_uring."); 50 | return 18; 51 | } 52 | else if(rc<0) 53 | { 54 | std::cout << "io_uring_submit(2): EBUSY" << std::endl; 55 | } 56 | else 57 | { 58 | break; 59 | } 60 | 61 | sleep(0); 62 | } 63 | fuse_ring.ring_submit=false; 64 | } 65 | else if(block) 66 | { 67 | int rc = io_uring_submit_and_wait(fuse_ring.ring, 1); 68 | if(rc<0) 69 | { 70 | perror("Error submitting to fuse io_uring (2)."); 71 | return 18; 72 | } 73 | } 74 | return 0; 75 | } 76 | 77 | int fuse_io_context::run(queue_fuse_read_t queue_read) 78 | { 79 | fuse_ring.ring_submit = false; 80 | 81 | while(true) 82 | { 83 | while(!fuse_ring.ios.empty()) 84 | { 85 | queue_read_set_rc(queue_read); 86 | } 87 | 88 | if(int rc; (rc=fuseuring_submit(true))!=0) 89 | return rc; 90 | 91 | 92 | unsigned head; 93 | unsigned count=0; 94 | struct io_uring_cqe *cqe; 95 | io_uring_for_each_cqe(fuse_ring.ring, head, cqe) 96 | { 97 | int rc = fuseuring_handle_cqe(cqe); 98 | if(rc<0) 99 | { 100 | std::cerr << "Error handling cqe rc=" << rc << std::endl; 101 | return 17; 102 | } 103 | ++count; 104 | } 105 | io_uring_cq_advance(fuse_ring.ring, count); 106 | 107 | if(last_rc) 108 | { 109 | std::cerr << "Task failed rc=" << last_rc << ". Shutting down." << std::endl; 110 | return 19; 111 | } 112 | } 113 | } 114 | 115 | fuse_io_context::io_uring_task_discard fuse_io_context::queue_read_set_rc(queue_fuse_read_t queue_read) 116 | { 117 | int rc = co_await queue_read(*this); 118 | if(rc!=0) 119 | { 120 | last_rc=rc; 121 | } 122 | co_return rc; 123 | } -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Fuseuring 2 | 3 | This is an example program that demonstrates how to use io_uring to drive Linux fuse. The only thing it does is forward a single file to the fuse mount point. 4 | 5 | ### Why? 6 | 7 | Using io_uring reduces the number of system calls the fuse program makes which should speed it up. 8 | 9 | Fuse is sometimes used to export a single file to be used as volume, as well. E.g. vdfuse, [s3backer](https://github.com/archiecobbs/s3backer), [UrBackup](https://www.urbackup.org/) (vhd/vhdz mounting). Recent improvements in loop (async direct-io), fuse and Linux memory management (`PR_SET_IO_FLUSHER`) have made this really performant. 10 | 11 | ### Performance 12 | 13 | With large iodepth it gets good results (backing file on tmpfs): 14 | 15 | ``` 16 | fio ~/fio/ssd-test.fio 17 | seq-read: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=1024 18 | rand-read: (g=1): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=1024 19 | seq-write: (g=2): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=1024 20 | rand-write: (g=3): rw=randwrite, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=1024 21 | fio-3.21 22 | Starting 4 processes 23 | Jobs: 1 (f=1): [_(3),w(1)][100.0%][w=384MiB/s][w=98.3k IOPS][eta 00m:00s] 24 | seq-read: (groupid=0, jobs=1): err= 0: pid=178089: Sat Nov 7 16:39:44 2020 25 | read: IOPS=165k, BW=643MiB/s (674MB/s)(3500MiB/5443msec) 26 | slat (nsec): min=1000, max=9680.6k, avg=4407.18, stdev=18980.57 27 | clat (usec): min=318, max=33373, avg=6190.66, stdev=3064.92 28 | lat (usec): min=320, max=33379, avg=6195.14, stdev=3066.45 29 | clat percentiles (usec): 30 | | 1.00th=[ 1778], 5.00th=[ 2573], 10.00th=[ 2999], 20.00th=[ 3621], 31 | | 30.00th=[ 4228], 40.00th=[ 4817], 50.00th=[ 5473], 60.00th=[ 6259], 32 | | 70.00th=[ 7308], 80.00th=[ 8586], 90.00th=[10421], 95.00th=[11994], 33 | | 99.00th=[14222], 99.50th=[15401], 99.90th=[26608], 99.95th=[28967], 34 | | 99.99th=[32637] 35 | bw ( KiB/s): min=614544, max=745196, per=100.00%, avg=662270.43, stdev=49887.45, samples=7 36 | iops : min=153636, max=186299, avg=165567.43, stdev=12471.72, samples=7 37 | lat (usec) : 500=0.01%, 750=0.02%, 1000=0.06% 38 | lat (msec) : 2=1.54%, 4=24.67%, 10=61.52%, 20=11.89%, 50=0.30% 39 | cpu : usr=18.83%, sys=43.37%, ctx=84144, majf=0, minf=1036 40 | IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=99.9% 41 | submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% 42 | complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% 43 | issued rwts: total=896000,0,0,0 short=0,0,0,0 dropped=0,0,0,0 44 | latency : target=0, window=0, percentile=100.00%, depth=1024 45 | rand-read: (groupid=1, jobs=1): err= 0: pid=178096: Sat Nov 7 16:39:44 2020 46 | read: IOPS=91.0k, BW=356MiB/s (373MB/s)(3500MiB/9845msec) 47 | slat (nsec): min=1100, max=16238k, avg=9354.69, stdev=93603.08 48 | clat (usec): min=189, max=47250, avg=11192.69, stdev=2888.77 49 | lat (usec): min=192, max=47252, avg=11202.13, stdev=2890.28 50 | clat percentiles (usec): 51 | | 1.00th=[ 6521], 5.00th=[ 7635], 10.00th=[ 8225], 20.00th=[ 8979], 52 | | 30.00th=[ 9634], 40.00th=[10290], 50.00th=[10814], 60.00th=[11469], 53 | | 70.00th=[12125], 80.00th=[13042], 90.00th=[14353], 95.00th=[15664], 54 | | 99.00th=[20317], 99.50th=[22938], 99.90th=[34866], 99.95th=[42730], 55 | | 99.99th=[44303] 56 | bw ( KiB/s): min=333610, max=383504, per=100.00%, avg=365092.00, stdev=16877.24, samples=13 57 | iops : min=83402, max=95876, avg=91272.69, stdev=4219.28, samples=13 58 | lat (usec) : 250=0.01%, 500=0.01%, 750=0.01%, 1000=0.01% 59 | lat (msec) : 2=0.06%, 4=0.16%, 10=34.80%, 20=63.89%, 50=1.06% 60 | cpu : usr=11.62%, sys=27.98%, ctx=128214, majf=0, minf=1037 61 | IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=99.9% 62 | submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% 63 | complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% 64 | issued rwts: total=896000,0,0,0 short=0,0,0,0 dropped=0,0,0,0 65 | latency : target=0, window=0, percentile=100.00%, depth=1024 66 | seq-write: (groupid=2, jobs=1): err= 0: pid=178107: Sat Nov 7 16:39:44 2020 67 | write: IOPS=131k, BW=513MiB/s (538MB/s)(3500MiB/6818msec); 0 zone resets 68 | slat (nsec): min=1100, max=4008.6k, avg=5821.89, stdev=18205.65 69 | clat (usec): min=239, max=21808, avg=7749.25, stdev=3409.43 70 | lat (usec): min=243, max=21809, avg=7755.16, stdev=3411.55 71 | clat percentiles (usec): 72 | | 1.00th=[ 2638], 5.00th=[ 3163], 10.00th=[ 3654], 20.00th=[ 4555], 73 | | 30.00th=[ 5407], 40.00th=[ 6390], 50.00th=[ 7373], 60.00th=[ 8356], 74 | | 70.00th=[ 9372], 80.00th=[10683], 90.00th=[12256], 95.00th=[14091], 75 | | 99.00th=[17171], 99.50th=[17957], 99.90th=[19792], 99.95th=[20317], 76 | | 99.99th=[21103] 77 | bw ( KiB/s): min=479727, max=612688, per=99.28%, avg=521884.56, stdev=40381.06, samples=9 78 | iops : min=119931, max=153172, avg=130470.78, stdev=10095.48, samples=9 79 | lat (usec) : 250=0.01%, 500=0.01%, 750=0.01%, 1000=0.01% 80 | lat (msec) : 2=0.16%, 4=13.54%, 10=61.47%, 20=24.72%, 50=0.08% 81 | cpu : usr=17.10%, sys=39.93%, ctx=112618, majf=0, minf=14 82 | IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=99.9% 83 | submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% 84 | complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% 85 | issued rwts: total=0,896000,0,0 short=0,0,0,0 dropped=0,0,0,0 86 | latency : target=0, window=0, percentile=100.00%, depth=1024 87 | rand-write: (groupid=3, jobs=1): err= 0: pid=178115: Sat Nov 7 16:39:44 2020 88 | write: IOPS=103k, BW=403MiB/s (422MB/s)(3500MiB/8695msec); 0 zone resets 89 | slat (nsec): min=1200, max=16351k, avg=8084.77, stdev=67434.88 90 | clat (usec): min=1126, max=38086, avg=9877.97, stdev=2952.08 91 | lat (usec): min=1128, max=38088, avg=9886.14, stdev=2953.72 92 | clat percentiles (usec): 93 | | 1.00th=[ 6325], 5.00th=[ 7373], 10.00th=[ 7701], 20.00th=[ 8094], 94 | | 30.00th=[ 8455], 40.00th=[ 8848], 50.00th=[ 9110], 60.00th=[ 9503], 95 | | 70.00th=[10159], 80.00th=[11076], 90.00th=[12518], 95.00th=[14615], 96 | | 99.00th=[23725], 99.50th=[26870], 99.90th=[32637], 99.95th=[35390], 97 | | 99.99th=[36963] 98 | bw ( KiB/s): min=340472, max=447488, per=99.79%, avg=411309.00, stdev=31900.28, samples=17 99 | iops : min=85118, max=111872, avg=102827.24, stdev=7975.05, samples=17 100 | lat (msec) : 2=0.04%, 4=0.28%, 10=67.14%, 20=30.73%, 50=1.82% 101 | cpu : usr=13.52%, sys=30.96%, ctx=74227, majf=0, minf=12 102 | IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=99.9% 103 | submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% 104 | complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% 105 | issued rwts: total=0,896000,0,0 short=0,0,0,0 dropped=0,0,0,0 106 | latency : target=0, window=0, percentile=100.00%, depth=1024 107 | 108 | Run status group 0 (all jobs): 109 | READ: bw=643MiB/s (674MB/s), 643MiB/s-643MiB/s (674MB/s-674MB/s), io=3500MiB (3670MB), run=5443-5443msec 110 | 111 | Run status group 1 (all jobs): 112 | READ: bw=356MiB/s (373MB/s), 356MiB/s-356MiB/s (373MB/s-373MB/s), io=3500MiB (3670MB), run=9845-9845msec 113 | 114 | Run status group 2 (all jobs): 115 | WRITE: bw=513MiB/s (538MB/s), 513MiB/s-513MiB/s (538MB/s-538MB/s), io=3500MiB (3670MB), run=6818-6818msec 116 | 117 | Run status group 3 (all jobs): 118 | WRITE: bw=403MiB/s (422MB/s), 403MiB/s-403MiB/s (422MB/s-422MB/s), io=3500MiB (3670MB), run=8695-8695msec 119 | 120 | Disk stats (read/write): 121 | loop0: ios=1363439/1466332, merge=428561/321891, ticks=1868225/1741830, in_queue=3610054, util=94.74% 122 | ``` 123 | 124 | ### How to compile 125 | 126 | Need gcc >= 10 for C++ coroutines. Depends on (recent) liburing-dev. 127 | 128 | ```bash 129 | autoreconf --install 130 | ./configure 131 | make 132 | ``` 133 | 134 | ### How to run 135 | 136 | Needs a recent Linux kernel (>=5.9) for io_uring functionality and recent `losetup`. 137 | 138 | ```bash 139 | FMNT=/media/test 140 | BMNT=/media/bench 141 | mkdir -p "$FMNT" 142 | mkdir -p "$BMNT" 143 | ./fuseuring /tmp/backing_file.img "$FMNT" $((500*1024*1024)) 200 5000 1 & 144 | LODEV=$(losetup --find --show "$FMNT/volume" --direct-io=on) 145 | mkfs.ext4 $LODEV || true 146 | mount $LODEV "$BMNT" 147 | losetup -d $LODEV 148 | ``` 149 | 150 | Or see `bench.sh`. 151 | -------------------------------------------------------------------------------- /m4/ax_pthread.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_pthread.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro figures out how to build C programs using POSIX threads. It 12 | # sets the PTHREAD_LIBS output variable to the threads library and linker 13 | # flags, and the PTHREAD_CFLAGS output variable to any special C compiler 14 | # flags that are needed. (The user can also force certain compiler 15 | # flags/libs to be tested by setting these environment variables.) 16 | # 17 | # Also sets PTHREAD_CC to any special C compiler that is needed for 18 | # multi-threaded programs (defaults to the value of CC otherwise). (This 19 | # is necessary on AIX to use the special cc_r compiler alias.) 20 | # 21 | # NOTE: You are assumed to not only compile your program with these flags, 22 | # but also link it with them as well. e.g. you should link with 23 | # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS 24 | # 25 | # If you are only building threads programs, you may wish to use these 26 | # variables in your default LIBS, CFLAGS, and CC: 27 | # 28 | # LIBS="$PTHREAD_LIBS $LIBS" 29 | # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 30 | # CC="$PTHREAD_CC" 31 | # 32 | # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant 33 | # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name 34 | # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). 35 | # 36 | # ACTION-IF-FOUND is a list of shell commands to run if a threads library 37 | # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it 38 | # is not found. If ACTION-IF-FOUND is not specified, the default action 39 | # will define HAVE_PTHREAD. 40 | # 41 | # Please let the authors know if this macro fails on any platform, or if 42 | # you have any other suggestions or comments. This macro was based on work 43 | # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help 44 | # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by 45 | # Alejandro Forero Cuervo to the autoconf macro repository. We are also 46 | # grateful for the helpful feedback of numerous users. 47 | # 48 | # LICENSE 49 | # 50 | # Copyright (c) 2008 Steven G. Johnson 51 | # 52 | # This program is free software: you can redistribute it and/or modify it 53 | # under the terms of the GNU General Public License as published by the 54 | # Free Software Foundation, either version 3 of the License, or (at your 55 | # option) any later version. 56 | # 57 | # This program is distributed in the hope that it will be useful, but 58 | # WITHOUT ANY WARRANTY; without even the implied warranty of 59 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 60 | # Public License for more details. 61 | # 62 | # You should have received a copy of the GNU General Public License along 63 | # with this program. If not, see . 64 | # 65 | # As a special exception, the respective Autoconf Macro's copyright owner 66 | # gives unlimited permission to copy, distribute and modify the configure 67 | # scripts that are the output of Autoconf when processing the Macro. You 68 | # need not follow the terms of the GNU General Public License when using 69 | # or distributing such scripts, even though portions of the text of the 70 | # Macro appear in them. The GNU General Public License (GPL) does govern 71 | # all other use of the material that constitutes the Autoconf Macro. 72 | # 73 | # This special exception to the GPL applies to versions of the Autoconf 74 | # Macro released by the Autoconf Archive. When you make and distribute a 75 | # modified version of the Autoconf Macro, you may extend this special 76 | # exception to the GPL to apply to your modified version as well. 77 | 78 | #serial 9 79 | 80 | AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) 81 | AC_DEFUN([AX_PTHREAD], [ 82 | AC_REQUIRE([AC_CANONICAL_HOST]) 83 | AC_LANG_SAVE 84 | AC_LANG_C 85 | ax_pthread_ok=no 86 | 87 | # We used to check for pthread.h first, but this fails if pthread.h 88 | # requires special compiler flags (e.g. on True64 or Sequent). 89 | # It gets checked for in the link test anyway. 90 | 91 | # First of all, check if the user has set any of the PTHREAD_LIBS, 92 | # etcetera environment variables, and if threads linking works using 93 | # them: 94 | if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then 95 | save_CFLAGS="$CFLAGS" 96 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 97 | save_LIBS="$LIBS" 98 | LIBS="$PTHREAD_LIBS $LIBS" 99 | AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) 100 | AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) 101 | AC_MSG_RESULT($ax_pthread_ok) 102 | if test x"$ax_pthread_ok" = xno; then 103 | PTHREAD_LIBS="" 104 | PTHREAD_CFLAGS="" 105 | fi 106 | LIBS="$save_LIBS" 107 | CFLAGS="$save_CFLAGS" 108 | fi 109 | 110 | # We must check for the threads library under a number of different 111 | # names; the ordering is very important because some systems 112 | # (e.g. DEC) have both -lpthread and -lpthreads, where one of the 113 | # libraries is broken (non-POSIX). 114 | 115 | # Create a list of thread flags to try. Items starting with a "-" are 116 | # C compiler flags, and other items are library names, except for "none" 117 | # which indicates that we try without any flags at all, and "pthread-config" 118 | # which is a program returning the flags for the Pth emulation library. 119 | 120 | ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" 121 | 122 | # The ordering *is* (sometimes) important. Some notes on the 123 | # individual items follow: 124 | 125 | # pthreads: AIX (must check this before -lpthread) 126 | # none: in case threads are in libc; should be tried before -Kthread and 127 | # other compiler flags to prevent continual compiler warnings 128 | # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) 129 | # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) 130 | # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) 131 | # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) 132 | # -pthreads: Solaris/gcc 133 | # -mthreads: Mingw32/gcc, Lynx/gcc 134 | # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it 135 | # doesn't hurt to check since this sometimes defines pthreads too; 136 | # also defines -D_REENTRANT) 137 | # ... -mt is also the pthreads flag for HP/aCC 138 | # pthread: Linux, etcetera 139 | # --thread-safe: KAI C++ 140 | # pthread-config: use pthread-config program (for GNU Pth library) 141 | 142 | case "${host_cpu}-${host_os}" in 143 | *solaris*) 144 | 145 | # On Solaris (at least, for some versions), libc contains stubbed 146 | # (non-functional) versions of the pthreads routines, so link-based 147 | # tests will erroneously succeed. (We need to link with -pthreads/-mt/ 148 | # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather 149 | # a function called by this macro, so we could check for that, but 150 | # who knows whether they'll stub that too in a future libc.) So, 151 | # we'll just look for -pthreads and -lpthread first: 152 | 153 | ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" 154 | ;; 155 | 156 | *-darwin*) 157 | ax_pthread_flags="-pthread $ax_pthread_flags" 158 | ;; 159 | esac 160 | 161 | if test x"$ax_pthread_ok" = xno; then 162 | for flag in $ax_pthread_flags; do 163 | 164 | case $flag in 165 | none) 166 | AC_MSG_CHECKING([whether pthreads work without any flags]) 167 | ;; 168 | 169 | -*) 170 | AC_MSG_CHECKING([whether pthreads work with $flag]) 171 | PTHREAD_CFLAGS="$flag" 172 | ;; 173 | 174 | pthread-config) 175 | AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) 176 | if test x"$ax_pthread_config" = xno; then continue; fi 177 | PTHREAD_CFLAGS="`pthread-config --cflags`" 178 | PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" 179 | ;; 180 | 181 | *) 182 | AC_MSG_CHECKING([for the pthreads library -l$flag]) 183 | PTHREAD_LIBS="-l$flag" 184 | ;; 185 | esac 186 | 187 | save_LIBS="$LIBS" 188 | save_CFLAGS="$CFLAGS" 189 | LIBS="$PTHREAD_LIBS $LIBS" 190 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 191 | 192 | # Check for various functions. We must include pthread.h, 193 | # since some functions may be macros. (On the Sequent, we 194 | # need a special flag -Kthread to make this header compile.) 195 | # We check for pthread_join because it is in -lpthread on IRIX 196 | # while pthread_create is in libc. We check for pthread_attr_init 197 | # due to DEC craziness with -lpthreads. We check for 198 | # pthread_cleanup_push because it is one of the few pthread 199 | # functions on Solaris that doesn't have a non-functional libc stub. 200 | # We try pthread_create on general principles. 201 | AC_TRY_LINK([#include 202 | static void routine(void* a) {a=0;} 203 | static void* start_routine(void* a) {return a;}], 204 | [pthread_t th; pthread_attr_t attr; 205 | pthread_create(&th,0,start_routine,0); 206 | pthread_join(th, 0); 207 | pthread_attr_init(&attr); 208 | pthread_cleanup_push(routine, 0); 209 | pthread_cleanup_pop(0); ], 210 | [ax_pthread_ok=yes]) 211 | 212 | LIBS="$save_LIBS" 213 | CFLAGS="$save_CFLAGS" 214 | 215 | AC_MSG_RESULT($ax_pthread_ok) 216 | if test "x$ax_pthread_ok" = xyes; then 217 | break; 218 | fi 219 | 220 | PTHREAD_LIBS="" 221 | PTHREAD_CFLAGS="" 222 | done 223 | fi 224 | 225 | # Various other checks: 226 | if test "x$ax_pthread_ok" = xyes; then 227 | save_LIBS="$LIBS" 228 | LIBS="$PTHREAD_LIBS $LIBS" 229 | save_CFLAGS="$CFLAGS" 230 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 231 | 232 | # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. 233 | AC_MSG_CHECKING([for joinable pthread attribute]) 234 | attr_name=unknown 235 | for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do 236 | AC_TRY_LINK([#include ], [int attr=$attr; return attr;], 237 | [attr_name=$attr; break]) 238 | done 239 | AC_MSG_RESULT($attr_name) 240 | if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then 241 | AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, 242 | [Define to necessary symbol if this constant 243 | uses a non-standard name on your system.]) 244 | fi 245 | 246 | AC_MSG_CHECKING([if more special flags are required for pthreads]) 247 | flag=no 248 | case "${host_cpu}-${host_os}" in 249 | *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; 250 | *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; 251 | esac 252 | AC_MSG_RESULT(${flag}) 253 | if test "x$flag" != xno; then 254 | PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" 255 | fi 256 | 257 | AC_TRY_LINK([#include ], [pthread_t ct = pthread_self();pthread_setname_np(ct, "test");], 258 | [AC_DEFINE([HAVE_PTHREAD_SETNAME_NP], [1], [pthread has GNU extension thread_setname_np])]) 259 | 260 | LIBS="$save_LIBS" 261 | CFLAGS="$save_CFLAGS" 262 | 263 | # More AIX lossage: must compile with xlc_r or cc_r 264 | if test x"$GCC" != xyes; then 265 | AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) 266 | else 267 | PTHREAD_CC=$CC 268 | fi 269 | else 270 | PTHREAD_CC="$CC" 271 | fi 272 | 273 | AC_SUBST(PTHREAD_LIBS) 274 | AC_SUBST(PTHREAD_CFLAGS) 275 | AC_SUBST(PTHREAD_CC) 276 | 277 | # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: 278 | if test x"$ax_pthread_ok" = xyes; then 279 | ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) 280 | : 281 | else 282 | ax_pthread_ok=no 283 | $2 284 | fi 285 | AC_LANG_RESTORE 286 | ])dnl AX_PTHREAD 287 | -------------------------------------------------------------------------------- /fuse_io_context.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | // Copyright (C) Martin Raiber 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define DBG_PRINT(x) 14 | 15 | /* 16 | //for clang and libc++ 17 | namespace std 18 | { 19 | template 20 | using coroutine_handle = std::experimental::coroutine_handle; 21 | 22 | using suspend_never = std::experimental::suspend_never; 23 | 24 | using suspend_always = std::experimental::suspend_always; 25 | } 26 | */ 27 | 28 | static uint64_t handle_v(std::coroutine_handle<> p_awaiter) 29 | { 30 | return (uint64_t)*((uint64_t*)&p_awaiter); 31 | } 32 | 33 | struct fuse_io_context 34 | { 35 | template 36 | struct IoUringAwaiter 37 | { 38 | struct IoUringAwaiterGlobalRes 39 | { 40 | std::coroutine_handle<> awaiter; 41 | uint8_t tocomplete; 42 | }; 43 | 44 | struct IoUringAwaiterRes 45 | { 46 | IoUringAwaiterRes() noexcept 47 | : res(-1) {} 48 | 49 | int res; 50 | IoUringAwaiterGlobalRes* gres; 51 | }; 52 | 53 | IoUringAwaiter(std::vector sqes) noexcept 54 | { 55 | awaiter_res.resize(sqes.size()); 56 | global_res.tocomplete = sqes.size(); 57 | for(size_t i=0;iuser_data = reinterpret_cast(&awaiter_res[i]); 61 | } 62 | } 63 | 64 | IoUringAwaiter(IoUringAwaiter const&) = delete; 65 | IoUringAwaiter(IoUringAwaiter&& other) = delete; 66 | IoUringAwaiter& operator=(IoUringAwaiter&&) = delete; 67 | IoUringAwaiter& operator=(IoUringAwaiter const&) = delete; 68 | 69 | bool await_ready() const noexcept 70 | { 71 | return false; 72 | } 73 | 74 | void await_suspend(std::coroutine_handle<> p_awaiter) noexcept 75 | { 76 | DBG_PRINT(std::cout << "Await suspend io "<< handle_v(p_awaiter) << std::endl); 77 | global_res.awaiter = p_awaiter; 78 | } 79 | 80 | template >::value, int> = 0> 81 | std::vector await_resume() const noexcept 82 | { 83 | std::vector res; 84 | for(auto& sr: awaiter_res) 85 | { 86 | res.push_back(sr.res); 87 | } 88 | return res; 89 | } 90 | 91 | template::value, int> = 0> 92 | int await_resume() const noexcept 93 | { 94 | return awaiter_res[0].res; 95 | } 96 | 97 | template >::value, int> = 0> 98 | std::pair await_resume() const noexcept 99 | { 100 | return std::make_pair(awaiter_res[0].res, awaiter_res[1].res); 101 | } 102 | 103 | private: 104 | IoUringAwaiterGlobalRes global_res; 105 | std::vector awaiter_res; 106 | }; 107 | 108 | [[nodiscard]] auto complete(std::vector sqes) 109 | { 110 | return IoUringAwaiter >(sqes); 111 | } 112 | 113 | [[nodiscard]] auto complete(io_uring_sqe* sqe) 114 | { 115 | return IoUringAwaiter({sqe}); 116 | } 117 | 118 | [[nodiscard]] auto complete(std::pair sqes) 119 | { 120 | return IoUringAwaiter >({sqes.first, sqes.second}); 121 | } 122 | 123 | io_uring_sqe* get_sqe(unsigned int peek=1) noexcept 124 | { 125 | if(peek>1) 126 | { 127 | while(true) 128 | { 129 | struct io_uring_sq *sq = &fuse_ring.ring->sq; 130 | unsigned int head = io_uring_smp_load_acquire(sq->khead); 131 | unsigned int tail = sq->sqe_tail; 132 | struct io_uring_sqe *__sqe = NULL; 133 | 134 | if (tail - head < *sq->kring_entries && 135 | *sq->kring_entries - (tail - head) >= peek) 136 | { 137 | io_uring_sqe* sqe = &sq->sqes[sq->sqe_tail & *sq->kring_mask]; 138 | sq->sqe_tail = tail + 1; 139 | return sqe; 140 | } 141 | 142 | int rc = io_uring_submit(fuse_ring.ring); 143 | if(rc<0 && errno!=EBUSY) 144 | { 145 | perror("io_uring_submit failed in get_sqe"); 146 | return nullptr; 147 | } 148 | else if(rc<0) 149 | { 150 | std::cout << "io_uring_submit: EBUSY" << std::endl; 151 | sleep(0); 152 | } 153 | } 154 | } 155 | 156 | fuse_ring.ring_submit=true; 157 | auto ret = io_uring_get_sqe(fuse_ring.ring); 158 | if(ret==nullptr) 159 | { 160 | /* Needs newer Linux 5.10 161 | int rc = io_uring_sqring_wait(fuse_ring.ring); 162 | if(rc<0) 163 | { 164 | return nullptr; 165 | } 166 | else if(rc==0) 167 | {*/ 168 | int rc = io_uring_submit(fuse_ring.ring); 169 | if(rc<0) 170 | { 171 | perror("io_uring_submit failed in get_sqe"); 172 | return nullptr; 173 | } 174 | 175 | do 176 | { 177 | ret = io_uring_get_sqe(fuse_ring.ring); 178 | } while (ret==nullptr); 179 | //} 180 | } 181 | return ret; 182 | } 183 | 184 | struct MallocItem 185 | { 186 | MallocItem* next; 187 | }; 188 | 189 | thread_local static MallocItem* malloc_cache_head; 190 | static constexpr size_t malloc_cache_item_size = 500; 191 | 192 | static void clear_malloc_cache() 193 | { 194 | while(malloc_cache_head) 195 | { 196 | MallocItem* next = malloc_cache_head->next; 197 | delete []reinterpret_cast(malloc_cache_head); 198 | malloc_cache_head = next; 199 | } 200 | } 201 | 202 | static void* malloc_cache() 203 | { 204 | if(malloc_cache_head!=nullptr) 205 | { 206 | MallocItem* ret = malloc_cache_head; 207 | malloc_cache_head = malloc_cache_head->next; 208 | return ret; 209 | } 210 | 211 | return new char[malloc_cache_item_size]; 212 | } 213 | 214 | static void malloc_cache_free(void* data) 215 | { 216 | MallocItem* mi = reinterpret_cast(data); 217 | mi->next = malloc_cache_head; 218 | malloc_cache_head = mi; 219 | } 220 | 221 | template 222 | struct io_uring_promise_type_base 223 | { 224 | using promise_type = io_uring_promise_type_base; 225 | using handle = std::coroutine_handle; 226 | 227 | enum class e_res_state 228 | { 229 | Init, 230 | Detached, 231 | Res 232 | }; 233 | 234 | io_uring_promise_type_base() 235 | : res_state(e_res_state::Init) {} 236 | 237 | auto initial_suspend() { 238 | return std::suspend_never{}; 239 | } 240 | 241 | auto final_suspend() noexcept { 242 | struct final_awaiter : std::suspend_always 243 | { 244 | final_awaiter(promise_type* promise) 245 | :promise(promise) {} 246 | 247 | void await_suspend(std::coroutine_handle<> p_awaiter) const noexcept 248 | { 249 | if(promise->res_state==e_res_state::Detached) 250 | { 251 | DBG_PRINT(std::cout << "promise final detached" << std::endl); 252 | if(promise->awaiter) 253 | promise->awaiter.destroy(); 254 | handle::from_promise(*promise).destroy(); 255 | } 256 | else if(promise->awaiter) 257 | { 258 | DBG_PRINT(std::cout << "promise final await resume" << std::endl); 259 | promise->awaiter.resume(); 260 | } 261 | else 262 | { 263 | DBG_PRINT(std::cout << "promise final no awaiter" << std::endl); 264 | } 265 | } 266 | 267 | private: 268 | promise_type* promise; 269 | }; 270 | return final_awaiter(this); 271 | } 272 | 273 | void unhandled_exception() 274 | { 275 | abort(); 276 | } 277 | 278 | void* operator new(std::size_t count) 279 | { 280 | if(count <= malloc_cache_item_size) 281 | { 282 | return malloc_cache(); 283 | } 284 | return ::new char[count]; 285 | } 286 | void operator delete(void* ptr, std::size_t sz) noexcept 287 | { 288 | if(sz <= malloc_cache_item_size) 289 | { 290 | malloc_cache_free(ptr); 291 | return; 292 | } 293 | ::delete (sz, reinterpret_cast(ptr)); 294 | } 295 | 296 | std::coroutine_handle<> awaiter; 297 | 298 | e_res_state res_state; 299 | }; 300 | 301 | template 302 | struct io_uring_promise_type : io_uring_promise_type_base 303 | { 304 | using handle = std::coroutine_handle >; 305 | 306 | T res; 307 | 308 | auto get_return_object() 309 | { 310 | return io_uring_task{handle::from_promise(*this)}; 311 | } 312 | 313 | void return_value(T v) 314 | { 315 | if(io_uring_promise_type_base::res_state!=io_uring_promise_type_base::e_res_state::Detached) 316 | { 317 | io_uring_promise_type_base::res_state = io_uring_promise_type_base::e_res_state::Res; 318 | res = std::move(v); 319 | } 320 | } 321 | }; 322 | 323 | template 324 | struct [[nodiscard]] io_uring_task 325 | { 326 | using promise_type = io_uring_promise_type; 327 | using handle = std::coroutine_handle; 328 | 329 | io_uring_task(io_uring_task const&) = delete; 330 | 331 | io_uring_task(io_uring_task&& other) noexcept 332 | : coro_h(std::exchange(other.coro_h, {})) 333 | { 334 | 335 | } 336 | 337 | io_uring_task& operator=(io_uring_task&&) = delete; 338 | io_uring_task& operator=(io_uring_task const&) = delete; 339 | 340 | io_uring_task(handle h) noexcept 341 | : coro_h(h) 342 | { 343 | 344 | } 345 | 346 | ~io_uring_task() noexcept 347 | { 348 | if(coro_h) 349 | { 350 | if(!coro_h.done()) 351 | { 352 | DBG_PRINT(std::cout << "Detach" << std::endl); 353 | coro_h.promise().res_state = promise_type::e_res_state::Detached; 354 | } 355 | else 356 | { 357 | DBG_PRINT(std::cout << "Destroy" << std::endl); 358 | coro_h.destroy(); 359 | } 360 | } 361 | } 362 | 363 | bool has_res() const noexcept 364 | { 365 | return coro_h.promise().res_state == promise_type::e_res_state::Res; 366 | } 367 | 368 | bool await_ready() const noexcept 369 | { 370 | bool r = has_res(); 371 | if(r) 372 | { 373 | DBG_PRINT(std::cout << "Await is ready" << std::endl); 374 | } 375 | else 376 | { 377 | DBG_PRINT(std::cout << "Await is not ready" << std::endl); 378 | } 379 | 380 | return r; 381 | } 382 | 383 | template 384 | void await_suspend(std::coroutine_handle > p_awaiter) noexcept 385 | { 386 | DBG_PRINT(std::cout << "Await task " << (uint64_t)this << " suspend "<< handle_v(p_awaiter) << " prev " << handle_v(coro_h.promise().awaiter) << std::endl); 387 | coro_h.promise().awaiter = p_awaiter; 388 | } 389 | 390 | template::value, int> = 0> 391 | void await_resume() const noexcept 392 | { 393 | assert(has_res()); 394 | } 395 | 396 | template::value, int> = 0> 397 | T await_resume() const noexcept 398 | { 399 | assert(has_res()); 400 | return std::move(coro_h.promise().res); 401 | } 402 | 403 | protected: 404 | handle coro_h; 405 | }; 406 | 407 | struct FuseIo 408 | { 409 | int fuse_fd; 410 | int pipe[2]; 411 | char* header_buf; 412 | size_t header_buf_idx; 413 | char* scratch_buf; 414 | size_t scratch_buf_idx; 415 | }; 416 | 417 | struct FuseIoVal 418 | { 419 | FuseIoVal(fuse_io_context& io_service, 420 | std::unique_ptr fuse_io) 421 | : io_service(io_service), 422 | fuse_io(std::move(fuse_io)) 423 | { 424 | 425 | } 426 | 427 | ~FuseIoVal() 428 | { 429 | io_service.release_fuse_io(std::move(fuse_io)); 430 | } 431 | 432 | FuseIo& get() const noexcept 433 | { 434 | return *fuse_io.get(); 435 | } 436 | 437 | FuseIo* operator->() const noexcept 438 | { 439 | return fuse_io.get(); 440 | } 441 | 442 | private: 443 | fuse_io_context& io_service; 444 | std::unique_ptr fuse_io; 445 | }; 446 | 447 | struct FuseRing 448 | { 449 | FuseRing() 450 | : ring(nullptr), ring_submit(false), 451 | max_bufsize(1*1024*1024), backing_fd(-1), 452 | backing_fd_orig(-1), backing_f_size(0) 453 | {} 454 | 455 | FuseRing(FuseRing&&) = default; 456 | FuseRing(FuseRing const&) = delete; 457 | FuseRing& operator=(FuseRing&&) = delete; 458 | FuseRing& operator=(FuseRing const&) = delete; 459 | 460 | std::vector > ios; 461 | struct io_uring* ring; 462 | bool ring_submit; 463 | size_t max_bufsize; 464 | int backing_fd; 465 | int backing_fd_orig; 466 | uint64_t backing_f_size; 467 | }; 468 | 469 | FuseRing fuse_ring; 470 | 471 | fuse_io_context(FuseRing fuse_ring); 472 | fuse_io_context(fuse_io_context const&) = delete; 473 | fuse_io_context(fuse_io_context&& other) = delete; 474 | fuse_io_context& operator=(fuse_io_context&&) = delete; 475 | fuse_io_context& operator=(fuse_io_context const&) = delete; 476 | 477 | typedef fuse_io_context::io_uring_task (*queue_fuse_read_t)(fuse_io_context& io); 478 | 479 | int run(queue_fuse_read_t queue_read); 480 | 481 | FuseIoVal get_fuse_io() 482 | { 483 | std::unique_ptr fuse_io = std::move(fuse_ring.ios.back()); 484 | fuse_ring.ios.pop_back(); 485 | return FuseIoVal(*this, std::move(fuse_io)); 486 | } 487 | 488 | void release_fuse_io(std::unique_ptr fuse_io) 489 | { 490 | fuse_ring.ios.push_back(std::move(fuse_io)); 491 | } 492 | 493 | private: 494 | 495 | template 496 | struct io_uring_task_discard : io_uring_task 497 | { 498 | io_uring_task_discard(io_uring_task&& other) noexcept 499 | : io_uring_task(std::move(other)) 500 | { 501 | } 502 | }; 503 | 504 | fuse_io_context::io_uring_task_discard queue_read_set_rc(queue_fuse_read_t queue_read); 505 | 506 | int fuseuring_handle_cqe(struct io_uring_cqe *cqe); 507 | int fuseuring_submit(bool block); 508 | 509 | int last_rc; 510 | }; 511 | 512 | template<> 513 | struct fuse_io_context::io_uring_promise_type : fuse_io_context::io_uring_promise_type_base 514 | { 515 | using handle = std::coroutine_handle >; 516 | 517 | auto get_return_object() 518 | { 519 | return io_uring_task{handle::from_promise(*this)}; 520 | } 521 | 522 | void return_void() 523 | { 524 | if(fuse_io_context::io_uring_promise_type_base::res_state!=fuse_io_context::io_uring_promise_type_base::e_res_state::Detached) 525 | { 526 | fuse_io_context::io_uring_promise_type_base::res_state = fuse_io_context::io_uring_promise_type_base::e_res_state::Res; 527 | } 528 | } 529 | }; 530 | -------------------------------------------------------------------------------- /fuse_kernel.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */ 2 | /* 3 | This file defines the kernel interface of FUSE 4 | Copyright (C) 2001-2008 Miklos Szeredi 5 | 6 | This program can be distributed under the terms of the GNU GPL. 7 | See the file COPYING. 8 | 9 | This -- and only this -- header file may also be distributed under 10 | the terms of the BSD Licence as follows: 11 | 12 | Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions 16 | are met: 17 | 1. Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 2. Redistributions in binary form must reproduce the above copyright 20 | notice, this list of conditions and the following disclaimer in the 21 | documentation and/or other materials provided with the distribution. 22 | 23 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 | SUCH DAMAGE. 34 | */ 35 | 36 | /* 37 | * This file defines the kernel interface of FUSE 38 | * 39 | * Protocol changelog: 40 | * 41 | * 7.9: 42 | * - new fuse_getattr_in input argument of GETATTR 43 | * - add lk_flags in fuse_lk_in 44 | * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in 45 | * - add blksize field to fuse_attr 46 | * - add file flags field to fuse_read_in and fuse_write_in 47 | * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in 48 | * 49 | * 7.10 50 | * - add nonseekable open flag 51 | * 52 | * 7.11 53 | * - add IOCTL message 54 | * - add unsolicited notification support 55 | * - add POLL message and NOTIFY_POLL notification 56 | * 57 | * 7.12 58 | * - add umask flag to input argument of create, mknod and mkdir 59 | * - add notification messages for invalidation of inodes and 60 | * directory entries 61 | * 62 | * 7.13 63 | * - make max number of background requests and congestion threshold 64 | * tunables 65 | * 66 | * 7.14 67 | * - add splice support to fuse device 68 | * 69 | * 7.15 70 | * - add store notify 71 | * - add retrieve notify 72 | * 73 | * 7.16 74 | * - add BATCH_FORGET request 75 | * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct 76 | * fuse_ioctl_iovec' instead of ambiguous 'struct iovec' 77 | * - add FUSE_IOCTL_32BIT flag 78 | * 79 | * 7.17 80 | * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK 81 | * 82 | * 7.18 83 | * - add FUSE_IOCTL_DIR flag 84 | * - add FUSE_NOTIFY_DELETE 85 | * 86 | * 7.19 87 | * - add FUSE_FALLOCATE 88 | * 89 | * 7.20 90 | * - add FUSE_AUTO_INVAL_DATA 91 | * 92 | * 7.21 93 | * - add FUSE_READDIRPLUS 94 | * - send the requested events in POLL request 95 | * 96 | * 7.22 97 | * - add FUSE_ASYNC_DIO 98 | * 99 | * 7.23 100 | * - add FUSE_WRITEBACK_CACHE 101 | * - add time_gran to fuse_init_out 102 | * - add reserved space to fuse_init_out 103 | * - add FATTR_CTIME 104 | * - add ctime and ctimensec to fuse_setattr_in 105 | * - add FUSE_RENAME2 request 106 | * - add FUSE_NO_OPEN_SUPPORT flag 107 | * 108 | * 7.24 109 | * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support 110 | * 111 | * 7.25 112 | * - add FUSE_PARALLEL_DIROPS 113 | * 114 | * 7.26 115 | * - add FUSE_HANDLE_KILLPRIV 116 | * - add FUSE_POSIX_ACL 117 | * 118 | * 7.27 119 | * - add FUSE_ABORT_ERROR 120 | * 121 | * 7.28 122 | * - add FUSE_COPY_FILE_RANGE 123 | * - add FOPEN_CACHE_DIR 124 | * - add FUSE_MAX_PAGES, add max_pages to init_out 125 | * - add FUSE_CACHE_SYMLINKS 126 | * 127 | * 7.29 128 | * - add FUSE_NO_OPENDIR_SUPPORT flag 129 | * 130 | * 7.30 131 | * - add FUSE_EXPLICIT_INVAL_DATA 132 | * - add FUSE_IOCTL_COMPAT_X32 133 | * 134 | * 7.31 135 | * - add FUSE_WRITE_KILL_PRIV flag 136 | */ 137 | 138 | #ifndef _LINUX_FUSE_H 139 | #define _LINUX_FUSE_H 140 | 141 | #ifdef __KERNEL__ 142 | #include 143 | #else 144 | #include 145 | #endif 146 | 147 | /* 148 | * Version negotiation: 149 | * 150 | * Both the kernel and userspace send the version they support in the 151 | * INIT request and reply respectively. 152 | * 153 | * If the major versions match then both shall use the smallest 154 | * of the two minor versions for communication. 155 | * 156 | * If the kernel supports a larger major version, then userspace shall 157 | * reply with the major version it supports, ignore the rest of the 158 | * INIT message and expect a new INIT message from the kernel with a 159 | * matching major version. 160 | * 161 | * If the library supports a larger major version, then it shall fall 162 | * back to the major protocol version sent by the kernel for 163 | * communication and reply with that major version (and an arbitrary 164 | * supported minor version). 165 | */ 166 | 167 | /** Version number of this interface */ 168 | #define FUSE_KERNEL_VERSION 7 169 | 170 | /** Minor version number of this interface */ 171 | #define FUSE_KERNEL_MINOR_VERSION 31 172 | 173 | /** The node ID of the root inode */ 174 | #define FUSE_ROOT_ID 1 175 | 176 | /* Make sure all structures are padded to 64bit boundary, so 32bit 177 | userspace works under 64bit kernels */ 178 | 179 | struct fuse_attr { 180 | uint64_t ino; 181 | uint64_t size; 182 | uint64_t blocks; 183 | uint64_t atime; 184 | uint64_t mtime; 185 | uint64_t ctime; 186 | uint32_t atimensec; 187 | uint32_t mtimensec; 188 | uint32_t ctimensec; 189 | uint32_t mode; 190 | uint32_t nlink; 191 | uint32_t uid; 192 | uint32_t gid; 193 | uint32_t rdev; 194 | uint32_t blksize; 195 | uint32_t padding; 196 | }; 197 | 198 | struct fuse_kstatfs { 199 | uint64_t blocks; 200 | uint64_t bfree; 201 | uint64_t bavail; 202 | uint64_t files; 203 | uint64_t ffree; 204 | uint32_t bsize; 205 | uint32_t namelen; 206 | uint32_t frsize; 207 | uint32_t padding; 208 | uint32_t spare[6]; 209 | }; 210 | 211 | struct fuse_file_lock { 212 | uint64_t start; 213 | uint64_t end; 214 | uint32_t type; 215 | uint32_t pid; /* tgid */ 216 | }; 217 | 218 | /** 219 | * Bitmasks for fuse_setattr_in.valid 220 | */ 221 | #define FATTR_MODE (1 << 0) 222 | #define FATTR_UID (1 << 1) 223 | #define FATTR_GID (1 << 2) 224 | #define FATTR_SIZE (1 << 3) 225 | #define FATTR_ATIME (1 << 4) 226 | #define FATTR_MTIME (1 << 5) 227 | #define FATTR_FH (1 << 6) 228 | #define FATTR_ATIME_NOW (1 << 7) 229 | #define FATTR_MTIME_NOW (1 << 8) 230 | #define FATTR_LOCKOWNER (1 << 9) 231 | #define FATTR_CTIME (1 << 10) 232 | 233 | /** 234 | * Flags returned by the OPEN request 235 | * 236 | * FOPEN_DIRECT_IO: bypass page cache for this open file 237 | * FOPEN_KEEP_CACHE: don't invalidate the data cache on open 238 | * FOPEN_NONSEEKABLE: the file is not seekable 239 | * FOPEN_CACHE_DIR: allow caching this directory 240 | * FOPEN_STREAM: the file is stream-like (no file position at all) 241 | */ 242 | #define FOPEN_DIRECT_IO (1 << 0) 243 | #define FOPEN_KEEP_CACHE (1 << 1) 244 | #define FOPEN_NONSEEKABLE (1 << 2) 245 | #define FOPEN_CACHE_DIR (1 << 3) 246 | #define FOPEN_STREAM (1 << 4) 247 | 248 | /** 249 | * INIT request/reply flags 250 | * 251 | * FUSE_ASYNC_READ: asynchronous read requests 252 | * FUSE_POSIX_LOCKS: remote locking for POSIX file locks 253 | * FUSE_FILE_OPS: kernel sends file handle for fstat, etc... (not yet supported) 254 | * FUSE_ATOMIC_O_TRUNC: handles the O_TRUNC open flag in the filesystem 255 | * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." 256 | * FUSE_BIG_WRITES: filesystem can handle write size larger than 4kB 257 | * FUSE_DONT_MASK: don't apply umask to file mode on create operations 258 | * FUSE_SPLICE_WRITE: kernel supports splice write on the device 259 | * FUSE_SPLICE_MOVE: kernel supports splice move on the device 260 | * FUSE_SPLICE_READ: kernel supports splice read on the device 261 | * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks 262 | * FUSE_HAS_IOCTL_DIR: kernel supports ioctl on directories 263 | * FUSE_AUTO_INVAL_DATA: automatically invalidate cached pages 264 | * FUSE_DO_READDIRPLUS: do READDIRPLUS (READDIR+LOOKUP in one) 265 | * FUSE_READDIRPLUS_AUTO: adaptive readdirplus 266 | * FUSE_ASYNC_DIO: asynchronous direct I/O submission 267 | * FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes 268 | * FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens 269 | * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir 270 | * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc 271 | * FUSE_POSIX_ACL: filesystem supports posix acls 272 | * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED 273 | * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages 274 | * FUSE_CACHE_SYMLINKS: cache READLINK responses 275 | * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir 276 | * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request 277 | */ 278 | #define FUSE_ASYNC_READ (1 << 0) 279 | #define FUSE_POSIX_LOCKS (1 << 1) 280 | #define FUSE_FILE_OPS (1 << 2) 281 | #define FUSE_ATOMIC_O_TRUNC (1 << 3) 282 | #define FUSE_EXPORT_SUPPORT (1 << 4) 283 | #define FUSE_BIG_WRITES (1 << 5) 284 | #define FUSE_DONT_MASK (1 << 6) 285 | #define FUSE_SPLICE_WRITE (1 << 7) 286 | #define FUSE_SPLICE_MOVE (1 << 8) 287 | #define FUSE_SPLICE_READ (1 << 9) 288 | #define FUSE_FLOCK_LOCKS (1 << 10) 289 | #define FUSE_HAS_IOCTL_DIR (1 << 11) 290 | #define FUSE_AUTO_INVAL_DATA (1 << 12) 291 | #define FUSE_DO_READDIRPLUS (1 << 13) 292 | #define FUSE_READDIRPLUS_AUTO (1 << 14) 293 | #define FUSE_ASYNC_DIO (1 << 15) 294 | #define FUSE_WRITEBACK_CACHE (1 << 16) 295 | #define FUSE_NO_OPEN_SUPPORT (1 << 17) 296 | #define FUSE_PARALLEL_DIROPS (1 << 18) 297 | #define FUSE_HANDLE_KILLPRIV (1 << 19) 298 | #define FUSE_POSIX_ACL (1 << 20) 299 | #define FUSE_ABORT_ERROR (1 << 21) 300 | #define FUSE_MAX_PAGES (1 << 22) 301 | #define FUSE_CACHE_SYMLINKS (1 << 23) 302 | #define FUSE_NO_OPENDIR_SUPPORT (1 << 24) 303 | #define FUSE_EXPLICIT_INVAL_DATA (1 << 25) 304 | 305 | /** 306 | * CUSE INIT request/reply flags 307 | * 308 | * CUSE_UNRESTRICTED_IOCTL: use unrestricted ioctl 309 | */ 310 | #define CUSE_UNRESTRICTED_IOCTL (1 << 0) 311 | 312 | /** 313 | * Release flags 314 | */ 315 | #define FUSE_RELEASE_FLUSH (1 << 0) 316 | #define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1) 317 | 318 | /** 319 | * Getattr flags 320 | */ 321 | #define FUSE_GETATTR_FH (1 << 0) 322 | 323 | /** 324 | * Lock flags 325 | */ 326 | #define FUSE_LK_FLOCK (1 << 0) 327 | 328 | /** 329 | * WRITE flags 330 | * 331 | * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed 332 | * FUSE_WRITE_LOCKOWNER: lock_owner field is valid 333 | * FUSE_WRITE_KILL_PRIV: kill suid and sgid bits 334 | */ 335 | #define FUSE_WRITE_CACHE (1 << 0) 336 | #define FUSE_WRITE_LOCKOWNER (1 << 1) 337 | #define FUSE_WRITE_KILL_PRIV (1 << 2) 338 | 339 | /** 340 | * Read flags 341 | */ 342 | #define FUSE_READ_LOCKOWNER (1 << 1) 343 | 344 | /** 345 | * Ioctl flags 346 | * 347 | * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine 348 | * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed 349 | * FUSE_IOCTL_RETRY: retry with new iovecs 350 | * FUSE_IOCTL_32BIT: 32bit ioctl 351 | * FUSE_IOCTL_DIR: is a directory 352 | * FUSE_IOCTL_COMPAT_X32: x32 compat ioctl on 64bit machine (64bit time_t) 353 | * 354 | * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs 355 | */ 356 | #define FUSE_IOCTL_COMPAT (1 << 0) 357 | #define FUSE_IOCTL_UNRESTRICTED (1 << 1) 358 | #define FUSE_IOCTL_RETRY (1 << 2) 359 | #define FUSE_IOCTL_32BIT (1 << 3) 360 | #define FUSE_IOCTL_DIR (1 << 4) 361 | #define FUSE_IOCTL_COMPAT_X32 (1 << 5) 362 | 363 | #define FUSE_IOCTL_MAX_IOV 256 364 | 365 | /** 366 | * Poll flags 367 | * 368 | * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify 369 | */ 370 | #define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) 371 | 372 | /** 373 | * Fsync flags 374 | * 375 | * FUSE_FSYNC_FDATASYNC: Sync data only, not metadata 376 | */ 377 | #define FUSE_FSYNC_FDATASYNC (1 << 0) 378 | 379 | enum fuse_opcode { 380 | FUSE_LOOKUP = 1, 381 | FUSE_FORGET = 2, /* no reply */ 382 | FUSE_GETATTR = 3, 383 | FUSE_SETATTR = 4, 384 | FUSE_READLINK = 5, 385 | FUSE_SYMLINK = 6, 386 | FUSE_MKNOD = 8, 387 | FUSE_MKDIR = 9, 388 | FUSE_UNLINK = 10, 389 | FUSE_RMDIR = 11, 390 | FUSE_RENAME = 12, 391 | FUSE_LINK = 13, 392 | FUSE_OPEN = 14, 393 | FUSE_READ = 15, 394 | FUSE_WRITE = 16, 395 | FUSE_STATFS = 17, 396 | FUSE_RELEASE = 18, 397 | FUSE_FSYNC = 20, 398 | FUSE_SETXATTR = 21, 399 | FUSE_GETXATTR = 22, 400 | FUSE_LISTXATTR = 23, 401 | FUSE_REMOVEXATTR = 24, 402 | FUSE_FLUSH = 25, 403 | FUSE_INIT = 26, 404 | FUSE_OPENDIR = 27, 405 | FUSE_READDIR = 28, 406 | FUSE_RELEASEDIR = 29, 407 | FUSE_FSYNCDIR = 30, 408 | FUSE_GETLK = 31, 409 | FUSE_SETLK = 32, 410 | FUSE_SETLKW = 33, 411 | FUSE_ACCESS = 34, 412 | FUSE_CREATE = 35, 413 | FUSE_INTERRUPT = 36, 414 | FUSE_BMAP = 37, 415 | FUSE_DESTROY = 38, 416 | FUSE_IOCTL = 39, 417 | FUSE_POLL = 40, 418 | FUSE_NOTIFY_REPLY = 41, 419 | FUSE_BATCH_FORGET = 42, 420 | FUSE_FALLOCATE = 43, 421 | FUSE_READDIRPLUS = 44, 422 | FUSE_RENAME2 = 45, 423 | FUSE_LSEEK = 46, 424 | FUSE_COPY_FILE_RANGE = 47, 425 | 426 | /* CUSE specific operations */ 427 | CUSE_INIT = 4096 428 | }; 429 | 430 | enum fuse_notify_code { 431 | FUSE_NOTIFY_POLL = 1, 432 | FUSE_NOTIFY_INVAL_INODE = 2, 433 | FUSE_NOTIFY_INVAL_ENTRY = 3, 434 | FUSE_NOTIFY_STORE = 4, 435 | FUSE_NOTIFY_RETRIEVE = 5, 436 | FUSE_NOTIFY_DELETE = 6, 437 | FUSE_NOTIFY_CODE_MAX 438 | }; 439 | 440 | /* The read buffer is required to be at least 8k, but may be much larger */ 441 | #define FUSE_MIN_READ_BUFFER 8192 442 | 443 | #define FUSE_COMPAT_ENTRY_OUT_SIZE 120 444 | 445 | struct fuse_entry_out { 446 | uint64_t nodeid; /* Inode ID */ 447 | uint64_t generation; /* Inode generation: nodeid:gen must 448 | be unique for the fs's lifetime */ 449 | uint64_t entry_valid; /* Cache timeout for the name */ 450 | uint64_t attr_valid; /* Cache timeout for the attributes */ 451 | uint32_t entry_valid_nsec; 452 | uint32_t attr_valid_nsec; 453 | struct fuse_attr attr; 454 | }; 455 | 456 | struct fuse_forget_in { 457 | uint64_t nlookup; 458 | }; 459 | 460 | struct fuse_forget_one { 461 | uint64_t nodeid; 462 | uint64_t nlookup; 463 | }; 464 | 465 | struct fuse_batch_forget_in { 466 | uint32_t count; 467 | uint32_t dummy; 468 | }; 469 | 470 | struct fuse_getattr_in { 471 | uint32_t getattr_flags; 472 | uint32_t dummy; 473 | uint64_t fh; 474 | }; 475 | 476 | #define FUSE_COMPAT_ATTR_OUT_SIZE 96 477 | 478 | struct fuse_attr_out { 479 | uint64_t attr_valid; /* Cache timeout for the attributes */ 480 | uint32_t attr_valid_nsec; 481 | uint32_t dummy; 482 | struct fuse_attr attr; 483 | }; 484 | 485 | #define FUSE_COMPAT_MKNOD_IN_SIZE 8 486 | 487 | struct fuse_mknod_in { 488 | uint32_t mode; 489 | uint32_t rdev; 490 | uint32_t umask; 491 | uint32_t padding; 492 | }; 493 | 494 | struct fuse_mkdir_in { 495 | uint32_t mode; 496 | uint32_t umask; 497 | }; 498 | 499 | struct fuse_rename_in { 500 | uint64_t newdir; 501 | }; 502 | 503 | struct fuse_rename2_in { 504 | uint64_t newdir; 505 | uint32_t flags; 506 | uint32_t padding; 507 | }; 508 | 509 | struct fuse_link_in { 510 | uint64_t oldnodeid; 511 | }; 512 | 513 | struct fuse_setattr_in { 514 | uint32_t valid; 515 | uint32_t padding; 516 | uint64_t fh; 517 | uint64_t size; 518 | uint64_t lock_owner; 519 | uint64_t atime; 520 | uint64_t mtime; 521 | uint64_t ctime; 522 | uint32_t atimensec; 523 | uint32_t mtimensec; 524 | uint32_t ctimensec; 525 | uint32_t mode; 526 | uint32_t unused4; 527 | uint32_t uid; 528 | uint32_t gid; 529 | uint32_t unused5; 530 | }; 531 | 532 | struct fuse_open_in { 533 | uint32_t flags; 534 | uint32_t unused; 535 | }; 536 | 537 | struct fuse_create_in { 538 | uint32_t flags; 539 | uint32_t mode; 540 | uint32_t umask; 541 | uint32_t padding; 542 | }; 543 | 544 | struct fuse_open_out { 545 | uint64_t fh; 546 | uint32_t open_flags; 547 | uint32_t padding; 548 | }; 549 | 550 | struct fuse_release_in { 551 | uint64_t fh; 552 | uint32_t flags; 553 | uint32_t release_flags; 554 | uint64_t lock_owner; 555 | }; 556 | 557 | struct fuse_flush_in { 558 | uint64_t fh; 559 | uint32_t unused; 560 | uint32_t padding; 561 | uint64_t lock_owner; 562 | }; 563 | 564 | struct fuse_read_in { 565 | uint64_t fh; 566 | uint64_t offset; 567 | uint32_t size; 568 | uint32_t read_flags; 569 | uint64_t lock_owner; 570 | uint32_t flags; 571 | uint32_t padding; 572 | }; 573 | 574 | #define FUSE_COMPAT_WRITE_IN_SIZE 24 575 | 576 | struct fuse_write_in { 577 | uint64_t fh; 578 | uint64_t offset; 579 | uint32_t size; 580 | uint32_t write_flags; 581 | uint64_t lock_owner; 582 | uint32_t flags; 583 | uint32_t padding; 584 | }; 585 | 586 | struct fuse_write_out { 587 | uint32_t size; 588 | uint32_t padding; 589 | }; 590 | 591 | #define FUSE_COMPAT_STATFS_SIZE 48 592 | 593 | struct fuse_statfs_out { 594 | struct fuse_kstatfs st; 595 | }; 596 | 597 | struct fuse_fsync_in { 598 | uint64_t fh; 599 | uint32_t fsync_flags; 600 | uint32_t padding; 601 | }; 602 | 603 | struct fuse_setxattr_in { 604 | uint32_t size; 605 | uint32_t flags; 606 | }; 607 | 608 | struct fuse_getxattr_in { 609 | uint32_t size; 610 | uint32_t padding; 611 | }; 612 | 613 | struct fuse_getxattr_out { 614 | uint32_t size; 615 | uint32_t padding; 616 | }; 617 | 618 | struct fuse_lk_in { 619 | uint64_t fh; 620 | uint64_t owner; 621 | struct fuse_file_lock lk; 622 | uint32_t lk_flags; 623 | uint32_t padding; 624 | }; 625 | 626 | struct fuse_lk_out { 627 | struct fuse_file_lock lk; 628 | }; 629 | 630 | struct fuse_access_in { 631 | uint32_t mask; 632 | uint32_t padding; 633 | }; 634 | 635 | struct fuse_init_in { 636 | uint32_t major; 637 | uint32_t minor; 638 | uint32_t max_readahead; 639 | uint32_t flags; 640 | }; 641 | 642 | #define FUSE_COMPAT_INIT_OUT_SIZE 8 643 | #define FUSE_COMPAT_22_INIT_OUT_SIZE 24 644 | 645 | struct fuse_init_out { 646 | uint32_t major; 647 | uint32_t minor; 648 | uint32_t max_readahead; 649 | uint32_t flags; 650 | uint16_t max_background; 651 | uint16_t congestion_threshold; 652 | uint32_t max_write; 653 | uint32_t time_gran; 654 | uint16_t max_pages; 655 | uint16_t padding; 656 | uint32_t unused[8]; 657 | }; 658 | 659 | #define CUSE_INIT_INFO_MAX 4096 660 | 661 | struct cuse_init_in { 662 | uint32_t major; 663 | uint32_t minor; 664 | uint32_t unused; 665 | uint32_t flags; 666 | }; 667 | 668 | struct cuse_init_out { 669 | uint32_t major; 670 | uint32_t minor; 671 | uint32_t unused; 672 | uint32_t flags; 673 | uint32_t max_read; 674 | uint32_t max_write; 675 | uint32_t dev_major; /* chardev major */ 676 | uint32_t dev_minor; /* chardev minor */ 677 | uint32_t spare[10]; 678 | }; 679 | 680 | struct fuse_interrupt_in { 681 | uint64_t unique; 682 | }; 683 | 684 | struct fuse_bmap_in { 685 | uint64_t block; 686 | uint32_t blocksize; 687 | uint32_t padding; 688 | }; 689 | 690 | struct fuse_bmap_out { 691 | uint64_t block; 692 | }; 693 | 694 | struct fuse_ioctl_in { 695 | uint64_t fh; 696 | uint32_t flags; 697 | uint32_t cmd; 698 | uint64_t arg; 699 | uint32_t in_size; 700 | uint32_t out_size; 701 | }; 702 | 703 | struct fuse_ioctl_iovec { 704 | uint64_t base; 705 | uint64_t len; 706 | }; 707 | 708 | struct fuse_ioctl_out { 709 | int32_t result; 710 | uint32_t flags; 711 | uint32_t in_iovs; 712 | uint32_t out_iovs; 713 | }; 714 | 715 | struct fuse_poll_in { 716 | uint64_t fh; 717 | uint64_t kh; 718 | uint32_t flags; 719 | uint32_t events; 720 | }; 721 | 722 | struct fuse_poll_out { 723 | uint32_t revents; 724 | uint32_t padding; 725 | }; 726 | 727 | struct fuse_notify_poll_wakeup_out { 728 | uint64_t kh; 729 | }; 730 | 731 | struct fuse_fallocate_in { 732 | uint64_t fh; 733 | uint64_t offset; 734 | uint64_t length; 735 | uint32_t mode; 736 | uint32_t padding; 737 | }; 738 | 739 | struct fuse_in_header { 740 | uint32_t len; 741 | uint32_t opcode; 742 | uint64_t unique; 743 | uint64_t nodeid; 744 | uint32_t uid; 745 | uint32_t gid; 746 | uint32_t pid; 747 | uint32_t padding; 748 | }; 749 | 750 | struct fuse_out_header { 751 | uint32_t len; 752 | int32_t error; 753 | uint64_t unique; 754 | }; 755 | 756 | struct fuse_dirent { 757 | uint64_t ino; 758 | uint64_t off; 759 | uint32_t namelen; 760 | uint32_t type; 761 | char name[]; 762 | }; 763 | 764 | #define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) 765 | #define FUSE_DIRENT_ALIGN(x) \ 766 | (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) 767 | #define FUSE_DIRENT_SIZE(d) \ 768 | FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) 769 | 770 | struct fuse_direntplus { 771 | struct fuse_entry_out entry_out; 772 | struct fuse_dirent dirent; 773 | }; 774 | 775 | #define FUSE_NAME_OFFSET_DIRENTPLUS \ 776 | offsetof(struct fuse_direntplus, dirent.name) 777 | #define FUSE_DIRENTPLUS_SIZE(d) \ 778 | FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) 779 | 780 | struct fuse_notify_inval_inode_out { 781 | uint64_t ino; 782 | int64_t off; 783 | int64_t len; 784 | }; 785 | 786 | struct fuse_notify_inval_entry_out { 787 | uint64_t parent; 788 | uint32_t namelen; 789 | uint32_t padding; 790 | }; 791 | 792 | struct fuse_notify_delete_out { 793 | uint64_t parent; 794 | uint64_t child; 795 | uint32_t namelen; 796 | uint32_t padding; 797 | }; 798 | 799 | struct fuse_notify_store_out { 800 | uint64_t nodeid; 801 | uint64_t offset; 802 | uint32_t size; 803 | uint32_t padding; 804 | }; 805 | 806 | struct fuse_notify_retrieve_out { 807 | uint64_t notify_unique; 808 | uint64_t nodeid; 809 | uint64_t offset; 810 | uint32_t size; 811 | uint32_t padding; 812 | }; 813 | 814 | /* Matches the size of fuse_write_in */ 815 | struct fuse_notify_retrieve_in { 816 | uint64_t dummy1; 817 | uint64_t offset; 818 | uint32_t size; 819 | uint32_t dummy2; 820 | uint64_t dummy3; 821 | uint64_t dummy4; 822 | }; 823 | 824 | /* Device ioctls: */ 825 | #define FUSE_DEV_IOC_CLONE _IOR(229, 0, uint32_t) 826 | 827 | struct fuse_lseek_in { 828 | uint64_t fh; 829 | uint64_t offset; 830 | uint32_t whence; 831 | uint32_t padding; 832 | }; 833 | 834 | struct fuse_lseek_out { 835 | uint64_t offset; 836 | }; 837 | 838 | struct fuse_copy_file_range_in { 839 | uint64_t fh_in; 840 | uint64_t off_in; 841 | uint64_t nodeid_out; 842 | uint64_t fh_out; 843 | uint64_t off_out; 844 | uint64_t len; 845 | uint64_t flags; 846 | }; 847 | 848 | #endif /* _LINUX_FUSE_H */ 849 | -------------------------------------------------------------------------------- /fuseuring_main.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | // Copyright (C) Martin Raiber 3 | #include 4 | #include "fuse_kernel.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "fuse_io_context.h" 17 | #include "fuseuring_main.h" 18 | 19 | namespace 20 | { 21 | const size_t fuse_max_pages = 256; 22 | const size_t header_buf_size = std::max(sizeof(fuse_in_header) + sizeof(fuse_write_in), 23 | sizeof(fuse_out_header) + sizeof(fuse_write_out)); 24 | const size_t scratch_buf_size = std::max(std::max(std::max( 25 | static_cast(4096), 26 | sizeof(fuse_out_header)+sizeof(fuse_attr_out)), 27 | sizeof(fuse_out_header)+sizeof(fuse_entry_out)), 28 | sizeof(fuse_out_header)+sizeof(fuse_write_out)); 29 | 30 | template 31 | auto round_up(T numToRound, T multiple) 32 | { 33 | return ((numToRound + multiple - 1) / multiple) * multiple; 34 | } 35 | } 36 | 37 | [[nodiscard]] fuse_io_context::io_uring_task read_rbytes(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 38 | size_t rbytes, bool add_zero, size_t init_read, std::vector& free_buf) 39 | { 40 | if(rbytes==0) 41 | { 42 | co_return fuse_io->scratch_buf; 43 | } 44 | 45 | bool io_read=true; 46 | if(init_read==rbytes) 47 | { 48 | if(add_zero) 49 | io_read=false; 50 | else 51 | co_return fuse_io->header_buf + sizeof(fuse_in_header); 52 | } 53 | 54 | bool read_fixed=false; 55 | if(rbytes + (add_zero ? 1 : 0) < scratch_buf_size) 56 | { 57 | if(init_read>0) 58 | memcpy(fuse_io->scratch_buf, fuse_io->header_buf + sizeof(fuse_in_header), init_read); 59 | 60 | read_fixed=true; 61 | } 62 | else 63 | { 64 | free_buf.resize(rbytes + (add_zero ? 1 : 0)); 65 | 66 | if(init_read>0) 67 | memcpy(free_buf.data(), fuse_io->header_buf + sizeof(fuse_in_header), init_read); 68 | } 69 | 70 | if(io_read) 71 | { 72 | size_t read_done = init_read; 73 | do 74 | { 75 | io_uring_sqe *sqe = io.get_sqe(); 76 | if(sqe==nullptr) 77 | co_return nullptr; 78 | 79 | DBG_PRINT(std::cout << "Read rbytes "<< rbytes << " init read " << init_read << std::endl); 80 | 81 | if(read_fixed) 82 | { 83 | io_uring_prep_read_fixed(sqe, fuse_io->pipe[0], fuse_io->scratch_buf + read_done, 84 | rbytes - read_done, 0, fuse_io->scratch_buf_idx); 85 | } 86 | else 87 | { 88 | io_uring_prep_read(sqe, fuse_io->pipe[0], &free_buf[read_done], 89 | rbytes-read_done, 0); 90 | } 91 | 92 | sqe->flags |= IOSQE_FIXED_FILE; 93 | int rc = co_await io.complete(sqe); 94 | 95 | if(rc<=0) 96 | { 97 | std::cerr << "Reading rbytes failed rc=" << rc << std::endl; 98 | co_return nullptr; 99 | } 100 | 101 | read_done+=rc; 102 | 103 | } while(read_donescratch_buf[rbytes] = 0; 110 | 111 | co_return fuse_io->scratch_buf; 112 | } 113 | else 114 | { 115 | if(add_zero) 116 | free_buf[rbytes]=0; 117 | 118 | co_return &free_buf[0]; 119 | } 120 | } 121 | 122 | [[nodiscard]] fuse_io_context::io_uring_task send_reply(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io) 123 | { 124 | struct io_uring_sqe *sqe; 125 | sqe = io.get_sqe(2); 126 | if(sqe==nullptr) 127 | co_return -1; 128 | 129 | size_t reply_size = reinterpret_cast(fuse_io->scratch_buf)->len; 130 | 131 | io_uring_prep_write_fixed(sqe, fuse_io->pipe[1], 132 | fuse_io->scratch_buf, reply_size, 133 | 0, fuse_io->scratch_buf_idx); 134 | sqe->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 135 | 136 | io_uring_sqe *sqe2 = io.get_sqe(); 137 | 138 | io_uring_prep_splice(sqe2, fuse_io->pipe[0], 139 | -1, fuse_io->fuse_fd, -1, reply_size, 140 | SPLICE_F_MOVE| SPLICE_F_FD_IN_FIXED | SPLICE_F_NONBLOCK); 141 | sqe2->flags |= IOSQE_FIXED_FILE; 142 | 143 | auto [rc1, rc2] = co_await io.complete(std::make_pair(sqe, sqe2)); 144 | 145 | if(rc1!=reply_size || rc2!=reply_size) 146 | { 147 | std::cerr << "# Send reply failed rc1="<< rc1 << " rc2=" << rc2 << std::endl; 148 | co_return -1; 149 | } 150 | else 151 | { 152 | co_return 0; 153 | } 154 | } 155 | 156 | [[nodiscard]] fuse_io_context::io_uring_task send_reply(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 157 | const std::vector& buf) 158 | { 159 | struct io_uring_sqe *sqe; 160 | sqe = io.get_sqe(2); 161 | if(sqe==nullptr) 162 | co_return -1; 163 | 164 | DBG_PRINT(std::cout << "send unique buf: " << reinterpret_cast(buf.data())->unique << std::endl); 165 | io_uring_prep_write(sqe, fuse_io->pipe[1], 166 | buf.data(), buf.size(), 167 | 0); 168 | sqe->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 169 | 170 | io_uring_sqe *sqe2 = io.get_sqe(); 171 | if(sqe2==nullptr) 172 | co_return -1; 173 | 174 | io_uring_prep_splice(sqe2, fuse_io->pipe[0], 175 | -1, fuse_io->fuse_fd, -1, buf.size(), 176 | SPLICE_F_MOVE| SPLICE_F_FD_IN_FIXED | SPLICE_F_NONBLOCK); 177 | sqe2->flags |= IOSQE_FIXED_FILE; 178 | 179 | auto [rc1, rc2] = co_await io.complete(std::make_pair(sqe, sqe2)); 180 | 181 | if(rc1!=buf.size() || rc2!=buf.size()) 182 | { 183 | std::cerr << "# Send reply buf failed rc1="<< rc1 << " rc2=" << rc2 << std::endl; 184 | co_return -1; 185 | } 186 | else 187 | { 188 | co_return 0; 189 | } 190 | } 191 | 192 | [[nodiscard]] fuse_io_context::io_uring_task handle_unknown(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io) 193 | { 194 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 195 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 196 | 197 | out_header->error = -ENOSYS; 198 | out_header->len = sizeof(fuse_out_header); 199 | out_header->unique = fheader->unique; 200 | 201 | co_return co_await send_reply(io, fuse_io); 202 | } 203 | 204 | [[nodiscard]] fuse_io_context::io_uring_task send_attr(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 205 | uint64_t unique, uint64_t nodeid) 206 | { 207 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 208 | out_header->error = 0; 209 | out_header->len = sizeof(fuse_attr_out) + sizeof(fuse_out_header); 210 | out_header->unique = unique; 211 | 212 | fuse_attr_out* attr_out = reinterpret_cast(fuse_io->scratch_buf + sizeof(fuse_out_header)); 213 | attr_out->attr_valid = 3600; 214 | attr_out->attr_valid_nsec = 0; 215 | memset(&attr_out->attr, 0, sizeof(attr_out->attr)); 216 | 217 | DBG_PRINT(std::cout << "send_attr nodeid " << nodeid << std::endl); 218 | if(nodeid==1) 219 | { 220 | attr_out->attr.mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; 221 | attr_out->attr.ino = 1; 222 | } 223 | else if(nodeid==3) 224 | { 225 | attr_out->attr.mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; 226 | attr_out->attr.ino = 3; 227 | attr_out->attr.size = io.fuse_ring.backing_f_size; 228 | attr_out->attr.blocks = round_up(attr_out->attr.size, 512); 229 | attr_out->attr.blksize = getpagesize(); 230 | } 231 | else 232 | { 233 | out_header->error = -EACCES; 234 | out_header->len = sizeof(fuse_out_header); 235 | } 236 | 237 | co_return co_await send_reply(io, fuse_io); 238 | } 239 | 240 | [[nodiscard]] fuse_io_context::io_uring_task handle_getattr(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 241 | char* rbytes_buf) 242 | { 243 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 244 | uint64_t nodeid = fheader->nodeid; 245 | 246 | fuse_getattr_in* getattr_in = reinterpret_cast(rbytes_buf); 247 | bool getattr_fh = (getattr_in->getattr_flags & FUSE_GETATTR_FH)>0; 248 | if(getattr_fh) 249 | { 250 | DBG_PRINT(std::cout << "fattr fh" << std::endl); 251 | nodeid = getattr_in->fh; 252 | } 253 | 254 | co_return co_await send_attr(io, fuse_io, fheader->unique, nodeid); 255 | } 256 | 257 | [[nodiscard]] fuse_io_context::io_uring_task handle_setattr(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 258 | char* rbytes_buf) 259 | { 260 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 261 | uint64_t nodeid = fheader->nodeid; 262 | 263 | fuse_setattr_in* setattr_in = reinterpret_cast(rbytes_buf); 264 | if(setattr_in->fh) 265 | { 266 | DBG_PRINT(std::cout << "fattr fh" << std::endl); 267 | nodeid = setattr_in->fh; 268 | } 269 | 270 | if(nodeid==3) 271 | { 272 | DBG_PRINT(std::cout << "Set attr new size " << setattr_in->size << " denied" << std::endl); 273 | } 274 | 275 | co_return co_await send_attr(io, fuse_io, fheader->unique, nodeid); 276 | } 277 | 278 | [[nodiscard]] fuse_io_context::io_uring_task handle_lookup(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 279 | char* rbytes_buf) 280 | { 281 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 282 | 283 | DBG_PRINT(std::cout << "fuse lookup " << rbytes_buf << std::endl); 284 | 285 | std::string lname = rbytes_buf; 286 | 287 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 288 | out_header->error = 0; 289 | out_header->len = sizeof(fuse_entry_out) + sizeof(fuse_out_header); 290 | out_header->unique = fheader->unique; 291 | 292 | fuse_entry_out* entry_out = reinterpret_cast(fuse_io->scratch_buf + sizeof(fuse_out_header)); 293 | 294 | entry_out->generation = 0; 295 | entry_out->entry_valid = 3600; 296 | entry_out->entry_valid_nsec = 0; 297 | entry_out->attr_valid = 3600; 298 | entry_out->attr_valid_nsec = 0; 299 | 300 | if(lname=="volume") 301 | { 302 | DBG_PRINT(std::cout << "Looking up volume" << std::endl); 303 | entry_out->nodeid = 3; 304 | entry_out->attr = {}; 305 | entry_out->attr.mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; 306 | entry_out->attr.ino = 3; 307 | entry_out->attr.size = io.fuse_ring.backing_f_size; 308 | entry_out->attr.blocks = round_up(entry_out->attr.size, 512); 309 | entry_out->attr.blksize = getpagesize(); 310 | } 311 | else 312 | { 313 | entry_out->nodeid = 1; 314 | entry_out->attr = {}; 315 | entry_out->attr.mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; 316 | entry_out->attr.ino = 1; 317 | } 318 | 319 | co_return co_await send_reply(io, fuse_io); 320 | } 321 | 322 | [[nodiscard]] fuse_io_context::io_uring_task handle_opendir(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 323 | char* rbytes_buf) 324 | { 325 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 326 | fuse_open_in* open_in = reinterpret_cast(rbytes_buf); 327 | 328 | DBG_PRINT(std::cout << "opendir nodeid " << fheader->nodeid << std::endl); 329 | 330 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 331 | out_header->error = 0; 332 | out_header->len = sizeof(fuse_open_out) + sizeof(fuse_out_header); 333 | out_header->unique = fheader->unique; 334 | 335 | fuse_open_out* open_out = reinterpret_cast(fuse_io->scratch_buf + sizeof(fuse_out_header)); 336 | open_out->fh = 1; 337 | open_out->open_flags = open_in->flags | FOPEN_CACHE_DIR; 338 | 339 | co_return co_await send_reply(io, fuse_io); 340 | } 341 | 342 | [[nodiscard]] fuse_io_context::io_uring_task handle_open(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 343 | char* rbytes_buf) 344 | { 345 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 346 | fuse_open_in* open_in = reinterpret_cast(rbytes_buf); 347 | 348 | DBG_PRINT(std::cout << "open nodeid " << fheader->nodeid << std::endl); 349 | 350 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 351 | out_header->error = 0; 352 | out_header->len = sizeof(fuse_open_out) + sizeof(fuse_out_header); 353 | out_header->unique = fheader->unique; 354 | 355 | fuse_open_out* open_out = reinterpret_cast(fuse_io->scratch_buf + sizeof(fuse_out_header)); 356 | open_out->fh = 3; 357 | open_out->open_flags = open_in->flags | FOPEN_KEEP_CACHE | FOPEN_DIRECT_IO; 358 | 359 | co_return co_await send_reply(io, fuse_io); 360 | } 361 | 362 | [[nodiscard]] fuse_io_context::io_uring_task handle_read(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 363 | char* rbytes_buf) 364 | { 365 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 366 | uint64_t read_offset; 367 | uint32_t read_size; 368 | { 369 | fuse_read_in* read_in = reinterpret_cast(rbytes_buf); 370 | 371 | DBG_PRINT(std::cout << "read nodeid " << fheader->nodeid << " off: " << read_in->offset << " size: "<size << std::endl); 372 | 373 | if(fheader->nodeid!=3) 374 | { 375 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 376 | out_header->error = 0; 377 | out_header->unique = fheader->unique; 378 | out_header->error = -ENOENT; 379 | co_return co_await send_reply(io, fuse_io); 380 | } 381 | 382 | if(read_in->offset + read_in->size > io.fuse_ring.backing_f_size) 383 | { 384 | read_in->size = io.fuse_ring.backing_f_size - read_in->offset; 385 | DBG_PRINT(std::cout << "Reading less: " << read_in->size << std::endl); 386 | } 387 | 388 | read_offset = read_in->offset; 389 | read_size = read_in->size; 390 | } 391 | 392 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 393 | out_header->error = 0; 394 | out_header->len = sizeof(fuse_out_header) + read_size; 395 | out_header->unique = fheader->unique; 396 | 397 | io_uring_sqe* sqe1 = io.get_sqe(3); 398 | if(sqe1==nullptr) 399 | co_return -1; 400 | 401 | io_uring_prep_write_fixed(sqe1, fuse_io->pipe[1], 402 | fuse_io->scratch_buf, sizeof(fuse_out_header), 403 | -1, fuse_io->scratch_buf_idx); 404 | sqe1->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 405 | 406 | io_uring_sqe *sqe2 = io.get_sqe(); 407 | if(sqe2==nullptr) 408 | co_return -1; 409 | 410 | io_uring_prep_splice(sqe2, io.fuse_ring.backing_fd, 411 | read_offset, fuse_io->pipe[1], -1, read_size, 412 | SPLICE_F_MOVE| SPLICE_F_FD_IN_FIXED | SPLICE_F_NONBLOCK); 413 | sqe2->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 414 | 415 | io_uring_sqe *sqe3 = io.get_sqe(); 416 | if(sqe3==nullptr) 417 | co_return -1; 418 | 419 | io_uring_prep_splice(sqe3, fuse_io->pipe[0], 420 | -1, fuse_io->fuse_fd, -1, out_header->len, 421 | SPLICE_F_MOVE | SPLICE_F_FD_IN_FIXED | SPLICE_F_NONBLOCK); 422 | sqe3->flags |= IOSQE_FIXED_FILE; 423 | 424 | std::vector rcs = co_await io.complete({sqe1, sqe2, sqe3}); 425 | 426 | for(int rc: rcs) 427 | { 428 | if(rc<0) 429 | { 430 | std::cerr << "handle_read failed. rcs=" << 431 | rcs[0] << ", " << rcs[1] << ", " << 432 | rcs[2] << std::endl; 433 | co_return -1; 434 | } 435 | } 436 | 437 | if(rcs[0]len) 448 | { 449 | co_return -1; 450 | } 451 | 452 | co_return 0; 453 | } 454 | 455 | [[nodiscard]] fuse_io_context::io_uring_task handle_write(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 456 | char* rbytes_buf) 457 | { 458 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 459 | uint64_t write_offset; 460 | uint32_t write_size; 461 | { 462 | fuse_write_in* write_in = reinterpret_cast(rbytes_buf); 463 | 464 | DBG_PRINT(std::cout << "write nodeid " << fheader->nodeid << " off: " << write_in->offset << " size: "<< write_in->size << std::endl); 465 | 466 | if(fheader->nodeid!=3) 467 | { 468 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 469 | out_header->unique = fheader->unique; 470 | out_header->error = -ENOENT; 471 | out_header->len = sizeof(fuse_out_header); 472 | co_return co_await send_reply(io, fuse_io); 473 | } 474 | 475 | write_offset = write_in->offset; 476 | write_size = write_in->size; 477 | 478 | /*if(write_offset + write_size > io.fuse_ring.backing_f_size) 479 | { 480 | write_size = io.fuse_ring.backing_f_size - write_offset; 481 | std::cout << "Writing less: " << write_size << std::endl; 482 | }*/ 483 | 484 | if(fheader->len!=sizeof(fuse_in_header)+sizeof(fuse_write_in)+write_size) 485 | { 486 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 487 | out_header->unique = fheader->unique; 488 | out_header->error = -EINVAL; 489 | out_header->len = sizeof(fuse_out_header); 490 | co_return co_await send_reply(io, fuse_io); 491 | } 492 | } 493 | 494 | /*if(write_size<=4096) 495 | { 496 | io_uring_sqe* sqe1 = co_await io.get_sqe(); 497 | io_uring_prep_read_fixed(sqe1, fuse_io->pipe[0], fuse_io->scratch_buf, 498 | write_size, 0, fuse_io->scratch_buf_idx); 499 | sqe1->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 500 | 501 | io_uring_sqe* sqe2 = co_await io.get_sqe(); 502 | io_uring_prep_write(sqe2, io.fuse_ring.backing_fd, fuse_io->scratch_buf, 503 | write_size, write_offset); 504 | sqe2->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 505 | 506 | uint64_t header_unique = fheader->unique; 507 | 508 | fuse_out_header* out_header = reinterpret_cast(fuse_io->header_buf); 509 | out_header->error = 0; 510 | out_header->len = sizeof(fuse_out_header) + sizeof(fuse_write_out); 511 | out_header->unique = header_unique; 512 | 513 | fuse_write_out* write_out = reinterpret_cast(fuse_io->header_buf + sizeof(fuse_out_header)); 514 | write_out->size = write_size; 515 | write_out->padding = 0; 516 | 517 | io_uring_sqe* sqe3 = co_await io.get_sqe(); 518 | io_uring_prep_write(sqe3, fuse_io->pipe[1], 519 | fuse_io->header_buf, out_header->len, 520 | 0); 521 | sqe3->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 522 | 523 | io_uring_sqe* sqe4 = co_await io.get_sqe(); 524 | io_uring_prep_splice(sqe4, fuse_io->pipe[0], 525 | -1, io.fuse_ring.fd, -1, out_header->len, 526 | SPLICE_F_MOVE| SPLICE_F_FD_IN_FIXED | SPLICE_F_NONBLOCK); 527 | sqe4->flags |= IOSQE_FIXED_FILE; 528 | 529 | std::vector rcs = co_await io.complete({sqe1, sqe2, sqe3, sqe4}); 530 | 531 | for(int rc: rcs) 532 | { 533 | if(rc<0) 534 | co_return -1; 535 | } 536 | 537 | co_return 0; 538 | } 539 | 540 | std::cout << "Splice ... write size " << write_size << std::endl;*/ 541 | 542 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 543 | out_header->error = 0; 544 | out_header->len = sizeof(fuse_out_header) + sizeof(fuse_write_out); 545 | out_header->unique = fheader->unique; 546 | 547 | fuse_write_out* write_out = reinterpret_cast(fuse_io->scratch_buf + sizeof(fuse_out_header)); 548 | write_out->size = write_size; 549 | write_out->padding = 0; 550 | 551 | io_uring_sqe* sqe1 = io.get_sqe(3); 552 | if(sqe1==nullptr) 553 | co_return -1; 554 | 555 | io_uring_prep_splice(sqe1, fuse_io->pipe[0], 556 | -1, io.fuse_ring.backing_fd, write_offset, write_size, 557 | SPLICE_F_MOVE| SPLICE_F_FD_IN_FIXED | SPLICE_F_NONBLOCK); 558 | sqe1->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 559 | 560 | io_uring_sqe* sqe2 = io.get_sqe(); 561 | if(sqe2==nullptr) 562 | co_return -1; 563 | 564 | io_uring_prep_write_fixed(sqe2, fuse_io->pipe[1], 565 | fuse_io->scratch_buf, out_header->len, 566 | 0, fuse_io->scratch_buf_idx); 567 | sqe2->flags |= IOSQE_FIXED_FILE | IOSQE_IO_LINK; 568 | 569 | io_uring_sqe *sqe3 = io.get_sqe(); 570 | if(sqe3==nullptr) 571 | co_return -1; 572 | 573 | io_uring_prep_splice(sqe3, fuse_io->pipe[0], 574 | -1, fuse_io->fuse_fd, -1, out_header->len, 575 | SPLICE_F_MOVE| SPLICE_F_FD_IN_FIXED | SPLICE_F_NONBLOCK); 576 | sqe3->flags |= IOSQE_FIXED_FILE; 577 | 578 | std::vector rcs = co_await io.complete({sqe1, sqe2, sqe3}); 579 | 580 | if(rcs[0]<0) 581 | { 582 | out_header->error = rcs[0]; 583 | out_header->len = sizeof(fuse_out_header); 584 | co_return co_await send_reply(io, fuse_io); 585 | } 586 | 587 | if(rcs[0]size = rcs[0]; 590 | co_return co_await send_reply(io, fuse_io); 591 | } 592 | 593 | if(rcs[1]<0 || rcs[2]<0 || 594 | rcs[1]!=out_header->len || rcs[2]!=out_header->len) 595 | { 596 | std::cerr << "handle_write failed rcs=" << 597 | rcs[0] << ", " << rcs[1] << ", " << 598 | rcs[2] << std::endl; 599 | co_return -1; 600 | } 601 | 602 | DBG_PRINT(std::cout << "FUSE_WRITE done" << std::endl); 603 | 604 | co_return 0; 605 | } 606 | 607 | [[nodiscard]] fuse_io_context::io_uring_task handle_releasedir(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 608 | char* rbytes_buf) 609 | { 610 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 611 | fuse_release_in* release_in = reinterpret_cast(rbytes_buf); 612 | 613 | DBG_PRINT(std::cout << "releasedir nodeid " << fheader->nodeid << std::endl); 614 | 615 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 616 | out_header->error = 0; 617 | out_header->len = sizeof(fuse_out_header); 618 | out_header->unique = fheader->unique; 619 | 620 | co_return co_await send_reply(io, fuse_io); 621 | } 622 | 623 | [[nodiscard]] fuse_io_context::io_uring_task handle_release(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 624 | char* rbytes_buf) 625 | { 626 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 627 | fuse_release_in* release_in = reinterpret_cast(rbytes_buf); 628 | 629 | DBG_PRINT(std::cout << "release nodeid " << fheader->nodeid << std::endl); 630 | 631 | fuse_out_header* out_header = reinterpret_cast(fuse_io->scratch_buf); 632 | out_header->error = 0; 633 | out_header->len = sizeof(fuse_out_header); 634 | out_header->unique = fheader->unique; 635 | 636 | co_return co_await send_reply(io, fuse_io); 637 | } 638 | 639 | void add_dir(std::vector& buf, const std::string& name, size_t off, const struct stat& stbuf) 640 | { 641 | size_t bsize = FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + name.size()); 642 | size_t orig_off = buf.size(); 643 | buf.resize(buf.size()+bsize); 644 | fuse_dirent* dirent = reinterpret_cast(&buf[orig_off]); 645 | 646 | dirent->ino = stbuf.st_ino; 647 | dirent->namelen = name.size(); 648 | dirent->off = off; 649 | dirent->type = (stbuf.st_mode & S_IFMT) >> 12; 650 | memcpy(dirent->name, name.data(), name.size()); 651 | memset(dirent->name + name.size(), 0, bsize-FUSE_NAME_OFFSET-name.size()); 652 | } 653 | 654 | [[nodiscard]] fuse_io_context::io_uring_task handle_readdir(fuse_io_context& io, fuse_io_context::FuseIoVal& fuse_io, 655 | char* rbytes_buf) 656 | { 657 | fuse_in_header* fheader = reinterpret_cast(fuse_io->header_buf); 658 | 659 | fuse_read_in* read_in = reinterpret_cast(rbytes_buf); 660 | 661 | DBG_PRINT(std::cout << "readdir nodeid " << fheader->nodeid << " offset: " << read_in->offset << " read_flags: " 662 | << read_in->read_flags << " size: " << read_in->size << std::endl); 663 | 664 | std::vector out_buf(sizeof(fuse_out_header)); 665 | fuse_out_header* out_header = reinterpret_cast(out_buf.data()); 666 | out_header->error = 0; 667 | out_header->unique = fheader->unique; 668 | 669 | if(read_in->offset==0) 670 | { 671 | struct stat stbuf = {}; 672 | stbuf.st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; 673 | 674 | stbuf.st_ino = 2; 675 | add_dir(out_buf, ".", 1, stbuf); 676 | 677 | stbuf.st_ino = 3; 678 | add_dir(out_buf, "..", 2, stbuf); 679 | 680 | stbuf.st_ino = 4; 681 | stbuf.st_size = io.fuse_ring.backing_f_size; 682 | stbuf.st_blocks = round_up(stbuf.st_size, 512); 683 | add_dir(out_buf, "volume", 3, stbuf); 684 | } 685 | 686 | out_header = reinterpret_cast(out_buf.data()); 687 | out_header->len = out_buf.size(); 688 | 689 | co_return co_await send_reply(io, fuse_io, out_buf); 690 | } 691 | 692 | fuse_io_context::io_uring_task queue_fuse_read(fuse_io_context& io) 693 | { 694 | fuse_io_context::FuseIoVal fuse_io = io.get_fuse_io(); 695 | 696 | DBG_PRINT(std::cout << "queue_fuse_read" << std::endl); 697 | struct io_uring_sqe *sqe1 = io.get_sqe(2); 698 | struct io_uring_sqe *sqe2 = io.get_sqe(); 699 | if(sqe1==nullptr || sqe2==nullptr) 700 | co_return -1; 701 | 702 | io_uring_prep_splice(sqe1, fuse_io->fuse_fd, -1, fuse_io->pipe[1], 703 | -1, io.fuse_ring.max_bufsize, SPLICE_F_MOVE|SPLICE_F_NONBLOCK|SPLICE_F_FD_IN_FIXED); 704 | sqe1->flags |= IOSQE_IO_HARDLINK | IOSQE_FIXED_FILE; 705 | 706 | io_uring_prep_read_fixed(sqe2, fuse_io->pipe[0], fuse_io->header_buf, 707 | sizeof(fuse_in_header) + sizeof(fuse_write_in), 0, fuse_io->header_buf_idx); 708 | sqe2->flags |= IOSQE_FIXED_FILE; 709 | 710 | auto [rbytes, init_read] = co_await io.complete(std::make_pair(sqe1, sqe2)); 711 | 712 | if(rbytes<0 || rbytes(fuse_io->header_buf); 753 | 754 | DBG_PRINT(std::cout << "## fheader opcode: "<< fheader->opcode << " unique: "<< fheader->unique << " rbytes: " << rbytes << " init_read: " << init_read << std::endl); 755 | 756 | size_t req_read_rbytes = 0; 757 | bool req_read_add_zero=false; 758 | bool req_allow_add_bytes=false; 759 | switch(fheader->opcode) 760 | { 761 | case FUSE_GETATTR: 762 | req_read_rbytes = sizeof(fuse_getattr_in); 763 | break; 764 | case FUSE_SETATTR: 765 | req_read_rbytes = sizeof(fuse_setattr_in); 766 | break; 767 | case FUSE_OPENDIR: 768 | req_read_rbytes = sizeof(fuse_open_in); 769 | break; 770 | case FUSE_READDIR: 771 | req_read_rbytes = sizeof(fuse_read_in); 772 | break; 773 | case FUSE_RELEASEDIR: 774 | req_read_rbytes = sizeof(fuse_release_in); 775 | break; 776 | case FUSE_LOOKUP: 777 | req_read_rbytes = rbytes - sizeof(fuse_in_header); 778 | req_read_add_zero=true; 779 | break; 780 | case FUSE_OPEN: 781 | req_read_rbytes = sizeof(fuse_open_in); 782 | break; 783 | case FUSE_READ: 784 | req_read_rbytes = sizeof(fuse_read_in); 785 | break; 786 | case FUSE_RELEASE: 787 | req_read_rbytes = sizeof(fuse_release_in); 788 | break; 789 | case FUSE_WRITE: 790 | req_read_rbytes = sizeof(fuse_write_in); 791 | req_allow_add_bytes=true; 792 | break; 793 | default: 794 | req_read_rbytes = rbytes - sizeof(fuse_in_header); 795 | } 796 | 797 | std::vector rbytes_buf_d; 798 | char* rbytes_buf; 799 | if(req_read_rbytes>0) 800 | { 801 | if(!req_allow_add_bytes && 802 | req_read_rbytes!=rbytes - sizeof(fuse_in_header)) 803 | { 804 | std::cerr << "Unexpected request size. Expected " << 805 | (rbytes - sizeof(fuse_in_header)) << " got " << 806 | req_read_rbytes << ". Opcode=" << fheader->opcode << 807 | " Unique=" << fheader->unique << std::endl; 808 | co_return -1; 809 | } 810 | 811 | rbytes_buf = co_await read_rbytes(io, fuse_io, req_read_rbytes, 812 | req_read_add_zero, init_read - sizeof(fuse_in_header), rbytes_buf_d); 813 | if(rbytes_buf==nullptr) 814 | { 815 | std::cerr << "Reading rbytes failed." << std::endl; 816 | co_return -1; 817 | } 818 | } 819 | 820 | /*#undef DBG_PRINT 821 | #define DBG_PRINT(x) x*/ 822 | int rc; 823 | switch(fheader->opcode) 824 | { 825 | case FUSE_GETATTR: 826 | DBG_PRINT(std::cout << "FUSE_GETATTR" << std::endl); 827 | rc = co_await handle_getattr(io, fuse_io, rbytes_buf); 828 | break; 829 | case FUSE_SETATTR: 830 | DBG_PRINT(std::cout << "FUSE_SETATTR" << std::endl); 831 | rc = co_await handle_setattr(io, fuse_io, rbytes_buf); 832 | break; 833 | case FUSE_OPENDIR: 834 | DBG_PRINT(std::cout << "FUSE_OPENDIR" << std::endl); 835 | rc = co_await handle_opendir(io, fuse_io, rbytes_buf); 836 | break; 837 | case FUSE_READDIR: 838 | DBG_PRINT(std::cout << "FUSE_READDIR" << std::endl); 839 | rc = co_await handle_readdir(io, fuse_io, rbytes_buf); 840 | break; 841 | case FUSE_RELEASEDIR: 842 | DBG_PRINT(std::cout << "FUSE_RELEASEDIR" << std::endl); 843 | rc = co_await handle_releasedir(io, fuse_io, rbytes_buf); 844 | break; 845 | case FUSE_LOOKUP: 846 | DBG_PRINT(std::cout << "FUSE_LOOKUP" << std::endl); 847 | rc = co_await handle_lookup(io, fuse_io, rbytes_buf); 848 | break; 849 | case FUSE_OPEN: 850 | DBG_PRINT(std::cout << "FUSE_OPEN" << std::endl); 851 | rc = co_await handle_open(io, fuse_io, rbytes_buf); 852 | break; 853 | case FUSE_READ: 854 | DBG_PRINT(std::cout << "FUSE_READ" << std::endl); 855 | rc = co_await handle_read(io, fuse_io, rbytes_buf); 856 | break; 857 | case FUSE_RELEASE: 858 | DBG_PRINT(std::cout << "FUSE_RELEASE" << std::endl); 859 | rc = co_await handle_release(io, fuse_io, rbytes_buf); 860 | break; 861 | case FUSE_WRITE: 862 | DBG_PRINT(std::cout << "FUSE_WRITE" << std::endl); 863 | rc = co_await handle_write(io, fuse_io, rbytes_buf); 864 | break; 865 | default: 866 | DBG_PRINT(std::cout << "## Unhandled opcode: " << fheader->opcode << std::endl); 867 | rc = co_await handle_unknown(io, fuse_io); 868 | break; 869 | } 870 | /*#undef DBG_PRINT 871 | #define DBG_PRINT(x)*/ 872 | 873 | DBG_PRINT(std::cout << "## handle fuse done" << std::endl); 874 | co_return rc; 875 | } 876 | 877 | int clone_fuse_fd(int fuse_fd) 878 | { 879 | int session_fd = open("/dev/fuse", O_RDWR | O_CLOEXEC); 880 | if(session_fd==-1) 881 | { 882 | perror("Error opening /dev/fuse -2."); 883 | return -1; 884 | } 885 | 886 | int rc = ioctl(session_fd, FUSE_DEV_IOC_CLONE, &fuse_fd); 887 | if(rc==-1) 888 | { 889 | perror("Error cloning fuse fd"); 890 | return -1; 891 | } 892 | 893 | return session_fd; 894 | } 895 | 896 | int fuseuring_main(int backing_fd, const std::string& mountpoint, int max_fuse_ios, 897 | int max_background, int congestion_threshold, size_t n_threads) 898 | { 899 | umount(mountpoint.c_str()); 900 | 901 | struct stat stbuf; 902 | int rc = stat(mountpoint.c_str(), &stbuf); 903 | if(rc!=0) 904 | { 905 | perror(("Error stat() mountpoint \""+mountpoint+"\". ").c_str()); 906 | return 1; 907 | } 908 | 909 | int fuse_fd = open("/dev/fuse", O_RDWR | O_CLOEXEC); 910 | if(fuse_fd==-1) 911 | { 912 | perror("Error opening /dev/fuse."); 913 | return 2; 914 | } 915 | 916 | char rootmode[10]; 917 | snprintf(rootmode, sizeof(rootmode), "%o", stbuf.st_mode & S_IFMT); 918 | std::string mount_opts = "fd="+std::to_string(fuse_fd)+",rootmode="+std::string(rootmode)+",user_id=0,group_id=0,default_permissions,allow_other"; 919 | 920 | rc = mount("tclouddrive", mountpoint.c_str(), "fuse", MS_NOSUID | MS_NODEV|MS_NOATIME|MS_NOEXEC, mount_opts.c_str()); 921 | 922 | if(rc!=0) 923 | { 924 | perror("Error mounting fuse file system."); 925 | return 3; 926 | } 927 | 928 | std::vector init_buf(8192); 929 | rc = read(fuse_fd, init_buf.data(), init_buf.size()); 930 | 931 | if(rc<=0) 932 | { 933 | perror(("Err fuse init rc="+std::to_string(rc)).c_str()); 934 | return 4; 935 | } 936 | 937 | struct InitInMsg 938 | { 939 | fuse_in_header header; 940 | fuse_init_in init_in; 941 | }; 942 | 943 | InitInMsg* init_in = reinterpret_cast(init_buf.data()); 944 | 945 | if(init_in->header.opcode != FUSE_INIT) 946 | { 947 | std::cerr << "Unexpected opcode during init" << std::endl; 948 | return 5; 949 | } 950 | 951 | if(init_in->header.len!=sizeof(InitInMsg)) 952 | { 953 | std::cerr << "Unexpected length during init" << std::endl; 954 | return 5; 955 | } 956 | 957 | if(init_in->init_in.majorinit_in.major << std::endl; 960 | return 5; 961 | } 962 | 963 | struct InitOutMsg 964 | { 965 | fuse_out_header header; 966 | fuse_init_out init_out; 967 | }; 968 | InitOutMsg init_out; 969 | init_out.header.error = 0; 970 | init_out.header.len = sizeof(InitOutMsg); 971 | init_out.header.unique = init_in->header.unique; 972 | init_out.init_out.major = FUSE_KERNEL_VERSION; 973 | init_out.init_out.minor =FUSE_KERNEL_MINOR_VERSION; 974 | 975 | if(init_in->init_in.major>FUSE_KERNEL_VERSION) 976 | { 977 | if(write(fuse_fd, &init_out, sizeof(init_out))!=sizeof(init_out)) 978 | { 979 | perror("Error writing ver reply"); 980 | return 6; 981 | } 982 | 983 | int rc = read(fuse_fd, init_buf.data(), init_buf.size()); 984 | 985 | if(rc<=0) 986 | { 987 | perror("Err fuse init 2"); 988 | return 7; 989 | } 990 | } 991 | 992 | init_out.init_out.max_readahead = init_in->init_in.max_readahead; 993 | if( !(init_in->init_in.flags & FUSE_ASYNC_READ) 994 | || !(init_in->init_in.flags & FUSE_PARALLEL_DIROPS) 995 | || !(init_in->init_in.flags & FUSE_AUTO_INVAL_DATA) 996 | || !(init_in->init_in.flags & FUSE_HANDLE_KILLPRIV) 997 | || !(init_in->init_in.flags & FUSE_ASYNC_DIO) 998 | || !(init_in->init_in.flags & FUSE_IOCTL_DIR) 999 | || !(init_in->init_in.flags & FUSE_ATOMIC_O_TRUNC) 1000 | || !(init_in->init_in.flags & FUSE_SPLICE_READ) 1001 | || !(init_in->init_in.flags & FUSE_SPLICE_WRITE) 1002 | || !(init_in->init_in.flags & FUSE_MAX_PAGES) 1003 | || !(init_in->init_in.flags & FUSE_WRITEBACK_CACHE) 1004 | || !(init_in->init_in.flags & FUSE_EXPORT_SUPPORT) 1005 | || !(init_in->init_in.flags & FUSE_SPLICE_MOVE) ) 1006 | { 1007 | std::cerr << "Linux kernel is missing required fuse capabilities." << std::endl; 1008 | return 8; 1009 | } 1010 | 1011 | init_out.init_out.flags = FUSE_MAX_PAGES |FUSE_PARALLEL_DIROPS 1012 | | FUSE_BIG_WRITES |FUSE_ASYNC_READ | FUSE_AUTO_INVAL_DATA 1013 | | FUSE_HANDLE_KILLPRIV | FUSE_ASYNC_DIO | FUSE_IOCTL_DIR 1014 | | FUSE_ATOMIC_O_TRUNC | FUSE_SPLICE_READ | FUSE_SPLICE_WRITE 1015 | | FUSE_MAX_PAGES | FUSE_WRITEBACK_CACHE | FUSE_EXPORT_SUPPORT 1016 | | FUSE_SPLICE_MOVE; 1017 | 1018 | 1019 | init_out.init_out.max_background = max_background; 1020 | init_out.init_out.congestion_threshold = congestion_threshold; 1021 | init_out.init_out.max_pages = static_cast(fuse_max_pages); 1022 | init_out.init_out.time_gran = 1; 1023 | size_t max_write = getpagesize()*fuse_max_pages; 1024 | init_out.init_out.max_write = max_write; 1025 | 1026 | if(write(fuse_fd, &init_out, sizeof(init_out))!=sizeof(init_out)) 1027 | { 1028 | perror("Error writing init reply"); 1029 | return 9; 1030 | } 1031 | 1032 | if(n_threads<=1) 1033 | { 1034 | return fuseuring_run(max_background, max_write, backing_fd, fuse_fd, nullptr, 0); 1035 | } 1036 | else 1037 | { 1038 | struct io_uring fuse_uring; 1039 | 1040 | int rc = io_uring_queue_init(std::max(100, max_fuse_ios*2), &fuse_uring, 0); 1041 | 1042 | if(rc<0) 1043 | { 1044 | perror("Error setting up io_uring."); 1045 | return 10; 1046 | } 1047 | 1048 | int thread_rc=0; 1049 | std::vector threads; 1050 | for(size_t i=0;i fixed_fds; 1100 | std::vector reg_buffers; 1101 | 1102 | fuse_io_context::FuseRing fuse_ring; 1103 | fuse_ring.backing_fd = fixed_fds.size(); 1104 | fixed_fds.push_back(backing_fd); 1105 | fuse_ring.backing_fd_orig = backing_fd; 1106 | 1107 | size_t max_bufsize = max_write + sizeof(fuse_in_header) + sizeof(fuse_write_in); 1108 | 1109 | std::vector header_buf_v(header_buf_size*max_fuse_ios); 1110 | char* header_buf = header_buf_v.data(); 1111 | 1112 | struct iovec iov; 1113 | iov.iov_base = header_buf; 1114 | iov.iov_len = header_buf_v.size(); 1115 | size_t header_buf_idx = reg_buffers.size(); 1116 | reg_buffers.push_back(iov); 1117 | 1118 | std::vector scratch_buf_v(scratch_buf_size*max_fuse_ios); 1119 | char* scratch_buf = scratch_buf_v.data(); 1120 | iov.iov_base = scratch_buf; 1121 | iov.iov_len = scratch_buf_v.size(); 1122 | size_t scratch_buf_idx = reg_buffers.size(); 1123 | reg_buffers.push_back(iov); 1124 | 1125 | std::vector pipe_fds; 1126 | 1127 | for(size_t i=0;i new_io = std::make_unique(); 1130 | int rc = pipe2(new_io->pipe, O_CLOEXEC|O_NONBLOCK); 1131 | if(rc!=0) 1132 | { 1133 | perror("Error creating pipe."); 1134 | return 11; 1135 | } 1136 | 1137 | rc = fcntl(new_io->pipe[0], F_SETPIPE_SZ, max_bufsize); 1138 | if(rc<0) 1139 | { 1140 | perror(("Error setting pipe size to "+std::to_string(max_bufsize)+".").c_str()); 1141 | return 12; 1142 | } 1143 | 1144 | pipe_fds.push_back(new_io->pipe[0]); 1145 | pipe_fds.push_back(new_io->pipe[1]); 1146 | 1147 | fixed_fds.push_back(new_io->pipe[0]); 1148 | new_io->pipe[0] = fixed_fds.size()-1; 1149 | fixed_fds.push_back(new_io->pipe[1]); 1150 | new_io->pipe[1] = fixed_fds.size()-1; 1151 | 1152 | new_io->header_buf = header_buf; 1153 | header_buf+=header_buf_size; 1154 | new_io->header_buf_idx = header_buf_idx; 1155 | 1156 | new_io->scratch_buf = scratch_buf; 1157 | scratch_buf+=scratch_buf_size; 1158 | new_io->scratch_buf_idx = scratch_buf_idx; 1159 | 1160 | int session_fd = clone_fuse_fd(fuse_fd); 1161 | if(session_fd==-1) 1162 | return 13; 1163 | 1164 | new_io->fuse_fd = fixed_fds.size(); 1165 | fixed_fds.push_back(session_fd); 1166 | 1167 | fuse_ring.ios.push_back(std::move(new_io)); 1168 | } 1169 | 1170 | int rc = io_uring_register_files(fuse_uring, &fixed_fds[0], fixed_fds.size()); 1171 | if(rc<0) 1172 | { 1173 | perror("Error registering fuse io_uring files."); 1174 | return 13; 1175 | } 1176 | 1177 | rc = io_uring_register_buffers(fuse_uring, ®_buffers[0], reg_buffers.size()); 1178 | if(rc<0) 1179 | { 1180 | perror("Error registering fuse io_uring buffers."); 1181 | return 14; 1182 | } 1183 | 1184 | fuse_ring.ring = fuse_uring; 1185 | fuse_ring.ring_submit = false; 1186 | fuse_ring.max_bufsize = max_bufsize; 1187 | 1188 | struct stat bst; 1189 | if(fstat(backing_fd, &bst)!=0) 1190 | { 1191 | perror("Error getting backing file info"); 1192 | return 15; 1193 | } 1194 | 1195 | fuse_ring.backing_f_size = bst.st_size; 1196 | 1197 | std::cout << "Running..." << std::endl; 1198 | fuse_io_context service(std::move(fuse_ring)); 1199 | rc = service.run(queue_fuse_read); 1200 | 1201 | io_uring_unregister_buffers(fuse_uring); 1202 | io_uring_unregister_files(fuse_uring); 1203 | 1204 | io_uring_queue_exit(fuse_uring); 1205 | 1206 | for(int p: pipe_fds) 1207 | { 1208 | close(p); 1209 | } 1210 | 1211 | fuse_io_context::clear_malloc_cache(); 1212 | 1213 | return rc; 1214 | } 1215 | --------------------------------------------------------------------------------